<pre>
目錄
1.什么是組件化霍转?
2.為什么需要組件化和組件化帶來的好處极阅?
3.組件化的基本框架
4.組件化框架的具體實現(xiàn)
4.1.基類庫的封裝
4.2組件模式和集成模式切換的實現(xiàn)
4.3第三方開源庫和組件版本號的管理
4.4.組件間通信實現(xiàn)
5.組件合并時res資源和AndroidManifest配置的問題
6.組件全局application的實現(xiàn)和數(shù)據(jù)的初始化
7.組件內(nèi)網(wǎng)絡請求和攔截器的實現(xiàn)
8.組件化實現(xiàn)的技術(shù)難點
8.1.greendao數(shù)據(jù)庫在組件內(nèi)的實現(xiàn)
8.2.資源命名沖突
8.3.butterKnife不能使用的原因
9.組件化與熱修復的無縫連接
10.結(jié)束語
前言
本文所講的組件化案例是基于自己開源的組件化框架項目
github上地址https://github.com/HelloChenJinJun/NewFastFrame
其中即時通訊(Chat)模塊是單獨的項目
github上地址https://github.com/HelloChenJinJun/TestChat
1.什么是組件化答憔?
項目發(fā)展到一定階段時攘已,隨著需求的增加以及頻繁地變更女蜈,項目會越來越大验烧,代碼變得越來越臃腫板驳,耦合會越來越多,開發(fā)效率也會降低碍拆,這個時候我們就需要對舊項目進行重構(gòu)即模塊的拆分若治,官方的說法就是組件化慨蓝。
2.為什么需要組件化和組件化帶來的好處?
1端幼、 現(xiàn)在Android項目中代碼量達到一定程度礼烈,編譯將是一件非常痛苦的事情,一般都需要變異5到6分鐘婆跑。Android studio推出instant run由于各種缺陷和限制條件(比如采用熱修復tinker)一般情況下是被關(guān)閉的此熬。而組件化框架可以使模塊單獨編譯調(diào)試,可以有效地減少編譯的時間滑进。
2犀忱、通過組件化可以更好的進行并行開發(fā),因為我們可以為每一個模塊進行單獨的版本控制郊供,甚至每一個模塊的負責人可以選擇自己的設(shè)計架構(gòu)而不影響其他模塊的開發(fā)峡碉,與此同時組件化還可以避免模塊之間的交叉依賴,每一個模塊的開發(fā)人員可以對自己的模塊進行獨立測試驮审,獨立編譯和運行鲫寄,甚至可以實現(xiàn)單獨的部署。從而極大的提高了并行開發(fā)效率疯淫。
3.組件化的基本框架
[圖片上傳失敗...(image-bf1f7e-1514268060021)]
[圖片上傳失敗...(image-424472-1514268060021)]
4.組件化框架的具體實現(xiàn)
4.1地来、基類庫的封裝
[圖片上傳失敗...(image-add815-1514268060021)]
基類庫中主要包括開發(fā)常用的一些框架。
1熙掺、網(wǎng)絡請求(多任務下載和上傳判族,采用Retrofit+RxJava框架)
2雀瓢、圖片加載(策略模式,Glide與Picasso之間可以切換)
3、通信機制(RxBus)
4各薇、基類adapter的封裝(支持item動畫渡冻、多布局item踊兜、下拉和加載更多犹赖、item點擊事件)
5、基類RecyclerView的封裝(支持原生風格的下拉加載董瞻,item側(cè)滑等)
6寞蚌、mvp框架
7、各組件的數(shù)據(jù)庫實體類
8钠糊、通用的工具類
9挟秤、自定義view(包括對話框,ToolBar布局抄伍,圓形圖片等view的自定義)
10艘刚、dagger的封裝(用于初始化全局的變量和網(wǎng)絡請求等配置)
等等
4.2組件模式和集成模式切換的實現(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的配置昔脯,用于組件間通信的實現(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運行即可静陈。
[圖片上傳失敗...(image-d84731-1514268060021)]
組件模式
1、首先需要在config,gradle文件中設(shè)置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:作為組件模式存在
// 各個組件版本號的統(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牵敷、組件間通信實現(xiàn)
組件間通信的實現(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è)置每個item的點擊事件時,啟動組件界面跳轉(zhuǎn)枷餐。
@Override
public void onItemClick(int position, View view) {
MainItemBean item=mainAdapter.getData(position);
ARouter.getInstance().build(item.getPath()).navigation();
}
每個組件入口界面的設(shè)置(比如直播Live組件,其它組件類似)
@Route(path = "/live/main")
public class MainActivity extends BaseActivity<List<CategoryLiveBean>, MainPresenter> implements View.OnClickListener {
5.組件合并時res資源和AndroidManifest配置的問題
我們通過判斷組件處于哪種模式來動態(tài)設(shè)置項目res資源和Manifest苫亦、以及代碼的位置毛肋。以直播組件為例,其它組件類似屋剑。
[圖片上傳失敗...(image-6955db-1514268060021)]
直播組件的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的實現(xiàn)和數(shù)據(jù)的初始化
采用類似于Glide在Manifest初始化配置的方式來初始化各個組件的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列表之后卫病,給每個組件Application注入
context,和Application的生命周期的回調(diào)典徘,用于實現(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) {
// 相應調(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) {
// 相應調(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會對其中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帜平,用于初始化組件內(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)中,以便實現(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進行初始化
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)絡請求和攔截器的實現(xiàn)
由于每個組件的BaseUrl和網(wǎng)絡配置等可能不一樣裆甩,所以每個組件可以在自己配置的dagger中的 MainConponent實現(xiàn)自己的網(wǎng)絡請求和攔截器冗锁。
以直播組件為例,其它類似嗤栓。
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.組件化實現(xiàn)的技術(shù)難點
8.1.greendao數(shù)據(jù)庫的實現(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ù)庫放在基類庫中叨叙,并且各個組件的實體類bean的創(chuàng)建也只能在基類庫中進行,以分包命名進行區(qū)分堪澎,如下圖擂错。因為如果在組件內(nèi)創(chuàng)建bean 會重新生成另一個副本DaoMaster并且不能操控其他組件的數(shù)據(jù)庫實體,有很大的局限性樱蛤。
[圖片上傳失敗...(image-b9fd79-1514268060021)]
8.2.資源命名沖突
官方說法是在每個module的build.gradle文件中配置資源文件名前綴
這種方法缺點就是钮呀,所有的資源名必須要以指定的字符串(moudle_prefix)做前綴,否則會異常報錯刹悴,而且這方法只限定xml里面的資源行楞,對圖片資源并不起作用,所以圖片資源仍然需要手動去修改資源名土匀。
所以不是很推薦使用這種方法來解決資源名沖突子房。所以只能自己注意點,在創(chuàng)建資源的時候就轧,盡量不讓其重復证杭。
resourcePrefix "moudle_prefix"
8.3.butterKnife不能使用的原因
雖然Butterknife支持在lib中使用,但是條件是用 R2 代替 R 妒御,在組件模式和集成模式的切換中解愤,R2<->R之間的切換是無法完成轉(zhuǎn)換的,切換一次要改動全身乎莉,是非常麻煩的送讲!所以不推薦在組件化中使用Butterknife。
8.4.library重復依賴問題
1惋啃、可能大家會認為哼鬓,每個組件都依賴基類庫,基類庫library次不是重復依賴了边灭?其實并不會存在這樣的問題异希,因為在構(gòu)建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平臺憨降,用于監(jiān)控異常信息父虑、熱修復和應用升級。
具體實現(xiàn):
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相關(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)建生成的基準包目錄
*/
def baseApkDir = "app-0831-17-50-44"
/**
* 對于插件各參數(shù)的詳細解析請參考
*/
tinkerSupport {
// 開啟tinker-support插件频轿,默認值true
enable = true
// 自動生成tinkerId, 你無須關(guān)注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"
// 構(gòu)建基準包跟補丁包都要修改tinkerId九火,主要用于區(qū)分
tinkerId = "1.0.5-base_patch"
// 打多渠道補丁時指定目錄
// buildAllFlavorsDir = "${bakPath}/${baseApkDir}"
// 是否使用加固模式,默認為false
// isProtectedApp = true
// 是否采用反射Application的方式集成册招,無須改造Application
enableProxyApplication = true
}
/**
* 一般來說,我們無需對下面的參數(shù)做任何的修改
* 對于各參數(shù)的詳細介紹請參考:
* 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è)置是否開啟熱更新能力岔激,默認為true
Beta.enableHotfix = true;
// 設(shè)置是否自動下載補丁
Beta.canAutoDownloadPatch = true;
// 設(shè)置是否提示用戶重啟
Beta.canNotifyUserRestart = true;
// 設(shè)置是否自動合成補丁
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) {
}
};
/**
* 補丁回調(diào)接口,可以監(jiān)聽補丁接收是掰、下載虑鼎、合成的回調(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();
// 這里實現(xiàn)SDK初始化,appId替換成你的在Bugly平臺申請的appId,調(diào)試時將第三個參數(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é)束語
該組件框架是自己在暑假實習期間做的键痛,由于實習公司的項目過于龐大和復雜炫彩,每次編譯都需要花費10幾分鐘,心都碎了絮短,所以才想嘗試下組件化框架江兢,摸索了很長時間,最后還是做出來了丁频,大概花費2個多月的時間杉允,由于最近項目上比較忙,所以沒什么時間來完善席里,界面有點簡陋叔磷,但邏輯基本實現(xiàn)了。歡迎fork and star胁勺。
有對組件化框架興趣的同學可以加本人QQ1981367757世澜,一起探討技術(shù)。
github上地址: https://github.com/HelloChenJinJun/NewFastFrame
原著:
作者:傻瓜愛笨蛋
鏈接:http://www.reibang.com/p/e6eb9c8d120f
來源:簡書
著作權(quán)歸作者所有署穗。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)寥裂,非商業(yè)轉(zhuǎn)載請注明出處嵌洼。</pre>