Android應(yīng)用模板之項(xiàng)目架構(gòu)和路由

應(yīng)用模板代碼地址:https://github.com/thfhongfeng/AndroidAppTemplate

項(xiàng)目架構(gòu)和路由介紹

Android項(xiàng)目架構(gòu)主要目的就是實(shí)現(xiàn)業(yè)務(wù)的模塊化,使之成為可插拔可配置的組件:
業(yè)務(wù)模塊化:這點(diǎn)Gradle的模塊化編譯實(shí)際上已經(jīng)實(shí)現(xiàn)。
模塊間的通信:模塊化后,模塊間的通信成為重點(diǎn)。這個(gè)有很多現(xiàn)成的庫(kù)幫我們實(shí)現(xiàn)了,比如Arouter。


Arouter的路由功能的實(shí)現(xiàn)主要分三個(gè)階段:
編譯階段:Arouter在編譯階段通過(guò)注解處理器將標(biāo)記的通信類(lèi)收集起來(lái)生成處理這些信息的類(lèi)文件(類(lèi)似ARouter$$Group$$XXX類(lèi)文件),并放在指定的包中(com.alibaba.android.arouter.routes )。
初始化階段:Arouter初始化時(shí)通過(guò)掃描指定的包(com.alibaba.android.arouter.routes ),將里面的類(lèi)進(jìn)行初始化。然后調(diào)用指定的方法(loadInto)將通信類(lèi)放到幾個(gè)靜態(tài)的Map中。
使用階段:在使用過(guò)程中通過(guò)標(biāo)記在Map中找到對(duì)應(yīng)的通信類(lèi)了,然后就是實(shí)例化這些通信類(lèi),根據(jù)通信類(lèi)的類(lèi)別做出相應(yīng)的處理和返回相應(yīng)的結(jié)果了。


而筆者應(yīng)用模板的架構(gòu)正是基于Arouter。
Arouter的路由功能雖然很強(qiáng)大,但仍然對(duì)業(yè)務(wù)代碼有入侵。為了盡量減少對(duì)業(yè)務(wù)代碼的入侵,應(yīng)用模板搭架了一個(gè)路由模塊router。router通信模塊是一個(gè)通信規(guī)范化的模塊,Arouter只是通信的一種實(shí)現(xiàn)方式。
router模塊的目錄結(jié)構(gòu):

router模塊目錄結(jié)構(gòu)

應(yīng)用模板模塊間通信的主要分兩塊(新增模塊時(shí)模塊間通信的搭建):
router模塊的command集:新增一個(gè)模塊時(shí),如果此模塊需要給其它模塊提供模塊間服務(wù),就需要在router模塊中添加該模塊提供給其他模塊使用的服務(wù)命令集RouterXxxCommand。
router模塊的command集

業(yè)務(wù)模塊的提供的remote通信服務(wù):新增一個(gè)模塊時(shí),如果此模塊需要給其它模塊提供模塊間服務(wù),就需要在本模塊中添加提供具體服務(wù)的類(lèi)XxxRemoteService和服務(wù)協(xié)議類(lèi)XxxArouterService。XxxRouterClient為本模塊調(diào)用其它模塊的統(tǒng)一服務(wù)出口類(lèi)。
業(yè)務(wù)模塊的提供的remote通信服務(wù)目錄結(jié)構(gòu)

以上兩塊都是通過(guò)注解的方式來(lái)實(shí)現(xiàn)的,下面進(jìn)行解析說(shuō)明。

本應(yīng)用架構(gòu)模塊間通信的原理

從使用出發(fā)一步一步解析
上面已經(jīng)展示了Login模塊的模塊間通信設(shè)施的搭架。那么我們來(lái)看看Welcome模塊是如何使用Login模塊提供的服務(wù)的。


Welcome模塊目錄結(jié)構(gòu)

Welcome模塊在合適的位置會(huì)通過(guò)調(diào)用WelcomeRouterClient里的autoLogin嘗試自動(dòng)登錄。

