Android組件化----完全解耦實(shí)踐方案<一>

首先粘貼源碼地址,歡迎frok,歡迎start
源碼地址

目前,Android 組件化普遍使用于移動(dòng)開發(fā),但是組件化的初衷是為了解耦代碼,并行開發(fā)效率;小型app似乎會(huì)care不到,完全解耦的組件化會(huì)在app越來越臃腫的時(shí)候帶來很大的提升;

1.組件化介紹

ok,那么我們需要知道完全解耦的組件化框架應(yīng)該注意哪些點(diǎn):

  1. 主app只加載業(yè)務(wù)組件,不可調(diào)用組件;組件與組件之間不存在調(diào)用關(guān)系;這樣無論是主app和業(yè)務(wù)組件都是完全獨(dú)立,完全解耦的;
  2. 主app和組件都依賴common組件,通過common的注冊(cè)和分發(fā)實(shí)現(xiàn)組件之間的交互,這個(gè)common我們姑且叫做業(yè)務(wù)主線
  3. android中page使用common下層接口和路由進(jìn)行實(shí)現(xiàn)(在本框架中,ARouter實(shí)現(xiàn)Activity跳轉(zhuǎn),ARouter-Interceptor實(shí)現(xiàn)Activity跳轉(zhuǎn)的攔截;Fragment通過common下沉注冊(cè)分發(fā)實(shí)現(xiàn)Fragment的填充)
  4. 每一個(gè)組件應(yīng)當(dāng)是一個(gè)app可單獨(dú)編譯:Library和Application之間轉(zhuǎn)化使用gradle配置相應(yīng)的Manifest和applicationId
component.jpg

2.單獨(dú)編譯組件化配置(gradle)

zhujianmulu.png

依賴關(guān)系

  • App 依賴common
  • Home/Login/News 依賴common
  • common 依賴component-base

2.1. 首先在整個(gè)工程的gradle.properties中配置組件 Library/Application切換的開關(guān):

isRunLogin = false  //login組件
isRunHome = false //home組件
isRunNews = false  //news組件

2.2. 由于android中Library(組件)/Application切換時(shí)的差異,需要單獨(dú)配置主見
以home組件為例:
首先開build.gradle:

//注釋1: 配置切換application/Library的打包
if (isRunHome.toBoolean()){
    apply plugin: 'com.android.application'
}else{
    apply plugin: 'com.android.library'
}

android {
    .......

    sourceSets {
       //注釋2: Library/Application切換  AndroidManifest  
        main {
            if (isRunLogin.toBoolean()){
                manifest.srcFile 'src/main/AndroidManifest.xml'
            }else{
                manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
            }
        }
    }

    ......

}
......

  • 注釋1: 切換application/Libaray的打包配置
  • 注釋2: Application為單獨(dú)編譯,需要有applicationId,并且主Activity需要配置main屬性;
    Libaray為集成編譯,組件不能有applicationId,且不可以設(shè)置啟動(dòng)的main Activity
    下面看集成編譯(Library)和單獨(dú)編譯(Application)的Manifest配置:


    1.png
//集成編譯,打包為Library
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="component.android.com.home">

    <application android:theme="@style/home_AppTheme">
        <activity android:name=".view.activity.HomeActivity"/>
    </application>

</manifest>

//單獨(dú)編譯,打包為單獨(dú)Application 可單獨(dú)編譯
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="component.android.com.home">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:name=".global.HomeApp"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/home_AppTheme">
        <activity android:name=".view.activity.HomeActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


3.組件之間activity跳轉(zhuǎn)(Actiivity跳轉(zhuǎn))

3.1. 組件之間的activity跳轉(zhuǎn),這里使用ARouter

ARouter是阿里開源的一種頁面跳轉(zhuǎn)task

首先看ARouter在build.gralde的配置:

//主app build.gradle
......
dependencies {
    ......
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
 
}
//home/login/news 組件  build.gradle
dependencies {
    ........
    annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
}

//common build.gradle

dependencies {
    ......
    api 'com.alibaba:arouter-api:1.3.1'
    // arouter-compiler 的注解依賴需要所有使用 ARouter 的 module 都添加依賴
    annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
}


3.2 在app和各組件中進(jìn)行page跳轉(zhuǎn)

  • 首先是ARouter的初始化

