終極組件化框架項(xiàng)目方案詳解

目錄

1.什么是組件化?
2.為什么需要組件化和組件化帶來的好處?
3.組件化的基本框架
4.組件化框架的具體實(shí)現(xiàn)
4.1.基類庫的封裝
4.2組件模式和集成模式切換的實(shí)現(xiàn)
4.3第三方開源庫和組件版本號的管理
4.4.組件間通信實(shí)現(xiàn)
5.組件合并時(shí)res資源和AndroidManifest配置的問題
6.組件全局application的實(shí)現(xiàn)和數(shù)據(jù)的初始化
7.組件內(nèi)網(wǎng)絡(luò)請求和攔截器的實(shí)現(xiàn)
8.組件化實(shí)現(xiàn)的技術(shù)難點(diǎn)
8.1.greendao數(shù)據(jù)庫在組件內(nèi)的實(shí)現(xiàn)
8.2.資源命名沖突
8.3.butterKnife不能使用的原因
9.組件化與熱修復(fù)的無縫連接
10.結(jié)束語

前言

本文所講的組件化案例是基于自己開源的組件化框架項(xiàng)目
github上地址https://github.com/HelloChenJinJun/NewFastFrame
其中即時(shí)通訊(Chat)模塊是單獨(dú)的項(xiàng)目
github上地址https://github.com/HelloChenJinJun/TestChat

1.什么是組件化?

項(xiàng)目發(fā)展到一定階段時(shí),隨著需求的增加以及頻繁地變更,項(xiàng)目會(huì)越來越大,代碼變得越來越臃腫,耦合會(huì)越來越多,開發(fā)效率也會(huì)降低,這個(gè)時(shí)候我們就需要對舊項(xiàng)目進(jìn)行重構(gòu)即模塊的拆分,官方的說法就是組件化。

2.為什么需要組件化和組件化帶來的好處?

1、 現(xiàn)在Android項(xiàng)目中代碼量達(dá)到一定程度,編譯將是一件非常痛苦的事情,一般都需要變異5到6分鐘。Android studio推出instant run由于各種缺陷和限制條件(比如采用熱修復(fù)tinker)一般情況下是被關(guān)閉的。而組件化框架可以使模塊單獨(dú)編譯調(diào)試,可以有效地減少編譯的時(shí)間。
2、通過組件化可以更好的進(jìn)行并行開發(fā),因?yàn)槲覀兛梢詾槊恳粋€(gè)模塊進(jìn)行單獨(dú)的版本控制,甚至每一個(gè)模塊的負(fù)責(zé)人可以選擇自己的設(shè)計(jì)架構(gòu)而不影響其他模塊的開發(fā),與此同時(shí)組件化還可以避免模塊之間的交叉依賴,每一個(gè)模塊的開發(fā)人員可以對自己的模塊進(jìn)行獨(dú)立測試,獨(dú)立編譯和運(yùn)行,甚至可以實(shí)現(xiàn)單獨(dú)的部署。從而極大的提高了并行開發(fā)效率。

3.組件化的基本框架

3.1組件框架圖
3.2項(xiàng)目結(jié)構(gòu)圖

4.組件化框架的具體實(shí)現(xiàn)

4.1、基類庫的封裝

4.1基類庫圖

基類庫中主要包括開發(fā)常用的一些框架。
1、網(wǎng)絡(luò)請求(多任務(wù)下載和上傳,采用Retrofit+RxJava框架)
2、圖片加載(策略模式,Glide與Picasso之間可以切換)
3、通信機(jī)制(RxBus)
4、基類adapter的封裝(支持item動(dòng)畫、多布局item、下拉和加載更多、item點(diǎn)擊事件)
5、基類RecyclerView的封裝(支持原生風(fēng)格的下拉加載,item側(cè)滑等)
6、mvp框架
7、各組件的數(shù)據(jù)庫實(shí)體類
8、通用的工具類
9、自定義view(包括對話框,ToolBar布局,圓形圖片等view的自定義)
10、dagger的封裝(用于初始化全局的變量和網(wǎng)絡(luò)請求等配置)
等等

4.2組件模式和集成模式切換的實(shí)現(xiàn)

music組件下的build.gradle文件,其他組件類似。

