從0開始,編寫一個(gè)組件化開發(fā)框架<一>

本文主要內(nèi)容:
組件之間如何進(jìn)行通信?暫不涉及組件單獨(dú)編譯的動(dòng)態(tài)配置。


一、新建一個(gè)項(xiàng)目

app模塊之外,再新建四個(gè)模塊,分別為usercenter(用戶中心),order(訂單),payment(支付)和common(統(tǒng)一工具);由于本框架考慮使用注解+APT技術(shù),所以再新建兩個(gè)java模塊,分別為router(注解),router_compiler(注解處理器)。項(xiàng)目結(jié)構(gòu)大致如下:

項(xiàng)目目錄結(jié)構(gòu)

二、模塊分工

app: 主模塊,負(fù)責(zé)展示商品;
common:基礎(chǔ)工具層,提供網(wǎng)絡(luò)、統(tǒng)一封裝;
usercenter:用戶中心,用戶信息操作相關(guān);
order:訂單模塊,用戶訂單相關(guān)操作;
payment:支付模塊,用戶支付訂單相關(guān)操作;
router:定義組件化相關(guān)注解;
router_compiler:注解處理器模塊;
app的主要流程:用戶打開app,進(jìn)入到app模塊,進(jìn)行商品的選購(gòu);選購(gòu)?fù)瓿芍?,下單進(jìn)入order模塊,order模塊操作完成之后,進(jìn)入支付模塊進(jìn)行付款;付款完成之后返回訂單模塊。
app模塊可以進(jìn)入到個(gè)人中心,個(gè)人中心可以查看訂單;

三、開始代碼編寫

組件與組件之間是相互沒(méi)有依賴的,那么,我們組件之間如何進(jìn)行通信呢?
一般有兩種思路:

  1. 像EventBus那樣,做一個(gè)事件總線,使用發(fā)布-訂閱的模式,各組件向總線訂閱消息,有消息時(shí)進(jìn)行處理。
  2. 做一個(gè)路由框架,用一個(gè)路由表來(lái)一一對(duì)應(yīng)各組件對(duì)外公開的業(yè)務(wù),其他組件可以通過(guò)這個(gè)路由表與相應(yīng)的組件進(jìn)行通信。

這里我們選擇第二種做法。
一種比較簡(jiǎn)單的實(shí)現(xiàn)就是在common模塊,手動(dòng)維護(hù)一個(gè)路由表,每個(gè)模塊有對(duì)外的業(yè)務(wù)就要這個(gè)路由表里增加一條路由。當(dāng)然,這是可以實(shí)現(xiàn)組件之間的通信的,不過(guò)每個(gè)模塊都去手動(dòng)修改同一個(gè)路由表,比較麻煩,也容易造成沖突,不易維護(hù)。
這里我們使用注解+注解處理器的方式,自動(dòng)的生成路由,可以避免這種情況。

首先,定義注解

router模塊里,定義一個(gè)注解:

@Target(ElementType.TYPE) //只作用于類
@Retention(RetentionPolicy.CLASS) //編譯期保留
public @interface Router {
    /**
     * 獲取路徑
     *
     * @return 路徑,做為路由表的key
     */
    String path();
}

需要提供服務(wù)的頁(yè)面就加上這個(gè)注解,如payment模塊需要對(duì)外提供支付服務(wù),那么我們?cè)?code>payment模塊里的PaymentActivity上加上Router注解,并傳入一個(gè)path:

/**
 * 支付組件首頁(yè),展示支付方式,調(diào)起具體支付流程,并返回給調(diào)用方支付結(jié)果
 */
@Router(path = "com.example.payment.PaymentActivity")
public class PaymentActivity extends BaseActivity {
    //...
}

這里的path我們直接使用類的全類名。

那么,Router注解標(biāo)記的類,我們應(yīng)該怎么處理呢?
我們應(yīng)該拿到注解標(biāo)記的類,對(duì)應(yīng)的path,生成一個(gè)把這些信息添加進(jìn)路由表,注解處理器就可以做到這個(gè)工作。

注解處理器,處理注解

@AutoService(Processor.class) //注冊(cè)
@SupportedAnnotationTypes(ProcessorConfig.ROUTER_PACKAGE) //指明需要處理的注解
@SupportedSourceVersion(SourceVersion.RELEASE_7)
// 注解處理器接收的參數(shù)
@SupportedOptions({ProcessorConfig.OPTIONS, ProcessorConfig.APT_PACKAGE})
public class RouterProcessor extends AbstractProcessor {