public class WelcomeRouterClient {
    public static void callCommand(Context context, String bundleKey,
                                   String command, Bundle args, IRouterCallback callback) {
        RouterManager.getInstance(bundleKey).callUiCommand(context,
                command, args, callback);
    }

    public static void autoLogin(Context context, Bundle args, IRouterCallback callback) {
        RouterManager.getInstance(ConfigKey.BUNDLE_LOGIN_KEY).callOpCommand(context,
                RouterLoginCommand.autoLogin, args, callback);
    }

    public static void goMainHomeActivity(Context context, Bundle args, IRouterCallback callback) {
        RouterManager.getInstance(ConfigKey.BUNDLE_MAIN_KEY).callUiCommand(context,
                RouterMainCommand.goMainHomeActivity, args, callback);
    }
}

autoLogin方法先根據(jù)Key獲取RouterManager實(shí)例??纯慈绾潍@取實(shí)例的

public class RouterManager {
    public static IRouterManager getInstance(String bundleKey) {
        switch (BuildConfig.APP_THIRD_ROUTER_PROVIDER) {
            case "arouter":
                return ARouterManager.getInstance(bundleKey);
            default:
                return ARouterManager.getInstance(bundleKey);
        }
    }
}

RouterManager的規(guī)劃是為了讓模塊間通信的第三方工具(如Arouter)可插拔,即也可以用其它的三方模塊間通信庫(kù)來(lái)實(shí)現(xiàn)。目前只實(shí)現(xiàn)了使用Arouter的方式,所以都會(huì)去調(diào)用ArouterMananger的getInstance方法。

public class ARouterManager implements IRouterManager {
    private final String TAG = LogUtils.makeLogTag(this.getClass());

    private static volatile List<String> mClassNameList = new ArrayList<>();
    private static volatile HashMap<String, ARouterManager> mInstanceMap = new HashMap<>();

    private String mBundleKey = "";
    private String mRemoteAction = "";