//控制組件模式和集成模式
if (rootProject.ext.isAlone) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}
apply plugin: 'com.neenbedankt.android-apt'
android {
    compileSdkVersion rootProject.ext.android.compileSdkVersion
    buildToolsVersion rootProject.ext.android.buildToolsVersion
    defaultConfig {
        if (rootProject.ext.isAlone) {
     //   組件模式下設(shè)置applicationId
            applicationId "com.example.cootek.music"
        }
        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode rootProject.ext.android.versionCode
        versionName rootProject.ext.android.versionName
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        if (!rootProject.ext.isAlone) {
//   集成模式下Arouter的配置,用于組件間通信的實(shí)現(xiàn)
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [moduleName: project.getName()]
                }
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }
    sourceSets {
        main {
    //控制兩種模式下的資源和代碼配置情況
            if (rootProject.ext.isAlone) {
                manifest.srcFile 'src/main/module/AndroidManifest.xml'
                java.srcDirs = ['src/main/java', 'src/main/module/java']
                res.srcDirs = ['src/main/res', 'src/main/module/res']
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
            }
        }
    }
}
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
//   依賴基類庫
    compile project(':commonlibrary')
//用作顏色選擇器
    compile 'com.afollestad.material-dialogs:commons:0.9.1.0'
    apt rootProject.ext.dependencies.dagger2_compiler
    if (!rootProject.ext.isAlone) {
//  集成模式下需要編譯器生成路由通信的代碼
        apt rootProject.ext.dependencies.arouter_compiler
    }
    testCompile 'junit:junit:4.12'
}
集成模式

1、首先需要在config,gradle文件中設(shè)置isAlone=false

