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.組件化的基本框架
?
?
?
?
4.組件化框架的具體實現
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運行即可。
?
組件模式
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
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。