public class MainApplication extends BaseApp {

    @Override
    public void onCreate() {
        super.onCreate();
        //在主app中初始化ARouter
        initRouter();
        initMoudleApp(this);
        initMoudleData(this);
    }

    private void initRouter() {
        if (BuildConfig.DEBUG){
            //打印日志
            ARouter.openLog();
            ARouter.openDebug();
        }
        ARouter.init(this);
    }
    @Override
    public void initMoudleApp(Application application) {
        for (String moduleApp : AppConfig.moduleApps) {
            try {
                Class clazz = Class.forName(moduleApp);
                BaseApp baseApp = (BaseApp) clazz.newInstance();
                baseApp.initMoudleApp(this);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void initMoudleData(Application application) {
        for (String moduleApp : AppConfig.moduleApps) {
            try {
                Class clazz = Class.forName(moduleApp);
                BaseApp baseApp = (BaseApp) clazz.newInstance();
                baseApp.initMoudleData(this);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    }
}



//組件中  home--HomeApplication
public class HomeApp extends BaseApp {


    @Override
    public void initMoudleApp(Application application) {
        if (BuildConfig.DEBUG){
            //打印日志
            ARouter.openLog();
            ARouter.openDebug();
        }
        ARouter.init(this);
    }

    @Override
    public void initMoudleData(Application application) {

    }
}

這里需要注意一下 當(dāng)集成編譯時(shí)候 ,組件僅僅是一個(gè)組件,不會(huì)單獨(dú)具備Applicagtion入口,所以需要在主app的MainApplication中利用反射的方式 initMoudleData/initMoudleData進(jìn)行ARouter等初始化的配置;
下面看ARouter的跳轉(zhuǎn)實(shí)例:

//app/MainActivity
....
private void initClick() {
        findViewById(R.id.btn_nav_home).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ARouter.getInstance().build("/home/homeActivity").navigation()
          }
}

在app中實(shí)現(xiàn)跳轉(zhuǎn),但是這個(gè) path/home/homeActivity需要在home組件目標(biāo)位置添加注解才能實(shí)現(xiàn)activity的跳轉(zhuǎn):

@Route(path = "/myhome/homeActivity")
public class HomeActivity extends AppCompatActivity {

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

這樣 就成功實(shí)現(xiàn)類組件之間的activity的跳轉(zhuǎn);

4.組件之間的邏輯交互

App點(diǎn)擊跳轉(zhuǎn)home,須判斷登錄邏輯:

  • 1.登錄則跳轉(zhuǎn)home組件的homeActivity
  • 2.未登錄則跳轉(zhuǎn)login組件的loginActivity,點(diǎn)擊登錄,再重復(fù)以上邏輯
    這樣 主app,home和login就實(shí)現(xiàn)了一個(gè)簡單的交互邏輯


    2.png

    首先開component:

//ILoginService
public interface ILoginService {
    boolean getLoginStatus();
    int getLoginUserId();
}
//DefultLoginService
public class DefultLoginService implements ILoginService {
    @Override
    public boolean getLoginStatus() {
        return false;
    }
    @Override
    public int getLoginUserId() {
        return 0;
    }
}
//ComponentServiceFactory
public class ComponentServiceFactory {

    ......

    public static ComponentServiceFactory getInstance(Context context){
        if (instance == null){
            synchronized (ComponentServiceFactory.class){
                if (instance == null){
                    instance = new ComponentServiceFactory();
                }
            }
        }
        return instance;
    }


    private ILoginService loginService;

    public void setLoginService(ILoginService iloginService){
        loginService = iloginService;
    }


    public ILoginService getLoginService(){
        if (loginService == null){
            loginService = new DefultLoginService();
        }
        return loginService;
    }

}


然后在login中通過common的ComponentServiceFactory注冊(cè)對(duì)應(yīng)的loginService

//LoginService
public class LoginService implements ILoginService {
    @Override
    public boolean getLoginStatus() {
        return AccountUtils.getInstance().isAccountStatus();
    }

    @Override
    public int getLoginUserId() {
        return 0;
    }
}

//Login組件LoginApp注冊(cè)service
........
public class LoginApp extends BaseApp {

    @Override
    public void initMoudleApp(Application application) {
        Log.i("LoginApp","initMoudleApp");
        if (BuildConfig.DEBUG){
            //打印日志
            ARouter.openLog();
            ARouter.openDebug();
        }
        ARouter.init(this);
        ComponentServiceFactory.getInstance(this).setLoginService(new LoginService());
    }

    @Override
    public void initMoudleData(Application application) {

    }
}

在App-MainActivity中跳轉(zhuǎn)homeActivity,在home組件中使用ARouter的攔截器:

//app-mainActivity
private void initClick() {
        findViewById(R.id.btn_nav_home).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ARouter.getInstance().build("/home/homeActivity").navigation(MainActivity.this, new NavCallback() {
                    //ARouter攔截器的監(jiān)聽
                    @Override
                    public void onArrival(Postcard postcard) {
                        LogUtils.LogI("loginInterceptor","done");
                    }

                    @Override
                    public void onFound(Postcard postcard) {
                        //super.onFound(postcard);
                        LogUtils.LogI("loginInterceptor","found");
                    }

                    @Override
                    public void onLost(Postcard postcard) {
                        //super.onLost(postcard);
                        LogUtils.LogI("loginInterceptor","lost");
                    }

                    @Override
                    public void onInterrupt(Postcard postcard) {
                        //super.onInterrupt(postcard);
                        LogUtils.LogI("loginInterceptor","interrupt");
                    }
                });
            }
        });
    }


//home-HomeInterceptor
@Interceptor(priority = 1,name = "homeInterceptor")
public class HomeInterceptor implements IInterceptor {
    private Context context;

    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {
        switch (postcard.getPath()){
            case "/myhome/homeActivity":
                //通過component進(jìn)行邏輯交互
                if (ComponentServiceFactory.getInstance(context).getLoginService().getLoginStatus()){
                    callback.onContinue(postcard);
                }else {
                    ARouter.getInstance().build("/login/loginActivity").navigation();
                    //callback.onInterrupt(new RuntimeException("請(qǐng)登錄"));
                    //callback.onContinue(postcard);
                }
                break;
            default:
                callback.onContinue(postcard);
                break;

        }
    }

    @Override
    public void init(Context context) {
        this.context = context;
    }
}

//login-loginInterceptor
@Interceptor(priority = 2,name = "loginInterceptor")
public class LoginInterceptor implements IInterceptor {
    private Context context;

    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {
        switch (postcard.getPath()){
            case "/login/loginActivity":
                LogUtils.LogI("loginInterceptor","請(qǐng)點(diǎn)擊登錄按鈕");
                callback.onContinue(postcard);
                break;
            default:
                callback.onContinue(postcard);
            //在每一個(gè)組件中添加一個(gè)navi的攔截器  邏輯在

        }
    }

    @Override
    public void init(Context context) {
        this.context = context;
    }
}
  • 1.在跳轉(zhuǎn)homeActivity時(shí),跳轉(zhuǎn)到home組件的homeInterceptor攔截器
  • 2.在homeInterceptor中通過component獲取login注冊(cè)的lohginservice來獲取登錄狀態(tài),實(shí)現(xiàn)下一步跳轉(zhuǎn)

可以看到 app 通過ARouter跳home home通過component的注冊(cè)分發(fā),判斷登錄邏輯 進(jìn)行下一步跳轉(zhuǎn);這樣就實(shí)現(xiàn)了不依賴其他組件的邏輯交互

5.組件化fragment解耦

在android中我們使用最多的就是fragment,一般情況下 我們會(huì)實(shí)例化fragment再進(jìn)行下一步邏輯;為了解耦我們?cè)赾omponent中注冊(cè)fragment接口,在相應(yīng)組件中注冊(cè)fragmentservice,在其他組件中實(shí)現(xiàn)分發(fā):

//component-LoginFragmentService
public class LoginFragmentService implements IFragmentService {