ext {
    isAlone = false;//false:作為Lib組件存在, true:作為application存在

2、然后Sync 下。
3、最后選擇app運(yùn)行即可。


運(yùn)行.png
組件模式

1、首先需要在config,gradle文件中設(shè)置isAlone=true

ext {
    isAlone = true;//false:作為Lib組件存在, true:作為application存在

2、然后Sync 下。
3、最后相應(yīng)的模塊(new、chat、live、music、app)進(jìn)行運(yùn)行即可。

4.3第三方開源庫和組件版本號的管理

config.gradle文件的配置情況

ext {
    isAlone = false;//false:作為集成模式存在, true:作為組件模式存在

//  各個(gè)組件版本號的統(tǒng)一管理
    android = [
            compileSdkVersion: 24,
            buildToolsVersion: "25.0.2",
            minSdkVersion    : 16,
            targetSdkVersion : 22,
            versionCode      : 1,
            versionName      : '1.0.0',
    ]



    libsVersion = [
            // 第三方庫版本號的管理
            supportLibraryVersion = "25.3.0",
            retrofitVersion = "2.1.0",
            glideVersion = "3.7.0",
            loggerVersion = "1.15",
//            eventbusVersion = "3.0.0",
            gsonVersion = "2.8.0",
            butterknife = "8.8.0",
            retrofit = "2.3.0",
            rxjava = "2.1.1",
            rxjava_android = "2.0.1",
            rxlifecycle = "2.1.0",
            rxlifecycle_components = "2.1.0",
            dagger_compiler = "2.11",
            dagger = "2.11",
            greenDao = "3.2.2",
            arouter_api = "1.2.2",
            arouter_compiler = "1.1.3",
            transformations = "2.0.2",
            rxjava_adapter = "2.3.0",
            gson_converter = "2.3.0",
            scalars_converter = "2.3.0",
            rxpermission = "0.9.4",
            eventbus="3.0.0",
            support_v4="25.4.0",
            okhttp3="3.8.1"
    ]

//  依賴庫管理
    dependencies = [
            appcompatV7               : "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion",
            design                    : "com.android.support:design:$rootProject.supportLibraryVersion",
            cardview                  : "com.android.support:cardview-v7:$rootProject.supportLibraryVersion",
            palette                   : "com.android.support:palette-v7:$rootProject.supportLibraryVersion",
            recycleview               : "com.android.support:recyclerview-v7:$rootProject.supportLibraryVersion",
            support_v4                : "com.android.support:support-v4:$rootProject.support_v4",
            annotations               : "com.android.support:support-annotations:$rootProject.supportLibraryVersion",
            eventBus                  : "org.greenrobot:eventbus:$rootProject.eventbus",
            glide                     : "com.github.bumptech.glide:glide:$rootProject.glideVersion",
            gson                      : "com.google.code.gson:gson:$rootProject.gsonVersion",
            logger                    : "com.orhanobut:logger:$rootProject.loggerVersion",
            butterknife               : "com.jakewharton:butterknife:$rootProject.butterknife",
            butterknife_compiler      : "com.jakewharton:butterknife-compiler:$rootProject.butterknife",
            retrofit                  : "com.squareup.retrofit2:retrofit:$rootProject.retrofit",
            okhttp3                   : "com.squareup.okhttp3:okhttp:$rootProject.retrofit",
            retrofit_adapter_rxjava2  : "com.squareup.retrofit2:adapter-rxjava2:$rootProject.rxjava_adapter",
            retrofit_converter_gson   : "com.squareup.retrofit2:converter-gson:$rootProject.gson_converter",
            retrofit_converter_scalars: "com.squareup.retrofit2:converter-scalars:$rootProject.scalars_converter",
            rxpermission              : "com.tbruyelle.rxpermissions2:rxpermissions:$rootProject.rxpermission@aar",
            rxjava2                   : "io.reactivex.rxjava2:rxjava:$rootProject.rxjava",
            rxjava2_android           : "io.reactivex.rxjava2:rxandroid:$rootProject.rxjava_android",
            rxlifecycle2              : "com.trello.rxlifecycle2:rxlifecycle:$rootProject.rxlifecycle",
            rxlifecycle2_components   : "com.trello.rxlifecycle2:rxlifecycle-components:$rootProject.rxlifecycle_components",
            dagger2_compiler          : "com.google.dagger:dagger-compiler:$rootProject.dagger_compiler",
            dagger2                   : "com.google.dagger:dagger:$rootProject.dagger",
            greenDao                  : "org.greenrobot:greendao:$rootProject.greenDao",
            transformations           : "jp.wasabeef:glide-transformations:$rootProject.transformations",
//路由通訊
            arouter_api               : "com.alibaba:arouter-api:$rootProject.arouter_api",
            arouter_compiler          : "com.alibaba:arouter-compiler:$rootProject.arouter_compiler"
    ]
}

4.4、組件間通信實(shí)現(xiàn)

組件間通信的實(shí)現(xiàn)是采用阿里開源的Arouter路由通信。
github地址:https://github.com/alibaba/ARouter
在App工程中,初始化組件通信數(shù)據(jù)

private List<MainItemBean> getDefaultData() {
        List<MainItemBean> result=new ArrayList<>();
        MainItemBean mainItemBean=new MainItemBean();
        mainItemBean.setName("校園");
        mainItemBean.setPath("/news/main");
        mainItemBean.setResId(R.mipmap.ic_launcher);
        MainItemBean music=new MainItemBean();
        music.setName("音樂");
        music.setResId(R.mipmap.ic_launcher);
        music.setPath("/music/main");
        MainItemBean live=new MainItemBean();
        live.setName("直播");
        live.setResId(R.mipmap.ic_launcher);
        live.setPath("/live/main");
        MainItemBean chat=new MainItemBean();
        chat.setName("聊天");
        chat.setPath("/chat/splash");
        chat.setResId(R.mipmap.ic_launcher);
        result.add(mainItemBean);
        result.add(music);
        result.add(live);
        result.add(chat);
        return result;
    }

然后在設(shè)置每個(gè)item的點(diǎn)擊事件時(shí),啟動(dòng)組件界面跳轉(zhuǎn)。

@Override
            public void onItemClick(int position, View view) {
                MainItemBean item=mainAdapter.getData(position);
                ARouter.getInstance().build(item.getPath()).navigation();
            }

每個(gè)組件入口界面的設(shè)置(比如直播Live組件,其它組件類似)

@Route(path = "/live/main")
public class MainActivity extends BaseActivity<List<CategoryLiveBean>, MainPresenter> implements View.OnClickListener {

5.組件合并時(shí)res資源和AndroidManifest配置的問題

我們通過判斷組件處于哪種模式來動(dòng)態(tài)設(shè)置項(xiàng)目res資源和Manifest、以及代碼的位置。以直播組件為例,其它組件類似。

直播組件框架

直播組件的build.gradle文件對代碼資源等位置的配置

sourceSets {
        main {
            if (rootProject.ext.isAlone) {
                manifest.srcFile 'src/main/module/AndroidManifest.xml'
                java.srcDirs = ['src/main/java', 'src/main/module/java']
                res.srcDirs = ['src/main/res', 'src/main/module/res']
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
            }
        }
    }

6.組件全局application的實(shí)現(xiàn)和數(shù)據(jù)的初始化

采用類似于Glide在Manifest初始化配置的方式來初始化各個(gè)組件的Application,以直播組件為例,其它類似。

在BaseApplication中,初始化ApplicationDelegate代理類

 @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        applicationDelegate = new ApplicationDelegate();
        applicationDelegate.attachBaseContext(base);
        MultiDex.install(this);
    }

ApplicationDelegate內(nèi)部是怎樣的呢?繼續(xù)看下去

public class ApplicationDelegate implements IAppLife {
    private List<IModuleConfig> list;
    private List<IAppLife> appLifes;
    private List<Application.ActivityLifecycleCallbacks> liferecycleCallbacks;


    public ApplicationDelegate() {
        appLifes = new ArrayList<>();
        liferecycleCallbacks = new ArrayList<>();
    }

    @Override
    public void attachBaseContext(Context base) {
//   初始化Manifest文件解析器,用于解析組件在自己的Manifest文件配置的Application
        ManifestParser manifestParser = new ManifestParser(base);
        list = manifestParser.parse();
//解析得到的組件Application列表之后,給每個(gè)組件Application注入
context,和Application的生命周期的回調(diào),用于實(shí)現(xiàn)application的同步
        if (list != null && list.size() > 0) {
            for (IModuleConfig configModule :
                    list) {
                configModule.injectAppLifecycle(base, appLifes);
                configModule.injectActivityLifecycle(base, liferecycleCallbacks);
            }
        }
        if (appLifes != null && appLifes.size() > 0) {
            for (IAppLife life :
                    appLifes) {
                life.attachBaseContext(base);
            }
        }
    }

    @Override
    public void onCreate(Application application) {
//  相應(yīng)調(diào)用組件Application代理類的onCreate方法
        if (appLifes != null && appLifes.size() > 0) {
            for (IAppLife life :
                    appLifes) {
                life.onCreate(application);
            }
        }
        if (liferecycleCallbacks != null && liferecycleCallbacks.size() > 0) {
            for (Application.ActivityLifecycleCallbacks life :
                    liferecycleCallbacks) {
                application.registerActivityLifecycleCallbacks(life);
            }
        }
    }

    @Override
    public void onTerminate(Application application) {
//  相應(yīng)調(diào)用組件Application代理類的onTerminate方法
        if (appLifes != null && appLifes.size() > 0) {
            for (IAppLife life :
                    appLifes) {
                life.onTerminate(application);
            }
        }
        if (liferecycleCallbacks != null && liferecycleCallbacks.size() > 0) {
            for (Application.ActivityLifecycleCallbacks life :
                    liferecycleCallbacks) {
                application.unregisterActivityLifecycleCallbacks(life);
            }
        }
    }
}

組件Manifest中application的全局配置

<meta-data
            android:name="com.example.live.LiveApplication"
            android:value="IModuleConfig" />

ManifestParser會(huì)對其中value為IModuleConfig的meta-data進(jìn)行解析,并通過反射生成實(shí)例。

public final class ManifestParser {
    private static final String MODULE_VALUE = "IModuleConfig";
    private final Context context;
    public ManifestParser(Context context) {
        this.context = context;
    }
    public List<IModuleConfig> parse() {
        List<IModuleConfig> modules = new ArrayList<>();
        try {
            ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(
                    context.getPackageName(), PackageManager.GET_META_DATA);
            if (appInfo.metaData != null) {
                for (String key : appInfo.metaData.keySet()) {
//會(huì)對其中value為IModuleConfig的meta-data進(jìn)行解析,并通過反射生成實(shí)例
                    if (MODULE_VALUE.equals(appInfo.metaData.get(key))) {
                        modules.add(parseModule(key));
                    }
                }
            }
        } catch (PackageManager.NameNotFoundException e) {
            throw new RuntimeException("Unable to find metadata to parse IModuleConfig", e);
        }
        return modules;
    }

//通過類名生成實(shí)例
    private static IModuleConfig parseModule(String className) {
        Class<?> clazz;
        try {
            clazz = Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException("Unable to find IModuleConfig implementation", e);
        }

        Object module;
        try {
            module = clazz.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException("Unable to instantiate IModuleConfig implementation for " + clazz, e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Unable to instantiate IModuleConfig implementation for " + clazz, e);
        }

        if (!(module instanceof IModuleConfig)) {
            throw new RuntimeException("Expected instanceof IModuleConfig, but found: " + module);
        }
        return (IModuleConfig) module;
    }

這樣通過以上步驟就可以在Manifest文件中配置自己組件的Application,用于初始化組件內(nèi)的數(shù)據(jù),比如在直播組件中初始化Dagger的全局配置

public class LiveApplication implements IModuleConfig,IAppLife {
    private static MainComponent mainComponent;
    @Override
    public void injectAppLifecycle(Context context, List<IAppLife> iAppLifes) {
//  這里需要把本引用添加到Application的生命周期的回調(diào)中,以便實(shí)現(xiàn)回調(diào)
        iAppLifes.add(this);
    }

    @Override
    public void injectActivityLifecycle(Context context, List<Application.ActivityLifecycleCallbacks> lifecycleCallbackses) {
    }

    @Override
    public void attachBaseContext(Context base) {
    }

    @Override
    public void onCreate(Application application) {
//     在onCreate方法中對Dagger進(jìn)行初始化
            mainComponent= DaggerMainComponent.builder().mainModule(new MainModule()).appComponent(BaseApplication.getAppComponent()).build();
    }

    @Override
    public void onTerminate(Application application) {
        if (mainComponent != null) {
            mainComponent = null;
        }
    }

    public static MainComponent getMainComponent() {
        return mainComponent;
    }
}

7.組件內(nèi)網(wǎng)絡(luò)請求和攔截器的實(shí)現(xiàn)

由于每個(gè)組件的BaseUrl和網(wǎng)絡(luò)配置等可能不一樣,所以每個(gè)組件可以在自己配置的dagger中的 MainConponent實(shí)現(xiàn)自己的網(wǎng)絡(luò)請求和攔截器。
以直播組件為例,其它類似。
MainComponent

@PerApplication
@Component(dependencies = AppComponent.class, modules = MainModule.class)
public interface MainComponent {
    public DaoSession getDaoSession();

    public MainRepositoryManager getMainRepositoryManager();
}

MainModule代碼

@Module
public class MainModule {
    @Provides
    @PerApplication
    public MainRepositoryManager provideRepositoryManager(@Named("live") Retrofit retrofit, DaoSession daoSession) {
        return new MainRepositoryManager(retrofit, daoSession);
    }
    @Provides
    @Named("live")
    @PerApplication
    public Retrofit provideRetrofit(@Named("live") OkHttpClient okHttpClient,@Nullable Gson gson){
        Retrofit.Builder builder=new Retrofit.Builder().baseUrl(LiveUtil.BASE_URL).addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create(gson)).client(okHttpClient);
        return builder.build();
    }
    @Provides
    @Named("live")
    @PerApplication
    public OkHttpClient provideOkHttpClient(@Named("live")LiveInterceptor interceptor){
        OkHttpClient.Builder builder=new OkHttpClient.Builder();
        builder.connectTimeout(10, TimeUnit.SECONDS).readTimeout(10,TimeUnit.SECONDS);
        builder.addInterceptor(interceptor);
        return builder.build();
    }
    @Provides
    @Named("live")
    @PerApplication
    public LiveInterceptor provideNewsInterceptor(){
        return new LiveInterceptor();
    }
}

8.組件化實(shí)現(xiàn)的技術(shù)難點(diǎn)

8.1.greendao數(shù)據(jù)庫的實(shí)現(xiàn)

greendao數(shù)據(jù)庫初始化代碼,在基類庫的NetClientModule.java中

public DaoSession provideDaoSession(Application application) {
        DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(application, "common_library_db", null);
        Database database = devOpenHelper.getWritableDb();
        DaoMaster master = new DaoMaster(database);
        return master.newSession();
    }

其中的DaoMaster是通過APT生成的,由于DaoMaster給全局的組件使用,所以只能將greendao 數(shù)據(jù)庫放在基類庫中,并且各個(gè)組件的實(shí)體類bean的創(chuàng)建也只能在基類庫中進(jìn)行,以分包命名進(jìn)行區(qū)分,如下圖。因?yàn)槿绻诮M件內(nèi)創(chuàng)建bean 會(huì)重新生成另一個(gè)副本DaoMaster并且不能操控其他組件的數(shù)據(jù)庫實(shí)體,有很大的局限性。

基類庫組件實(shí)體分包圖

8.2.資源命名沖突

官方說法是在每個(gè)module的build.gradle文件中配置資源文件名前綴
這種方法缺點(diǎn)就是,所有的資源名必須要以指定的字符串(moudle_prefix)做前綴,否則會(huì)異常報(bào)錯(cuò),而且這方法只限定xml里面的資源,對圖片資源并不起作用,所以圖片資源仍然需要手動(dòng)去修改資源名。
所以不是很推薦使用這種方法來解決資源名沖突。所以只能自己注意點(diǎn),在創(chuàng)建資源的時(shí)候,盡量不讓其重復(fù)。

resourcePrefix  "moudle_prefix"

8.3.butterKnife不能使用的原因

雖然Butterknife支持在lib中使用,但是條件是用 R2 代替 R ,在組件模式和集成模式的切換中,R2<->R之間的切換是無法完成轉(zhuǎn)換的,切換一次要改動(dòng)全身,是非常麻煩的!所以不推薦在組件化中使用Butterknife。

8.4.library重復(fù)依賴問題

1、可能大家會(huì)認(rèn)為,每個(gè)組件都依賴基類庫,基類庫library次不是重復(fù)依賴了?其實(shí)并不會(huì)存在這樣的問題,因?yàn)樵跇?gòu)建APP的過程中Gradle會(huì)自動(dòng)將重復(fù)的arr包排除,也就不會(huì)存在重復(fù)依賴基類庫的情況。
2、但是第三方開源庫依賴的包可能會(huì)與我們自己引用的包重復(fù),所以我們需要將多余的包給排除出去。
基類庫(CommonLibrary)中build.gradle

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile(rootProject.ext.dependencies.appcompatV7) {
        exclude module: "support-v4"
        exclude module: "support-annotations"
    }
    compile rootProject.ext.dependencies.recycleview
    compile rootProject.ext.dependencies.design

    compile(rootProject.ext.dependencies.support_v4) {
       exclude module: "support-annotations"
    }
    compile rootProject.ext.dependencies.annotations
    compile(rootProject.ext.dependencies.butterknife) {
        exclude module: 'support-annotations'
    }
    compile rootProject.ext.dependencies.rxjava2
    compile(rootProject.ext.dependencies.rxjava2_android) {
        exclude module: "rxjava"
    }
    compile(rootProject.ext.dependencies.rxlifecycle2) {
        exclude module: 'rxjava'
        exclude module: 'jsr305'
    }
    compile(rootProject.ext.dependencies.rxlifecycle2_components) {
        exclude module: 'support-v4'
        exclude module: 'appcompat-v7'
        exclude module: 'support-annotations'
        exclude module: 'rxjava'
        exclude module: 'rxandroid'
        exclude module: 'rxlifecycle'
    }
    compile(rootProject.ext.dependencies.retrofit) {
        exclude module: 'okhttp'
        exclude module: 'okio'
    }
    compile(rootProject.ext.dependencies.retrofit_converter_gson) {
        exclude module: 'gson'
        exclude module: 'okhttp'
        exclude module: 'okio'
        exclude module: 'retrofit'
    }
    compile(rootProject.ext.dependencies.retrofit_adapter_rxjava2) {
        exclude module: 'rxjava'
        exclude module: 'okhttp'
        exclude module: 'retrofit'
        exclude module: 'okio'
    }
    compile rootProject.ext.dependencies.greenDao
    compile rootProject.ext.dependencies.okhttp3
    compile rootProject.ext.dependencies.gson
    compile rootProject.ext.dependencies.glide
    compile rootProject.ext.dependencies.eventBus
    compile rootProject.ext.dependencies.dagger2
    compile(rootProject.ext.dependencies.rxpermission) {
        exclude module: 'rxjava'
    }
    compile rootProject.ext.dependencies.retrofit_converter_scalars
    annotationProcessor rootProject.ext.dependencies.dagger2_compiler
    annotationProcessor rootProject.ext.dependencies.butterknife_compiler
    compile rootProject.ext.dependencies.butterknife
    compile rootProject.ext.dependencies.transformations
    compile rootProject.ext.dependencies.arouter_api
}