    // 操作Element的工具類(類,函數(shù),屬性,其實(shí)都是Element)
    private Elements elementTool;

    // type(類信息)的工具類,包含用于操作TypeMirror的工具方法
    private Types typeTool;

    // Message用來(lái)打印 日志相關(guān)信息
    private Messager messager;

    // 文件生成器, 類 資源 等,就是最終要生成的文件 是需要Filer來(lái)完成的
    private Filer filer;
    //
    private String options;  // (模塊傳遞過(guò)來(lái)的)模塊名  app,personal
    private String aptPackage; // (模塊傳遞過(guò)來(lái)的) 包名

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        this.elementTool = processingEnvironment.getElementUtils();
        this.typeTool = processingEnvironment.getTypeUtils();
        this.filer = processingEnvironment.getFiler();
        this.messager = processingEnvironment.getMessager();
        print("RouterProcessor init");
        // 只有接受到 App殼 傳遞過(guò)來(lái)的書籍,才能證明我們的 APT環(huán)境搭建完成
        options = processingEnvironment.getOptions().get(ProcessorConfig.OPTIONS);
        aptPackage = processingEnvironment.getOptions().get(ProcessorConfig.APT_PACKAGE);
        print("options = " + options);
        print("aptPackage = " + aptPackage);
        if (options != null && aptPackage != null) {
            print("APT環(huán)境搭建完成");
        } else {
            print("APT環(huán)境有問(wèn)題,請(qǐng)檢查options 和 aptPackage");
        }
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // 獲取到系統(tǒng)類Activity的類型
        TypeElement activityType = elementTool.getTypeElement(ProcessorConfig.ACTIVITY_PACKAGE);
        TypeMirror activityMirror = activityType.asType();

        Set<? extends Element> elementSet = roundEnvironment.getElementsAnnotatedWith(Router.class);
        if (elementSet != null) {
            for (Element element : elementSet) {
                print("發(fā)現(xiàn)@Router注解的地方" + element.getSimpleName());

//                Router router = element.getAnnotation(Router.class);
                // 必須是Activity
                TypeMirror elementMirror = element.asType(); // 當(dāng)前 == Activity
                if (typeTool.isSubtype(elementMirror, activityMirror)) {
                    print("檢查當(dāng)前被注解的類,確認(rèn)是Activity類或及子類");
                } else {
                    // 不匹配拋出異常,這里謹(jǐn)慎使用!考慮維護(hù)問(wèn)題
                    throw new RuntimeException("@ARouter注解目前僅限用于Activity類之上");
                }
            }

            //獲取到使用注解的類
            //通過(guò)得到的信息,自動(dòng)生成一個(gè)路由表
            TypeElement implementType = elementTool.getTypeElement(ProcessorConfig.IROUTER_PATH);
            try {
                createPathFile(elementSet, implementType);
            } catch (IOException e) {
                e.printStackTrace();
                print("自動(dòng)創(chuàng)建路由表失敗");
            }
            return true;
        }
        return false;
    }

    /**
     * PATH 生成
     *
     * @param implementType
     * @throws IOException
     */
    private void createPathFile(Set<? extends Element> elementSet, TypeElement implementType) throws IOException {
        // 判斷 map倉(cāng)庫(kù)中,是否有需要生成的文件
        if (elementSet.isEmpty()) {
            return;
        }

        //類屬性 private Map<String, Class> mRouterMap = new HashMap<>();
        TypeName mapTypeName = ParameterizedTypeName.get(
                ClassName.get(Map.class), // Map
                ClassName.get(String.class), // Map<String,
                ClassName.get(Class.class)); // Map<String, Class>

        MethodSpec.Builder onCreateMethodBuilder = MethodSpec.methodBuilder(ProcessorConfig.METHOD_ONCREATE)
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .returns(mapTypeName);

        //方法體 mRouterMap.put(string, clazz.class);
        for (Element element : elementSet) {
            Router router = element.getAnnotation(Router.class);
            onCreateMethodBuilder.addStatement("$T<$T, $T> routerMap = new $T<>()",
                    ClassName.get(Map.class), // Map
                    ClassName.get(String.class), // String
                    ClassName.get(Class.class), // Class
                    ClassName.get(HashMap.class)); // HashMap
            onCreateMethodBuilder.addStatement("routerMap.put($S, $T.class)",
                    router.path(),
                    ClassName.get(((TypeElement) element)))
                    .addStatement("return routerMap")
                    .returns(mapTypeName);
        }

        //類定義 public class RouterManager implements IRouter {...}
        String clazzName = ProcessorConfig.ROUTER_MANAGER + "$" + options;
        TypeSpec className = TypeSpec.classBuilder(clazzName)
                .addModifiers(Modifier.PUBLIC)
                .addMethod(onCreateMethodBuilder.build())
                .addSuperinterface(ClassName.get(implementType))
                .build();

        // 生成 和 類 等等,結(jié)合一體
        JavaFile.builder(aptPackage, // 包名
                className) // 類構(gòu)建完成
                .build() // JavaFile構(gòu)建完成
                .writeTo(filer); // 文件生成器開始生成類文件

        print("自動(dòng)生成" + clazzName + "成功,請(qǐng)?jiān)赽uild/generated/ap_generated_sources/debug/out" + "目錄下查看");
    }


    private void print(String msg) {
        messager.printMessage(Diagnostic.Kind.NOTE, msg);
    }