    @Override
    public Fragment getFragment(String tag) {
        return new LginHomeFragment();
    }

    @Override
    public void newFragment(Activity activity, int resId, FragmentManager fragmentManager, Bundle bundle, String tag) {
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.add(resId,new LginHomeFragment(),tag);
        transaction.commit();
    }
}

//component-ComponentServiceFactory
public class ComponentServiceFactory {

    private static volatile ComponentServiceFactory instance;
    private IFragmentService newsFragmentService;
    private IFragmentService homeFragmentService;
    private IFragmentService loginFragmentService;

    public static ComponentServiceFactory getInstance(Context context){
        if (instance == null){
            synchronized (ComponentServiceFactory.class){
                if (instance == null){
                    instance = new ComponentServiceFactory();
                }
            }
        }
        return instance;
    }

........
    //主冊(cè)fragmentservice入口
    public void setHomeFragmentService(IFragmentService iFragmentService){
        homeFragmentService = iFragmentService;
    }

    public void setLoginFragmentService(IFragmentService iFragmentService){
        loginFragmentService = iFragmentService;
    }

    public void setNewsFragmentService(IFragmentService iFragmentService){
        newsFragmentService = iFragmentService;
    }

    public IFragmentService getNewsFragmentService() {
        return newsFragmentService;
    }