9.組件化與熱修復(fù)的無縫連接

本開源項(xiàng)目是基于騰訊的bugly平臺(tái),用于監(jiān)控異常信息、熱修復(fù)和應(yīng)用升級。
具體實(shí)現(xiàn):
1、在工程的根目錄build.gradle配置

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "com.tencent.bugly:tinker-support:1.0.8"
    }
}

然后在App 的build.gradle進(jìn)行以下配置

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    if (!rootProject.ext.isAlone) {
        compile project(':chat')
        compile project(':music')
        compile project(':news')
        compile project(':live')
        apt rootProject.ext.dependencies.arouter_compiler
    } else {
        compile project(':commonlibrary')
    }
    testCompile 'junit:junit:4.12'
//  依賴bugly相關(guān)SDK
    compile 'com.tencent.bugly:crashreport_upgrade:1.3.1'
    compile 'com.tencent.bugly:nativecrashreport:latest.release'
}
apply from: 'tinker-support.gradle'

然后依賴其中的插件腳本

apply from: 'tinker-support.gradle'

其中的tinker-support.gradle文件如下:

apply plugin: 'com.tencent.bugly.tinker-support'
def bakPath = file("${buildDir}/bakApk/")
/**
 * 此處填寫每次構(gòu)建生成的基準(zhǔn)包目錄
 */
