1. 程式人生 > >終極元件化框架專案方案詳解

終極元件化框架專案方案詳解

前言

1.什麼是元件化?

專案發展到一定階段時,隨著需求的增加以及頻繁地變更,專案會越來越大,程式碼變得越來越臃腫,耦合會越來越多,開發效率也會降低,這個時候我們就需要對舊專案進行重構即模組的拆分,官方的說法就是元件化。

2.為什麼需要元件化和元件化帶來的好處?

1、 現在Android專案中程式碼量達到一定程度,編譯將是一件非常痛苦的事情,一般都需要變異5到6分鐘。Android studio推出instant run由於各種缺陷和限制條件(比如採用熱修復tinker)一般情況下是被關閉的。而元件化框架可以使模組單獨編譯除錯,可以有效地減少編譯的時間。
2、通過元件化可以更好的進行並行開發,因為我們可以為每一個模組進行單獨的版本控制,甚至每一個模組的負責人可以選擇自己的設計架構而不影響其他模組的開發,與此同時元件化還可以避免模組之間的交叉依賴,每一個模組的開發人員可以對自己的模組進行獨立測試,獨立編譯和執行,甚至可以實現單獨的部署。從而極大的提高了並行開發效率。

3.元件化的基本框架

3.1元件框架圖3.1元件框架圖 3.2專案結構圖3.2專案結構圖

4.元件化框架的具體實現

4.1、基類庫的封裝

4.1基類庫圖4.1基類庫圖
基類庫中主要包括開發常用的一些框架。
1、網路請求(多工下載和上傳,採用Retrofit+RxJava框架)
2、圖片載入(策略模式,Glide與Picasso之間可以切換)
3、通訊機制(RxBus)
4、基類adapter的封裝(支援item動畫、多佈局item、下拉和載入更多、item點選事件)
5、基類RecyclerView的封裝(支援原生風格的下拉載入,item側滑等)
6、mvp框架
7、各元件的資料庫實體類
8、通用的工具類
9、自定義view(包括對話方塊,ToolBar佈局,圓形圖片等view的自定義)
10、dagger的封裝(用於初始化全域性的變數和網路請求等配置)
等等

4.2元件模式和整合模式切換的實現

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) { // 元件模式下設定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的配置,用於元件間通訊的實現 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檔案中設定isAlone=false

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

2、然後Sync 下。
3、最後選擇app執行即可。

執行.png執行.png

元件模式

1、首先需要在config,gradle檔案中設定isAlone=true

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

2、然後Sync 下。
3、最後相應的模組(new、chat、live、music、app)進行執行即可。

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

config.gradle檔案的配置情況

ext {
    isAlone = false;//false:作為整合模式存在, true:作為元件模式存在

//  各個元件版本號的統一管理
    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[email protected]",
            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、元件間通訊實現

元件間通訊的實現是採用阿里開源的Arouter路由通訊。
github地址:github.com/alibaba/ARo…
在App工程中,初始化元件通訊資料

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;
    }

然後在設定每個item的點選事件時,啟動元件介面跳轉。

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

每個元件入口介面的設定(比如直播Live元件,其它元件類似)

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

5.元件合併時res資源和AndroidManifest配置的問題

我們通過判斷元件處於哪種模式來動態設定專案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的實現和資料的初始化

採用類似於Glide在Manifest初始化配置的方式來初始化各個元件的Application,以直播元件為例,其它類似。

在BaseApplication中,初始化ApplicationDelegate代理類

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

ApplicationDelegate內部是怎樣的呢?繼續看下去

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列表之後,給每個元件Application注入
context,和Application的生命週期的回撥,用於實現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) {
//  相應呼叫元件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) {
//  相應呼叫元件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會對其中value為IModuleConfig的meta-data進行解析,並通過反射生成例項。

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()) {
//會對其中value為IModuleConfig的meta-data進行解析,並通過反射生成例項
                    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;
    }

//通過類名生成例項
    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,用於初始化元件內的資料,比如在直播元件中初始化Dagger的全域性配置