    static {
        try {
            mClassNameList = AndroidClassUtils.getFileNameByPackageName(AppUtils.getApplicationContext(),
                    "com.pine.router.command");
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private ARouterManager(@NonNull String bundleKey) {
        mBundleKey = bundleKey;
        for (int i = 0; i < mClassNameList.size(); i++) {
            try {
                Class<?> clazz = Class.forName(mClassNameList.get(i));
                ARouterRemoteAction remoteAction = clazz.getAnnotation(ARouterRemoteAction.class);
                if (remoteAction != null) {
                    if (mBundleKey.equals(remoteAction.Key())) {
                        mRemoteAction = remoteAction.RemoteAction();
                        break;
                    }
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    public static ARouterManager getInstance(@NonNull String bundleKey) {
        if (mInstanceMap.get(bundleKey) == null) {
            synchronized (ARouterManager.class) {
                if (mInstanceMap.get(bundleKey) == null) {
                    mInstanceMap.put(bundleKey, new ARouterManager(bundleKey));
                }
            }
        }
        return mInstanceMap.get(bundleKey);
    }

    ……
    ……
    public void callCommand(final String commandType, final Context context, String commandName,
                            Bundle args, final IRouterCallback callback) {
        if (!checkBundleValidity(commandType, context, callback)) {
            return;
        }
        ARouterBundleRemote routerService = ((ARouterBundleRemote) ARouter.getInstance().build(mRemoteAction)
                .navigation(context, new NavigationCallback() {
                    @Override
                    public void onFound(Postcard postcard) {
                        LogUtils.d(TAG, "callOpCommand path:'" + postcard.getPath() + "'onFound");
                    }

                    @Override
                    public void onLost(Postcard postcard) {
                        LogUtils.d(TAG, "callOpCommand path:'" + postcard.getPath() + "'onLost");
                        if (callback != null && !callback.onFail(IRouterManager.FAIL_CODE_LOST, "onLost")) {
                            onCommandFail(commandType, context, IRouterManager.FAIL_CODE_LOST, "onLost");
                        }
                    }

                    @Override
                    public void onArrival(Postcard postcard) {
                        LogUtils.d(TAG, "callOpCommand path:'" + postcard.getPath() + "'onArrival");
                    }

                    @Override
                    public void onInterrupt(Postcard postcard) {
                        LogUtils.d(TAG, "callOpCommand path:'" + postcard.getPath() + "'onInterrupt");
                        if (callback != null && !callback.onFail(IRouterManager.FAIL_CODE_INTERRUPT, "onInterrupt")) {
                            onCommandFail(commandType, context, IRouterManager.FAIL_CODE_INTERRUPT, "onInterrupt");
                        }
                    }
                }));
        if (routerService != null) {
            routerService.call(context, commandName, args, callback);
        }
    }
}

ARouterManager類(lèi)承載了主要的實(shí)現(xiàn)方法??梢钥吹皆陬?lèi)初始化的時(shí)候會(huì)去查找“com.pine.router.command”包下的所有類(lèi),而在構(gòu)造方法中會(huì)遍歷這些類(lèi),找到有注解@ARouterRemoteAction的類(lèi),并根據(jù)Key(模塊標(biāo)記)值,找到對(duì)應(yīng)的RemoteAction并保存起來(lái)。
看一下RouterLoginCommand這個(gè)類(lèi)

@ARouterRemoteAction(Key = ConfigKey.BUNDLE_LOGIN_KEY, RemoteAction = "/login/service")
public interface RouterLoginCommand {
    String goLoginActivity = "goLoginActivity";
    String autoLogin = "autoLogin";
    String logout = "logout";
}

因此,這個(gè)getInstance方法實(shí)際上就是獲取Key值對(duì)應(yīng)的ARouterManager對(duì)象,這個(gè)對(duì)象在初始化的時(shí)候保存RemoteAction到mRemoteAction成員變量中。實(shí)際上這個(gè)mRemoteAction就是Arouter的@Route注解的path值。
在Welcome模塊中獲取到RouterManager對(duì)象后調(diào)用的callXxxCommand,實(shí)際上最后都會(huì)走到RouterManager的callCommand方法。
callCommand方法就是我們熟悉的Arouter的使用了,而這個(gè)mRemoteAction對(duì)應(yīng)的@Route注解的類(lèi)就是Login模塊的LoginARouterRemote。

@Route(path = "/login/service")
public class LoginARouterRemote extends ARouterBundleRemote<LoginRemoteService> {

}

這個(gè)類(lèi)是一個(gè)空類(lèi),通過(guò)@Route注解讓Arouter找到它,在其父類(lèi)ARouterBundleRemote中通過(guò)泛型找到實(shí)際的服務(wù)實(shí)現(xiàn)類(lèi)LoginRemoteService。
最后調(diào)用ARouterBundleRemote的call方法,call方法中通過(guò)@RouterCommand注解找到對(duì)應(yīng)的服務(wù)方法,調(diào)用該方法實(shí)現(xiàn)模塊間的調(diào)用服務(wù)。

本應(yīng)用模板之所以這樣路由的原因,主要有兩個(gè):
1. 實(shí)現(xiàn)第三方路由庫(kù)的可插拔
2. 進(jìn)一步解耦業(yè)務(wù)代碼和路由代碼


搭架好業(yè)務(wù)模塊的路由功能后,往后的服務(wù)接口的添加只需要兩步:
1. 在router模塊中的RouterXxxCommand中添加一個(gè)表示該服務(wù)接口名的常量yyy
2. 在業(yè)務(wù)模塊中的XxxRemoteService中編寫(xiě)實(shí)現(xiàn)方法,并添加注解@RouterCommand,CommandName值為第一步中添加的常量

其它模塊中調(diào)用該服務(wù)接口:

 RouterManager.getInstance(ConfigKey.BUNDLE_XXX_KEY).callOpCommand(context,
                RouterXxxCommand.yyy, args, callback);
2019-08-26變更:

router模塊的command集移到了base模塊中,通過(guò)調(diào)用RouterManager的init方法,傳入command集所在的包名來(lái)初始化。從而將command集與router模塊分離,使得router模塊成為方法庫(kù),在開(kāi)發(fā)過(guò)程中不再需要頻繁更改。

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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