def baseApkDir = "app-0831-17-50-44"
/**
 * 對于插件各參數(shù)的詳細(xì)解析請參考
 */
tinkerSupport {
    // 開啟tinker-support插件,默認(rèn)值true
    enable = true
    // 自動(dòng)生成tinkerId, 你無須關(guān)注tinkerId,默認(rèn)為false
    autoGenerateTinkerId = true
    // 指定歸檔目錄,默認(rèn)值當(dāng)前module的子目錄tinker
    autoBackupApkDir = "${bakPath}"
    // 是否啟用覆蓋tinkerPatch配置功能,默認(rèn)值false
    // 開啟后tinkerPatch配置不生效,即無需添加tinkerPatch
    overrideTinkerPatchConfiguration = true
    // 編譯補(bǔ)丁包時(shí),必需指定基線版本的apk,默認(rèn)值為空
    // 如果為空,則表示不是進(jìn)行補(bǔ)丁包的編譯
    // @{link tinkerPatch.oldApk }
    baseApk =  "${bakPath}/${baseApkDir}/app-release.apk"
    // 對應(yīng)tinker插件applyMapping
    baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-release-mapping.txt"
    // 對應(yīng)tinker插件applyResourceMapping
    baseApkResourceMapping = "${bakPath}/${baseApkDir}/app-release-R.txt"
    // 構(gòu)建基準(zhǔn)包跟補(bǔ)丁包都要修改tinkerId,主要用于區(qū)分
      tinkerId = "1.0.5-base_patch"
    // 打多渠道補(bǔ)丁時(shí)指定目錄
    // buildAllFlavorsDir = "${bakPath}/${baseApkDir}"
    // 是否使用加固模式,默認(rèn)為false
    // isProtectedApp = true
    // 是否采用反射Application的方式集成,無須改造Application
    enableProxyApplication = true
}
/**
 * 一般來說,我們無需對下面的參數(shù)做任何的修改
 * 對于各參數(shù)的詳細(xì)介紹請參考:
 * https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
 */