如上,我們?cè)?code>router_compiler模塊里新建一個(gè)處理器類RouterProcessor 繼承自AbstractProcessor,并實(shí)現(xiàn)其兩個(gè)方法,
init(): 做一些初始化操作,如獲取工具類,Messager(打印日志),filer(文件生成器)等等
process(): 對(duì)注解進(jìn)行處理。

這里需要說(shuō)明一下RouterProcessor類使用到的注解:
@AutoService(Processor.class) : 把此注解處理類,進(jìn)行注冊(cè)。不注冊(cè)不能發(fā)揮作用。
@SupportedAnnotationTypes(ProcessorConfig.ROUTER_PACKAGE) :指明需要處理的注解
@SupportedSourceVersion(SourceVersion.RELEASE_7) :指明Java版本

process()方法里,我們拿到Router注解處理的類集合,根據(jù)這個(gè)集合,調(diào)用createPathFile()生成我們的路由表。

createPathFile()生成路由表的過(guò)程,可以通過(guò)硬編碼的形式寫文件,不過(guò)這種方法容易出錯(cuò)。這里我們使用javapoet來(lái)生成代碼,如何使用請(qǐng)移步https://github.com/square/javapoet
下面是自動(dòng)生成的payment模塊的路由表。

public class RouterTable$payment implements IRouter {
  @Override
  public Map<String, Class> onCreate() {
    Map<String, Class> routerMap = new HashMap<>();
    routerMap.put("com/example/payment/PaymentActivity", PaymentActivity.class);
    return routerMap;
  }
}
payment模塊路由表目錄

說(shuō)明:IRouter是在模塊router里定義的一個(gè)接口,用于給各模塊的路由表制定一個(gè)標(biāo)準(zhǔn)。里面定義一個(gè)方法,返回一個(gè)Map<String,Class>對(duì)象。

public interface IRouter {
    Map<String, Class> onCreate();
}

到此,我們已經(jīng)完成一大部分了,第個(gè)模塊都生成了自己的路由表。但是這些路由表都是在各自的模塊中,依然無(wú)法被其他模塊引用到。此時(shí)我們想到,是否可以在common模塊里,定義一個(gè)RouterManager處理類,把各模塊的路由表集中到這個(gè)類里進(jìn)行管理呢?

public enum RouterManager {
    instance;

    private Map<String, Class> mRouterMap;

    public void addRouterTable(Map<String, Class> routerTable) {
        if (mRouterMap == null) {
            mRouterMap = new HashMap<>();
        }
        mRouterMap.putAll(routerTable);
    }