public class LiveApplication implements IModuleConfig,IAppLife {
    private static MainComponent mainComponent;
    @Override
    public void injectAppLifecycle(Context context, List<IAppLife> iAppLifes) {
//  這裡需要把本引用新增到Application的生命週期的回撥中,以便實現回撥
        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進行初始化
            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.元件內網路請求和攔截器的實現

由於每個元件的BaseUrl和網路配置等可能不一樣,所以每個元件可以在自己配置的dagger中的 MainConponent實現自己的網路請求和攔截器。
以直播元件為例,其它類似。
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.元件化實現的技術難點

8.1.greendao資料庫的實現

greendao資料庫初始化程式碼,在基類庫的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 資料庫放在基類庫中,並且各個元件的實體類bean的建立也只能在基類庫中進行,以分包命名進行區分,如下圖。因為如果在元件內建立bean 會重新生成另一個副本DaoMaster並且不能操控其他元件的資料庫實體,有很大的侷限性。

基類庫元件實體分包圖基類庫元件實體分包圖

8.2.資源命名衝突

官方說法是在每個module的build.gradle檔案中配置資原始檔名字首
這種方法缺點就是,所有的資源名必須要以指定的字串(moudle_prefix)做字首,否則會異常報錯,而且這方法只限定xml裡面的資源,對圖片資源並不起作用,所以圖片資源仍然需要手動去修改資源名。
所以不是很推薦使用這種方法來解決資源名衝突。所以只能自己注意點,在建立資源的時候,儘量不讓其重複。

resourcePrefix  "moudle_prefix"

8.3.butterKnife不能使用的原因

雖然Butterknife支援在lib中使用,但是條件是用 R2 代替 R ,在元件模式和整合模式的切換中,R2<->R之間的切換是無法完成轉換的,切換一次要改動全身,是非常麻煩的!所以不推薦在元件化中使用Butterknife。

8.4.library重複依賴問題

1、可能大家會認為,每個元件都依賴基類庫,基類庫library次不是重複依賴了?其實並不會存在這樣的問題,因為在構建APP的過程中Gradle會自動將重複的arr包排除,也就不會存在重複依賴基類庫的情況。
2、但是第三方開源庫依賴的包可能會與我們自己引用的包重複,所以我們需要將多餘的包給排除出去。
基類庫(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.元件化與熱修復的無縫連線

本開源專案是基於騰訊的bugly平臺,用於監控異常資訊、熱修復和應用升級。
具體實現:
1、在工程的根目錄build.gradle配置

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

然後在App 的build.gradle進行以下配置

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相關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/")
/**
 * 此處填寫每次構建生成的基準包目錄
 */
def baseApkDir = "app-0831-17-50-44"
/**
 * 對於外掛各引數的詳細解析請參考
 */
tinkerSupport {
    // 開啟tinker-support外掛,預設值true
    enable = true
    // 自動生成tinkerId, 你無須關注tinkerId,預設為false
    autoGenerateTinkerId = true
    // 指定歸檔目錄,預設值當前module的子目錄tinker
    autoBackupApkDir = "${bakPath}"
    // 是否啟用覆蓋tinkerPatch配置功能,預設值false
    // 開啟後tinkerPatch配置不生效,即無需新增tinkerPatch
    overrideTinkerPatchConfiguration = true
    // 編譯補丁包時,必需指定基線版本的apk,預設值為空
    // 如果為空,則表示不是進行補丁包的編譯
    // @{link tinkerPatch.oldApk }
    baseApk =  "${bakPath}/${baseApkDir}/app-release.apk"
    // 對應tinker外掛applyMapping
    baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-release-mapping.txt"
    // 對應tinker外掛applyResourceMapping
    baseApkResourceMapping = "${bakPath}/${baseApkDir}/app-release-R.txt"
    // 構建基準包跟補丁包都要修改tinkerId,主要用於區分
      tinkerId = "1.0.5-base_patch"
    // 打多渠道補丁時指定目錄
    // buildAllFlavorsDir = "${bakPath}/${baseApkDir}"
    // 是否使用加固模式,預設為false
    // isProtectedApp = true
    // 是否採用反射Application的方式整合,無須改造Application
    enableProxyApplication = true
}
/**
 * 一般來說,我們無需對下面的引數做任何的修改
 * 對於各引數的詳細介紹請參考:
 * 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();
        // 設定是否開啟熱更新能力,預設為true
        Beta.enableHotfix = true;
        // 設定是否自動下載補丁
        Beta.canAutoDownloadPatch = true;
        // 設定是否提示使用者重啟
        Beta.canNotifyUserRestart = true;
        // 設定是否自動合成補丁
        Beta.canAutoPatch = true;

        /**
         *  全量升級狀態回撥
         */
        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) {

            }
        };
        /**
         * 補丁回撥介面,可以監聽補丁接收、下載、合成的回撥
         */
        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();
        // 這裡實現SDK初始化,appId替換成你的在Bugly平臺申請的appId,除錯時將第三個引數設定為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.結束語

該元件框架是自己在暑假實習期間做的,由於實習公司的專案過於龐大和複雜,每次編譯都需要花費10幾分鐘,心都碎了,所以才想嘗試下元件化框架,摸索了很長時間,最後還是做出來了,大概花費2個多月的時間,由於最近專案上比較忙,所以沒什麼時間來完善,介面有點簡陋,但邏輯基本實現了。歡迎fork and star。
有對元件化框架興趣的同學可以加本人QQ1981367757,一起探討技術。
github上地址: github.com/HelloChenJi…

相關推薦

終極元件框架專案方案:一個極具參考的元件專案

熱文導讀 | 點選標題閱讀作者:啊哈啊哈哈https://juejin.im/user/57a2

終極元件框架專案方案

前言 1.什麼是元件化? 專案發展到一定階段時,隨著需求的增加以及頻繁地變更,專案會越來越大,程式碼變得越來越臃腫,耦合會越來越多,開發效率也會降低,這個時候我們就需要對舊專案進行重構即模組的拆分,官方的說法就是元件化。 2.為什麼需要元件化和元件化帶來的好處? 1、 現在Android專案中程式碼量達到

Android元件框架專案

簡介 什麼是元件化? 專案發展到一定階段時,隨著需求的增加以及頻繁地變更,專案會越來越大,程式碼變得越來越臃腫,耦合會越來越多,開發效率也會降低,這個時候我們就需要對舊專案進行重構即模組的拆分,官方的說法就是元件化。 元件化帶來的好處 那麼,採用元件化能帶來什麼好處呢?主要有以下兩點: 1、

前端模塊開發解決方案

暴露 可見 返回 異步加載 def 今天 htm 硬盤 業務邏輯 一、模塊化開發方案 前端發展到今天,已經有不少模塊化的方案,比如 CommonJS(常用在服務器端,同步的,如nodejs) AMD(常用在瀏覽器端,異步的,如requirejs)(Asynchronous

元件框架專案

元件化框架專案   https://github.com/HelloChenJinJun/NewFastFrame 簡介 該專案目前整合現在主流的開發框架和技術,包括okhttp3、rxjava2、retrofit2、glide、greendao3.0、dagger2、mv

安卓專案實戰之強大的網路請求框架okGo使用(六):擴充套件專案okServer,更強大的下載上傳功能,支援斷點和多工管理

OkGo與OkDownload的區別就是,OkGo只是簡單的做一個下載功能,不具備斷點下載,暫停等操作,但是這在很多時候已經能滿足需要了。 而有些app需要有一個下載列表的功能,就像迅雷下載一樣,每個下載任務可以暫停,可以繼續,可以重新下載,可以有下載優先順序,這時候OkDownload就有

安卓專案實戰之強大的網路請求框架okGo使用(五):擴充套件專案okRx,完美結合RxJava

前言 在第一篇講解okGo框架新增依賴支援時,還記得我們額外新增的兩個依賴嗎,一個okRx和一個okServer,這兩個均是基於okGo框架的擴充套件專案,其中okRx可以使請求結合RxJava一起使用,而okServer則提供了強大的下載上傳功能,如斷點支援,多工管理等,本篇我們主要講

安卓專案實戰之強大的網路請求框架okGo使用(四):Cookie的管理

Cookie概念相關 具體來說cookie機制採用的是在客戶端保持狀態的方案,而session機制採用的是在伺服器端保持狀態的方案。同時我們也看到,由於採用伺服器端保持狀態的方案在客戶端也需要儲存一個標識,所以session機制是需要藉助於cookie機制來達到儲存標識的目的,所謂ses

安卓專案實戰之強大的網路請求框架okGo使用(三):快取的使用

相關實體類必須實現序列化介面 使用快取前,必須讓涉及到快取javaBean物件實現Serializable介面,否者會報NotSerializableException。因為快取的原理是將物件序列化後直接寫入資料庫中,如果不實現Serializable介面,會導致物件無法序列化,進而無法

安卓專案實戰之強大的網路請求框架okGo使用(二):深入理解Callback之自定義JsonCallback

前言 JSON是一種取代XML的資料結構,和xml相比,它更小巧但描述能力卻不差,由於它的小巧所以網路傳輸資料將減少更多流量從而加快了傳輸速度,目前客戶端伺服器返回的資料大多都是基於這種格式的,相應的我們瞭解的關於json的解析工具主要有兩個:Gson(Google官方出的)和fas

安卓專案實戰之強大的網路請求框架okGo使用(一):實現get,post基本網路請求,下載上傳進度監聽以及對Callback自定義的深入理解

1.新增依賴 //必須使用 compile 'com.lzy.net:okgo:3.0.4' //以下三個選擇新增,okrx和okrx2不能同時使用,一般選擇新增最新的rx2支援即可 compile 'com.lzy.net:okrx:1.0.2' compile 'com.lzy

mac下IDEA配置Spring框架+mybatis+maven配置管理的web專案過程

下面詳細講一下mac下怎麼利用IDEA來搭建一個利用spring框架+maven配置管理的web專案 1.再IDEA裡新建一個maven project,填入建立專案所必要的資訊 2.我們要利用spring框架或mybatis就得把與其相關的內容引入進專案啊,利用mav

Scrapy 爬蟲框架入門案例

tin mon setting 爬蟲框架 finished perror project 原因 create 歡迎大家關註騰訊雲技術社區-博客園官方主頁,我們將持續在博客園為大家推薦技術精品文章哦~ 作者:崔慶才 Scrapy入門 本篇會通過介紹一

接口自動化測試方案

system earch ply 找我 發現 safari todo 文件中 timestamp 前言 去年,我們進行了項目的拆分,拆分後的各個子系統也都逐步的改成了通過接口進行數據的交換,接口測試也被提上日程。經過一段時間的探索,接口自動化測試方案越來越完善,今天給大

傳美雲商系統軟件方案

ext mage 系統 做到 function ice 一個 集成 現在 傳美雲商系統是通過頤和果園讓公眾所知道的,頤和果園是做水果生鮮食品市場的,運用傳美雲商這個軟件為什麽能打通互聯網市場?究其主要原因是因新零售正在發生日新月異的變化! 任何新零售都需要數據的支撐。辦公室

22.Linux-塊設備驅動之框架詳細分析()

磁盤設備 回調 隊列 rst 學習 更新 表示 索引 函數實現 1.之前我們學的都是字符設備驅動,先來回憶一下 字符設備驅動: 當我們的應用層讀寫(read()/write())字符設備驅動時,是按字節/字符來讀寫數據的,期間沒有任何緩存區,因為數據量小,不能隨機讀取數據,

企業級代碼上線方案

代碼上線 代碼備份 軟鏈接 中小型企業代碼上線準則 (1)上線說明****對於重要的升級上線來說先有運維人員備份所有重要數據,然後經過開發人員測試和內部測試成功後直接上傳的站點目錄,出現問題後采用歷史代碼回滾策略。而對普通升級來說,先備份必要數據,然後代碼在開發人員和內外網測試成功後可直接上線。(

python調度框架APScheduler使用

body format nbsp start == value latest Coding iso # coding=utf-8 2 """ 3 Demonstrates how to use the background scheduler to schedule

Struts2框架執行流程

Struts2框架執行流程詳解1. Struts2源碼導入對於struts2框架它的源代碼我們主要使用三部分 struts2核心部分源代碼 org.apache.struts2xxsrc\core\src\main\java struts2的xwork核心部分源代碼src\xwork-core\src\m

電商購物直播app開發解決方案

分享 進入 發的 購物平臺 發出 阿裏巴巴 通過 出了 內嵌 最近有很多小夥伴咨詢電商直播app開發,在傳統的秀場直播競爭力逐漸下降的今天,“直播+”爆發出了無窮的“小宇宙”。在眾多“直播+”解決方案中,“直播+電商”是目前比較完善的解決方案,像阿裏巴巴旗下的淘寶直播,以及