[Android Pro] 終極組件化框架項目方案詳解

cp from :?https://blog.csdn.net/pochenpiji159/article/details/78660844

前言

本文所講的組件化案例是基于自己開源的組件化框架項目
github上地址github.com/HelloChenJi…
其中即時通訊(Chat)模塊是單獨的項目
github上地址github.com/HelloChenJi…

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.compileSdkVersionbuildToolsVersion rootProject.ext.android.buildToolsVersiondefaultConfig {if (rootProject.ext.isAlone) {//   組件模式下設置applicationIdapplicationId "com.example.cootek.music"}minSdkVersion rootProject.ext.android.minSdkVersiontargetSdkVersion rootProject.ext.android.targetSdkVersionversionCode rootProject.ext.android.versionCodeversionName rootProject.ext.android.versionNametestInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"if (!rootProject.ext.isAlone) {
//   集成模式下Arouter的配置,用于組件間通信的實現javaCompileOptions {annotationProcessorOptions {arguments = [moduleName: project.getName()]}}}}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}compileOptions {sourceCompatibility JavaVersion.VERSION_1_7targetCompatibility 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_compilerif (!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.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、組件間通信實現

組件間通信的實現是采用阿里開源的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的點擊事件時,啟動組件界面跳轉。

@Overridepublic 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代理類

 @Overrideprotected 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<>();}@Overridepublic void attachBaseContext(Context base) {
//   初始化Manifest文件解析器,用于解析組件在自己的Manifest文件配置的ApplicationManifestParser 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);}}}@Overridepublic 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);}}}@Overridepublic 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-dataandroid: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;@Overridepublic void injectAppLifecycle(Context context, List<IAppLife> iAppLifes) {
//  這里需要把本引用添加到Application的生命周期的回調中,以便實現回調iAppLifes.add(this);}@Overridepublic void injectActivityLifecycle(Context context, List<Application.ActivityLifecycleCallbacks> lifecycleCallbackses) {}@Overridepublic void attachBaseContext(Context base) {}@Overridepublic void onCreate(Application application) {
//     在onCreate方法中對Dagger進行初始化mainComponent= DaggerMainComponent.builder().mainModule(new MainModule()).appComponent(BaseApplication.getAppComponent()).build();}@Overridepublic 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@PerApplicationpublic MainRepositoryManager provideRepositoryManager(@Named("live") Retrofit retrofit, DaoSession daoSession) {return new MainRepositoryManager(retrofit, daoSession);}@Provides@Named("live")@PerApplicationpublic 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")@PerApplicationpublic 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")@PerApplicationpublic 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.recycleviewcompile rootProject.ext.dependencies.designcompile(rootProject.ext.dependencies.support_v4) {exclude module: "support-annotations"}compile rootProject.ext.dependencies.annotationscompile(rootProject.ext.dependencies.butterknife) {exclude module: 'support-annotations'}compile rootProject.ext.dependencies.rxjava2compile(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.greenDaocompile rootProject.ext.dependencies.okhttp3compile rootProject.ext.dependencies.gsoncompile rootProject.ext.dependencies.glidecompile rootProject.ext.dependencies.eventBuscompile rootProject.ext.dependencies.dagger2compile(rootProject.ext.dependencies.rxpermission) {exclude module: 'rxjava'}compile rootProject.ext.dependencies.retrofit_converter_scalarsannotationProcessor rootProject.ext.dependencies.dagger2_compilerannotationProcessor rootProject.ext.dependencies.butterknife_compilercompile rootProject.ext.dependencies.butterknifecompile rootProject.ext.dependencies.transformationscompile 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相關SDKcompile '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插件,默認值trueenable = true// 自動生成tinkerId, 你無須關注tinkerId,默認為falseautoGenerateTinkerId = true// 指定歸檔目錄,默認值當前module的子目錄tinkerautoBackupApkDir = "${bakPath}"// 是否啟用覆蓋tinkerPatch配置功能,默認值false// 開啟后tinkerPatch配置不生效,即無需添加tinkerPatchoverrideTinkerPatchConfiguration = true// 編譯補丁包時,必需指定基線版本的apk,默認值為空// 如果為空,則表示不是進行補丁包的編譯// @{link tinkerPatch.oldApk }baseApk =  "${bakPath}/${baseApkDir}/app-release.apk"// 對應tinker插件applyMappingbaseApkProguardMapping = "${bakPath}/${baseApkDir}/app-release-mapping.txt"// 對應tinker插件applyResourceMappingbaseApkResourceMapping = "${bakPath}/${baseApkDir}/app-release-R.txt"// 構建基準包跟補丁包都要修改tinkerId,主要用于區分tinkerId = "1.0.5-base_patch"// 打多渠道補丁時指定目錄// buildAllFlavorsDir = "${bakPath}/${baseApkDir}"// 是否使用加固模式,默認為false// isProtectedApp = true// 是否采用反射Application的方式集成,無須改造ApplicationenableProxyApplication = true
}
/*** 一般來說,我們無需對下面的參數做任何的修改* 對于各參數的詳細介紹請參考:* https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97*/
tinkerPatch {tinkerEnable = trueignoreWarning = falseuseSign = truedex {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配置文件配置如下

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

  

最后在Application中初始化bugly

public class App extends BaseApplication {@Overridepublic void onCreate() {super.onCreate();setStrictMode();// 設置是否開啟熱更新能力,默認為trueBeta.enableHotfix = true;// 設置是否自動下載補丁Beta.canAutoDownloadPatch = true;// 設置是否提示用戶重啟Beta.canNotifyUserRestart = true;// 設置是否自動合成補丁Beta.canAutoPatch = true;/***  全量升級狀態回調*/Beta.upgradeStateListener = new UpgradeStateListener() {@Overridepublic void onUpgradeFailed(boolean b) {}@Overridepublic void onUpgradeSuccess(boolean b) {}@Overridepublic void onUpgradeNoVersion(boolean b) {Toast.makeText(getApplicationContext(), "最新版本", Toast.LENGTH_SHORT).show();}@Overridepublic void onUpgrading(boolean b) {Toast.makeText(getApplicationContext(), "onUpgrading", Toast.LENGTH_SHORT).show();}@Overridepublic void onDownloadCompleted(boolean b) {}};/*** 補丁回調接口,可以監聽補丁接收、下載、合成的回調*/Beta.betaPatchListener = new BetaPatchListener() {@Overridepublic void onPatchReceived(String patchFileUrl) {Toast.makeText(getApplicationContext(), patchFileUrl, Toast.LENGTH_SHORT).show();}@Overridepublic 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();}@Overridepublic void onDownloadSuccess(String patchFilePath) {Toast.makeText(getApplicationContext(), patchFilePath, Toast.LENGTH_SHORT).show();
//                Beta.applyDownloadedPatch();}@Overridepublic void onDownloadFailure(String msg) {Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();}@Overridepublic void onApplySuccess(String msg) {Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();}@Overridepublic void onApplyFailure(String msg) {Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();}@Overridepublic void onPatchRollback() {Toast.makeText(getApplicationContext(), "onPatchRollback", Toast.LENGTH_SHORT).show();}};long start = System.currentTimeMillis();// 這里實現SDK初始化,appId替換成你的在Bugly平臺申請的appId,調試時將第三個參數設置為trueBugly.init(this, "2e5309db50", true);long end = System.currentTimeMillis();}@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);// you must install multiDex whatever tinker is installed!MultiDex.install(base);// 安裝tinkerBeta.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/post/5a1cc83551882503eb4b0334
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

轉載于:https://www.cnblogs.com/0616--ataozhijia/p/8985320.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/390521.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/390521.shtml
英文地址,請注明出處:http://en.pswp.cn/news/390521.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何寫一個vue指令directive

舉個例子 &#xff1a;clickoutside.js const clickoutsideContext clickoutsideContext;export default {/*param el 指令所綁定的元素param binding {Object} param vnode vue編譯生成的虛擬節點*/bind (el, binding, vnode) {const documentHandler function(e) {console.…

安裝angular cli_Angular 9適用于初學者—如何使用Angular CLI安裝第一個應用程序

安裝angular cliAngular is one of the most popular JavaScript frameworks created and developed by Google. In the last couple of years, ReactJS has gained a lot of interest and has become the most popular modern JS library in the industry. But this doesn’t …

leetcode 1818. 絕對差值和

給你兩個正整數數組 nums1 和 nums2 &#xff0c;數組的長度都是 n 。 數組 nums1 和 nums2 的 絕對差值和 定義為所有 |nums1[i] - nums2[i]|&#xff08;0 < i < n&#xff09;的 總和&#xff08;下標從 0 開始&#xff09;。 你可以選用 nums1 中的 任意一個 元素來…

【轉載】keil5中加入STM32F10X_HD,USE_STDPERIPH_DRIVER的原因

初學STM32&#xff0c;在RealView MDK 環境中使用STM32固件庫建立工程時&#xff0c;初學者可能會遇到編譯不通過的問題。出現如下警告或錯誤提示&#xff1a; warning: #223-D: function "assert_param" declared implicitly;assert_param(IS_GPIO_ALL_PERIPH(GPIOx…

下崗職工_下崗后我如何獲得多位軟件工程師的面試

下崗職工“Opportunities to find our deeper powers come when life seems most challenging.” -Joseph Campbell“當生活似乎最具挑戰性時&#xff0c;就有機會找到我們更深層的力量。” 約瑟夫坎貝爾 I was recently laid off for the first time in my life. I realized t…

1846. 減小和重新排列數組后的最大元素

給你一個正整數數組 arr 。請你對 arr 執行一些操作&#xff08;也可以不進行任何操作&#xff09;&#xff0c;使得數組滿足以下條件&#xff1a; arr 中 第一個 元素必須為 1 。任意相鄰兩個元素的差的絕對值 小于等于 1 &#xff0c;也就是說&#xff0c;對于任意的 1 <…

bashdb常用命令

一、列出代碼和查詢代碼類&#xff1a; l 列出當前行以下的10行- 列出正在執行的代碼行的前面10行. 回到正在執行的代碼行w 列出正在執行的代碼行前后的代碼/pat/ 向后搜索pat&#xff1f;pat&#xff1f;向前搜索pat二、Debug控制類&#xff1a; h 幫助help 命令 得到…

podcast播客資源_為什么播客是我的新維基百科-完美的非正式學習資源

podcast播客資源In this article, I’ll explain why podcasts replaced a lot of my Wikipedia usage for informal learning. I’ll also talk about how I listen to 5 hours of podcasts every day.在本文中&#xff0c;我將解釋為什么播客代替了我的許多Wikipedia用于非正…

劍指 Offer 53 - I. 在排序數組中查找數字 I(二分法)

統計一個數字在排序數組中出現的次數。 示例 1: 輸入: nums [5,7,7,8,8,10], target 8 輸出: 2 示例 2: 輸入: nums [5,7,7,8,8,10], target 6 輸出: 0 限制&#xff1a; 0 < 數組長度 < 50000 解題思路 先用二分法查找出其中一個目標元素再向目標元素兩邊查找…

MVC與三層架構區別

我們平時總是將三層架構與MVC混為一談&#xff0c;殊不知它倆并不是一個概念。下面我來為大家揭曉我所知道的一些真相。 首先&#xff0c;它倆根本不是一個概念。 三層架構是一個分層式的軟件體系架構設計&#xff0c;它可適用于任何一個項目。 MVC是一個設計模式&#xff0c;它…

tensorflow 實現邏輯回歸——原以為TensorFlow不擅長做線性回歸或者邏輯回歸,原來是這么簡單哇!...

實現的是預測 低 出生 體重 的 概率。尼克麥克盧爾&#xff08;Nick McClure&#xff09;. TensorFlow機器學習實戰指南 (智能系統與技術叢書) (Kindle 位置 1060-1061). Kindle 版本. # Logistic Regression #---------------------------------- # # This function shows ho…

sdlc 瀑布式 生命周期_SDLC指南–軟件開發生命周期的階段和方法

sdlc 瀑布式 生命周期When I decided to teach myself how to code almost four years ago I had never heard of, let alone thought about, the software development life cycle.當我差不多四年前決定教自己如何編碼時&#xff0c;我從未聽說過軟件開發生命周期&#xff0c;…

劍指 Offer 48. 最長不含重復字符的子字符串

請從字符串中找出一個最長的不包含重復字符的子字符串&#xff0c;計算該最長子字符串的長度。 示例 1: 輸入: “abcabcbb” 輸出: 3 解釋: 因為無重復字符的最長子串是 “abc”&#xff0c;所以其長度為 3。 示例 2: 輸入: “bbbbb” 輸出: 1 解釋: 因為無重復字符的最長子…

Mysql-my-innodb-heavy-4G.cnf配置文件注解

Mysql-同Nginx等一樣具備多實例的特點&#xff0c;簡單的講就是在一臺服務器上同時開啟多個不同的服務端口&#xff08;3306,3307&#xff09;同時運行多個Mysql服務進程&#xff0c;這些服務進程通過不同的socket監聽不同的服務端口來提供服務。這些Mysql多實例公用一套Mysql安…

is 和 == 的區別

is 和 操作符的區別 python官方解釋&#xff1a; 的meaning為equal&#xff1b; is的meaning為object identity&#xff1b; is 判斷對象是否相等&#xff0c;即身份是否相同&#xff0c;使用id值判斷&#xff1b; 判斷對象的值是否相等。id值是什么&#xff1f;id()函數官網…

win10管理凌亂桌面_用于管理凌亂的開源存儲庫的命令行技巧

win10管理凌亂桌面Effective collaboration, especially in open source software development, starts with effective organization. To make sure that nothing gets missed, the general rule, “one issue, one pull request” is a nice rule of thumb.有效的協作(特別是…

JAVA數組Java StringBuffer 和 StringBuilder 類

版權聲明&#xff1a;本文為博主原創文章&#xff0c;未經博主允許不得轉載。 https://blog.csdn.net/qq_34173549/article/details/80215173 Java StringBuffer 和 StringBuilder 類 當對字符串進行修改的時候&#xff0c;需要使用 StringBuffer 和 StringBuilder 類。 和 Str…

strlen和sizeof的長度區別

strlen返回字符長度 而sizeof返回整個數組占多長&#xff0c;字符串的\0也會計入一個長度轉載于:https://www.cnblogs.com/DawaTech/p/8086055.html

了解如何使用Yii2 PHP框架創建YouTube克隆

Yii is a fast, secure, and efficient PHP framework used to create all kinds of web apps. Weve released a full video course on how to use the Yii2 framework.Yii是一個快速&#xff0c;安全&#xff0c;高效PHP框架&#xff0c;用于創建各種Web應用程序。 我們已經發…

劍指 Offer 66. 構建乘積數組

給定一個數組 A[0,1,…,n-1]&#xff0c;請構建一個數組 B[0,1,…,n-1]&#xff0c;其中 B[i] 的值是數組 A 中除了下標 i 以外的元素的積, 即 B[i]A[0]A[1]…A[i-1]A[i1]…A[n-1]。不能使用除法。 示例: 輸入: [1,2,3,4,5] 輸出: [120,60,40,30,24] 提示&#xff1a; 所有…