    public void startActivityWith(Context context, String path, Bundle bundle) {
        if (path == null || path.isEmpty()) {
            throw new RuntimeException("path can not be null or empty!");
        }

        Class clazz = mRouterMap.get(path);
        if (clazz != null) {
            Intent intent = new Intent(context, clazz);
            if (bundle != null) {
                intent.putExtras(bundle);
            }
            context.startActivity(intent);
        } else {
            ToastManager.show(context, "要啟動(dòng)的Activity未注冊(cè): " + path);
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    public void startActivityForResultWith(Context context, String path, Bundle bundle, int requestCode) {
        if (context instanceof Activity) {
            Class clazz = mRouterMap.get(path);
            if (clazz != null) {
                Intent intent = new Intent(context, clazz);
                if (bundle != null) {
                    intent.putExtras(bundle);
                }
                ((Activity) context).startActivityForResult(intent, requestCode);
            } else {
                ToastManager.show(context, "要啟動(dòng)的Activity未注冊(cè): " + path);
            }
        }
    }
}

可以看到,這里是使用枚舉定義了一個(gè)單例,提供了三個(gè)方法:
addRouterTable():往mRouterMap里添加路由表;
startActivityWith()startActivityForResultWith()則是根據(jù)根據(jù)傳入的path,對(duì)路由進(jìn)行查找,匹配之后,進(jìn)行跨組件跳轉(zhuǎn)。

上在只是提供了添加路由的方法,但是在什么時(shí)候執(zhí)行呢?
當(dāng)然是在app啟動(dòng)越早越好,因?yàn)槟悴⒉恢烙脩魪膯?dòng)app到需要跨組件需要多久,也許很快。
所以我們想到,自定義一個(gè)Application,在Application的onCreate方法里,開個(gè)異步線程,通過(guò)反射獲取實(shí)例化對(duì)應(yīng)模塊的路由表,然后調(diào)用RouterManager.instance.addRouterTable(routerMap);把路由添加進(jìn)行RouterManager

public class BaseApplication extends Application {

    private static final String TAG = "BaseApplication >>>>> ";

    @Override
    public void onCreate() {
        super.onCreate();

        new Thread(new Runnable() {
            @Override
            public void run() {
                String[] bundles = new String[]{
                        "com.example.router.RouterTable$payment",
                        "com.example.router.RouterTable$order",
                        "com.example.router.RouterTable$usercenter"
                };
                for (String bundle : bundles) {
                    try {
                        Class clazz = Class.forName(bundle);
                        IRouter iRouter = (IRouter) clazz.newInstance();
                        Map<String, Class> routerMap = iRouter.onCreate();
                        RouterManager.instance.addRouterTable(routerMap);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    }
                }
                Log.i(TAG, "路由表已建好...");
            }
        }).start();
    }
}

執(zhí)行跨組件通信

order模塊的OrderActivity里,通過(guò)RouterManager.instance.startActivityForResultWith(this, "com/example/payment/PaymentActivity", bundle, payRequestCode);調(diào)用到支付模塊進(jìn)行支付:

@Router(path = "com.example.order.OrderActivity")
public class OrderActivity extends BaseActivity {
    private static final int payRequestCode = 111;
    private Button btnPay;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_order);

        initView();
    }

    @Override
    public void initView() {
        btnPay = findViewById(R.id.btnPay);
        btnPay.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        super.onClick(v);
        if (v.getId() == R.id.btnPay) {
            Bundle bundle = new Bundle();
            bundle.putString("orderSn", "123456789");
            bundle.putFloat("orderAmount", 99.99f);
            RouterManager.instance.startActivityForResultWith(this, "com.example.payment.PaymentActivity", bundle, payRequestCode);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == payRequestCode) {
            if (resultCode != Activity.RESULT_OK) {
                return;
            }

            boolean paySuccess = data != null && data.getBooleanExtra("payResult", false);
            toast("訂單'123456789'支付" + (paySuccess ? "成功" : "失敗"));
        }
    }
}

到此,我們的組件化后,組件(Activity與Activity)之間的通信就完成了。

你可能會(huì)問(wèn)了,如果與另一個(gè)組件的非Activity類進(jìn)行通信,怎么做呢?其實(shí)原理都是一樣的,這里就不貼代碼了。

最后,再附上payment模塊的gradle配置

apply plugin: 'com.android.library'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"


    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles 'consumer-rules.pro'

        // 都是為了 傳遞給 注解處理器
        // 在gradle文件中配置選項(xiàng)參數(shù)值(用于APT傳參接收)
        // 切記:必須寫在defaultConfig節(jié)點(diǎn)下
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [moduleName: project.getName(), packageNameForAPT: packageNameForAPT] //packageNameForAPT只是一個(gè)字符串,一個(gè)包名,存入模塊生成的路由類。
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

    // 公共基礎(chǔ)庫(kù)
    implementation project(path: ":common")

    // router 專用 注解模塊
    implementation project(path: ":router")

    // router 專用 注解處理器
    annotationProcessor project(path: ':router_compiler')
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容