    public IFragmentService getHomeFragmentService() {
        return homeFragmentService;
    }

    public IFragmentService getLoginFragmentService() {
        return loginFragmentService;
    }
}


在home組件中進(jìn)行fragmentservice的注冊(cè)工作:

//home-HomeApp
public class HomeApp extends BaseApp {


    @Override
    public void initMoudleApp(Application application) {
        if (BuildConfig.DEBUG){
            //打印日志
            ARouter.openLog();
            ARouter.openDebug();
        }
        ARouter.init(this);
        ComponentServiceFactory.getInstance(this).setHomeFragmentService(new HomeFragmentService());
    }

    @Override
    public void initMoudleData(Application application) {

    }
}

在App中調(diào)用:

app-MainActivity:
 private void initBaseView() {
        FragmentManager supportFragmentManager = getSupportFragmentManager();
        ComponentServiceFactory.getInstance(this)
                .getHomeFragmentService().newFragment(this,R.id.content,supportFragmentManager,null,null);

    }

這樣一個(gè)home組件中的homeFragment就加載到主app的xml中
同理組件之間的fragment引用亦如此
注意框架中component-IFragmentService實(shí)現(xiàn)了兩個(gè)方法:

//獲取目標(biāo)的fragment來進(jìn)行操作
    Fragment getFragment(String tag);

    //用于固定的區(qū)域來填充相應(yīng)fragment
    void newFragment(Activity activity, int resId, FragmentManager fragmentManager, Bundle bundle, String tag);
  • getFragment(String tag);為獲取目標(biāo)fragment接口,獲取到實(shí)例之后開發(fā)者自己實(shí)現(xiàn)fragment相關(guān)邏輯
  • newFragment(...);適用于在布局中靜態(tài)添加fragment,一步到位

當(dāng)然框架中還有部分限制組件資源的gradle配置,有興趣可以在github下載demo
源碼地址

感謝閱讀 歡迎github star

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌柱宦,老刑警劉巖逆粹,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件萤晴,死亡現(xiàn)場離奇詭異珍坊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)青柄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來预侯,“玉大人致开,你說我怎么就攤上這事∥冢” “怎么了双戳?”我有些...
    開封第一講書人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長校坑。 經(jīng)常有香客問我拣技,道長,這世上最難降的妖魔是什么耍目? 我笑而不...
    開封第一講書人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任膏斤,我火速辦了婚禮,結(jié)果婚禮上邪驮,老公的妹妹穿的比我還像新娘莫辨。我一直安慰自己,他們只是感情好毅访,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開白布沮榜。 她就那樣靜靜地躺著,像睡著了一般喻粹。 火紅的嫁衣襯著肌膚如雪蟆融。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,775評(píng)論 1 307
  • 那天守呜,我揣著相機(jī)與錄音型酥,去河邊找鬼山憨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛弥喉,可吹牛的內(nèi)容都是我干的郁竟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼由境,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼棚亩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起虏杰,我...
    開封第一講書人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤讥蟆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后嘹屯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體攻询,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年州弟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了钧栖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡婆翔,死狀恐怖拯杠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情啃奴,我是刑警寧澤潭陪,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站最蕾,受9級(jí)特大地震影響依溯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瘟则,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一黎炉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧醋拧,春花似錦慷嗜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至菌赖,卻和暖如春缭乘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背琉用。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來泰國打工堕绩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留薄啥,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓逛尚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親刁愿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子绰寞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

推薦閱讀更多精彩內(nèi)容