tinkerPatch {
    tinkerEnable = true
    ignoreWarning = false
    useSign = true
    dex {
        dexMode = "jar"
        pattern = ["classes*.dex"]
        loader = []
    }
    lib {
        pattern = ["lib/*/*.so"]
    }
    res {
        pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
        ignoreChange = []
        largeModSize = 100
    }
    packageConfig {
    }
    sevenZip {
        zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
//        path = "/usr/local/bin/7za"
    }
    buildConfig {
        keepDexApply = false
//      tinkerId = "base-2.0.1"
    }
}

然后需要在Manifest配置文件配置如下

<activity
            android:name="com.tencent.bugly.beta.ui.BetaActivity"   
      android:configChanges="keyboardHidden|orientation|screenSize|locale"
            android:theme="@android:style/Theme.Translucent" />
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${applicationId}.fileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"/>
        </provider>

最后在Application中初始化bugly

public class App extends BaseApplication {
    @Override
    public void onCreate() {
        super.onCreate();
        setStrictMode();
        // 設(shè)置是否開啟熱更新能力,默認(rèn)為true
        Beta.enableHotfix = true;
        // 設(shè)置是否自動(dòng)下載補(bǔ)丁
        Beta.canAutoDownloadPatch = true;
        // 設(shè)置是否提示用戶重啟
        Beta.canNotifyUserRestart = true;
        // 設(shè)置是否自動(dòng)合成補(bǔ)丁
        Beta.canAutoPatch = true;

        /**
         *  全量升級狀態(tài)回調(diào)
         */
        Beta.upgradeStateListener = new UpgradeStateListener() {
            @Override
            public void onUpgradeFailed(boolean b) {
            }

            @Override
            public void onUpgradeSuccess(boolean b) {
            }

            @Override
            public void onUpgradeNoVersion(boolean b) {
                Toast.makeText(getApplicationContext(), "最新版本", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onUpgrading(boolean b) {
                Toast.makeText(getApplicationContext(), "onUpgrading", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onDownloadCompleted(boolean b) {

            }
        };
        /**
         * 補(bǔ)丁回調(diào)接口,可以監(jiān)聽補(bǔ)丁接收、下載、合成的回調(diào)
         */
        Beta.betaPatchListener = new BetaPatchListener() {
            @Override
            public void onPatchReceived(String patchFileUrl) {
                Toast.makeText(getApplicationContext(), patchFileUrl, Toast.LENGTH_SHORT).show();
            }
            @Override
            public void onDownloadReceived(long savedLength, long totalLength) {
                Toast.makeText(getApplicationContext(), String.format(Locale.getDefault(),
                        "%s %d%%",
                        Beta.strNotificationDownloading,
                        (int) (totalLength == 0 ? 0 : savedLength * 100 / totalLength)), Toast.LENGTH_SHORT).show();
            }
            @Override
            public void onDownloadSuccess(String patchFilePath) {
                Toast.makeText(getApplicationContext(), patchFilePath, Toast.LENGTH_SHORT).show();
//                Beta.applyDownloadedPatch();
            }
            @Override
            public void onDownloadFailure(String msg) {
                Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
            }
            @Override
            public void onApplySuccess(String msg) {
                Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
            }
            @Override
            public void onApplyFailure(String msg) {
                Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onPatchRollback() {
                Toast.makeText(getApplicationContext(), "onPatchRollback", Toast.LENGTH_SHORT).show();
            }
        };
        long start = System.currentTimeMillis();
        // 這里實(shí)現(xiàn)SDK初始化,appId替換成你的在Bugly平臺(tái)申請的appId,調(diào)試時(shí)將第三個(gè)參數(shù)設(shè)置為true
        Bugly.init(this, "2e5309db50", true);
        long end = System.currentTimeMillis();
    }
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        // you must install multiDex whatever tinker is installed!
        MultiDex.install(base);
        // 安裝tinker
        Beta.installTinker();
    }
    @TargetApi(9)
    protected void setStrictMode() {
        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().permitAll().build());
        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
    }
}

10.結(jié)束語

該組件框架是自己在暑假實(shí)習(xí)期間做的,由于實(shí)習(xí)公司的項(xiàng)目過于龐大和復(fù)雜,每次編譯都需要花費(fèi)10幾分鐘,心都碎了,所以才想嘗試下組件化框架,摸索了很長時(shí)間,最后還是做出來了,大概花費(fèi)2個(gè)多月的時(shí)間,由于最近項(xiàng)目上比較忙,所以沒什么時(shí)間來完善,界面有點(diǎn)簡陋,但邏輯基本實(shí)現(xiàn)了。歡迎fork and star。一起探討技術(shù)。
github上地址: https://github.com/HelloChenJinJun/NewFastFrame

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,555評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,516評論 2 379

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,734評論 25 708
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,826評論 18 139
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,173評論 4 61
  • (一) 亙古青山一望收, 蔡侯文同古洋州。 秋風(fēng)號角自奮起, 大江東去笑孔丘。 (二) 漢江水漲葉正黃, 細(xì)雨秉燭...
    W和W閱讀 283評論 0 4
  • 邊際點(diǎn)效應(yīng),利用的還是人們內(nèi)心深處的一種渴望。就拿生活中最簡單的挑食來講可能更通俗易懂一些。現(xiàn)在的人動(dòng)不動(dòng),不想吃...
    蘇格拉底閱讀 187評論 3 5