目錄
1.什么是組件化?
2.為什么需要組件化和組件化帶來的好處?
3.組件化的基本框架
4.組件化框架的具體實(shí)現(xiàn)
4.1.基類庫的封裝
4.2組件模式和集成模式切換的實(shí)現(xiàn)
4.3第三方開源庫和組件版本號的管理
4.4.組件間通信實(shí)現(xiàn)
5.組件合并時(shí)res資源和AndroidManifest配置的問題
6.組件全局application的實(shí)現(xiàn)和數(shù)據(jù)的初始化
7.組件內(nèi)網(wǎng)絡(luò)請求和攔截器的實(shí)現(xiàn)
8.組件化實(shí)現(xiàn)的技術(shù)難點(diǎn)
8.1.greendao數(shù)據(jù)庫在組件內(nèi)的實(shí)現(xiàn)
8.2.資源命名沖突
8.3.butterKnife不能使用的原因
9.組件化與熱修復(fù)的無縫連接
10.結(jié)束語
前言
本文所講的組件化案例是基于自己開源的組件化框架項(xiàng)目
github上地址https://github.com/HelloChenJinJun/NewFastFrame
其中即時(shí)通訊(Chat)模塊是單獨(dú)的項(xiàng)目
github上地址https://github.com/HelloChenJinJun/TestChat
1.什么是組件化?
項(xiàng)目發(fā)展到一定階段時(shí),隨著需求的增加以及頻繁地變更,項(xiàng)目會(huì)越來越大,代碼變得越來越臃腫,耦合會(huì)越來越多,開發(fā)效率也會(huì)降低,這個(gè)時(shí)候我們就需要對舊項(xiàng)目進(jìn)行重構(gòu)即模塊的拆分,官方的說法就是組件化。
2.為什么需要組件化和組件化帶來的好處?
1、 現(xiàn)在Android項(xiàng)目中代碼量達(dá)到一定程度,編譯將是一件非常痛苦的事情,一般都需要變異5到6分鐘。Android studio推出instant run由于各種缺陷和限制條件(比如采用熱修復(fù)tinker)一般情況下是被關(guān)閉的。而組件化框架可以使模塊單獨(dú)編譯調(diào)試,可以有效地減少編譯的時(shí)間。
2、通過組件化可以更好的進(jìn)行并行開發(fā),因?yàn)槲覀兛梢詾槊恳粋€(gè)模塊進(jìn)行單獨(dú)的版本控制,甚至每一個(gè)模塊的負(fù)責(zé)人可以選擇自己的設(shè)計(jì)架構(gòu)而不影響其他模塊的開發(fā),與此同時(shí)組件化還可以避免模塊之間的交叉依賴,每一個(gè)模塊的開發(fā)人員可以對自己的模塊進(jìn)行獨(dú)立測試,獨(dú)立編譯和運(yùn)行,甚至可以實(shí)現(xiàn)單獨(dú)的部署。從而極大的提高了并行開發(fā)效率。
3.組件化的基本框架
4.組件化框架的具體實(shí)現(xiàn)
4.1、基類庫的封裝
基類庫中主要包括開發(fā)常用的一些框架。
1、網(wǎng)絡(luò)請求(多任務(wù)下載和上傳,采用Retrofit+RxJava框架)
2、圖片加載(策略模式,Glide與Picasso之間可以切換)
3、通信機(jī)制(RxBus)
4、基類adapter的封裝(支持item動(dòng)畫、多布局item、下拉和加載更多、item點(diǎn)擊事件)
5、基類RecyclerView的封裝(支持原生風(fēng)格的下拉加載,item側(cè)滑等)
6、mvp框架
7、各組件的數(shù)據(jù)庫實(shí)體類
8、通用的工具類
9、自定義view(包括對話框,ToolBar布局,圓形圖片等view的自定義)
10、dagger的封裝(用于初始化全局的變量和網(wǎng)絡(luò)請求等配置)
等等
4.2組件模式和集成模式切換的實(shí)現(xiàn)
music組件下的build.gradle文件,其他組件類似。
//控制組件模式和集成模式
if (rootProject.ext.isAlone) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
apply plugin: 'com.neenbedankt.android-apt'
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
buildToolsVersion rootProject.ext.android.buildToolsVersion
defaultConfig {
if (rootProject.ext.isAlone) {
// 組件模式下設(shè)置applicationId
applicationId "com.example.cootek.music"
}
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
if (!rootProject.ext.isAlone) {
// 集成模式下Arouter的配置,用于組件間通信的實(shí)現(xiàn)
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName()]
}
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
sourceSets {
main {
//控制兩種模式下的資源和代碼配置情況
if (rootProject.ext.isAlone) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
java.srcDirs = ['src/main/java', 'src/main/module/java']
res.srcDirs = ['src/main/res', 'src/main/module/res']
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
// 依賴基類庫
compile project(':commonlibrary')
//用作顏色選擇器
compile 'com.afollestad.material-dialogs:commons:0.9.1.0'
apt rootProject.ext.dependencies.dagger2_compiler
if (!rootProject.ext.isAlone) {
// 集成模式下需要編譯器生成路由通信的代碼
apt rootProject.ext.dependencies.arouter_compiler
}
testCompile 'junit:junit:4.12'
}
集成模式
1、首先需要在config,gradle文件中設(shè)置isAlone=false
ext {
isAlone = false;//false:作為Lib組件存在, true:作為application存在
2、然后Sync 下。
3、最后選擇app運(yùn)行即可。
組件模式
1、首先需要在config,gradle文件中設(shè)置isAlone=true
ext {
isAlone = true;//false:作為Lib組件存在, true:作為application存在
2、然后Sync 下。
3、最后相應(yīng)的模塊(new、chat、live、music、app)進(jìn)行運(yùn)行即可。
4.3第三方開源庫和組件版本號的管理
config.gradle文件的配置情況
ext {
isAlone = false;//false:作為集成模式存在, true:作為組件模式存在
// 各個(gè)組件版本號的統(tǒng)一管理
android = [
compileSdkVersion: 24,
buildToolsVersion: "25.0.2",
minSdkVersion : 16,
targetSdkVersion : 22,
versionCode : 1,
versionName : '1.0.0',
]
libsVersion = [
// 第三方庫版本號的管理
supportLibraryVersion = "25.3.0",
retrofitVersion = "2.1.0",
glideVersion = "3.7.0",
loggerVersion = "1.15",
// eventbusVersion = "3.0.0",
gsonVersion = "2.8.0",
butterknife = "8.8.0",
retrofit = "2.3.0",
rxjava = "2.1.1",
rxjava_android = "2.0.1",
rxlifecycle = "2.1.0",
rxlifecycle_components = "2.1.0",
dagger_compiler = "2.11",
dagger = "2.11",
greenDao = "3.2.2",
arouter_api = "1.2.2",
arouter_compiler = "1.1.3",
transformations = "2.0.2",
rxjava_adapter = "2.3.0",
gson_converter = "2.3.0",
scalars_converter = "2.3.0",
rxpermission = "0.9.4",
eventbus="3.0.0",
support_v4="25.4.0",
okhttp3="3.8.1"
]
// 依賴庫管理
dependencies = [
appcompatV7 : "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion",
design : "com.android.support:design:$rootProject.supportLibraryVersion",
cardview : "com.android.support:cardview-v7:$rootProject.supportLibraryVersion",
palette : "com.android.support:palette-v7:$rootProject.supportLibraryVersion",
recycleview : "com.android.support:recyclerview-v7:$rootProject.supportLibraryVersion",
support_v4 : "com.android.support:support-v4:$rootProject.support_v4",
annotations : "com.android.support:support-annotations:$rootProject.supportLibraryVersion",
eventBus : "org.greenrobot:eventbus:$rootProject.eventbus",
glide : "com.github.bumptech.glide:glide:$rootProject.glideVersion",
gson : "com.google.code.gson:gson:$rootProject.gsonVersion",
logger : "com.orhanobut:logger:$rootProject.loggerVersion",
butterknife : "com.jakewharton:butterknife:$rootProject.butterknife",
butterknife_compiler : "com.jakewharton:butterknife-compiler:$rootProject.butterknife",
retrofit : "com.squareup.retrofit2:retrofit:$rootProject.retrofit",
okhttp3 : "com.squareup.okhttp3:okhttp:$rootProject.retrofit",
retrofit_adapter_rxjava2 : "com.squareup.retrofit2:adapter-rxjava2:$rootProject.rxjava_adapter",
retrofit_converter_gson : "com.squareup.retrofit2:converter-gson:$rootProject.gson_converter",
retrofit_converter_scalars: "com.squareup.retrofit2:converter-scalars:$rootProject.scalars_converter",
rxpermission : "com.tbruyelle.rxpermissions2:rxpermissions:$rootProject.rxpermission@aar",
rxjava2 : "io.reactivex.rxjava2:rxjava:$rootProject.rxjava",
rxjava2_android : "io.reactivex.rxjava2:rxandroid:$rootProject.rxjava_android",
rxlifecycle2 : "com.trello.rxlifecycle2:rxlifecycle:$rootProject.rxlifecycle",
rxlifecycle2_components : "com.trello.rxlifecycle2:rxlifecycle-components:$rootProject.rxlifecycle_components",
dagger2_compiler : "com.google.dagger:dagger-compiler:$rootProject.dagger_compiler",
dagger2 : "com.google.dagger:dagger:$rootProject.dagger",
greenDao : "org.greenrobot:greendao:$rootProject.greenDao",
transformations : "jp.wasabeef:glide-transformations:$rootProject.transformations",
//路由通訊
arouter_api : "com.alibaba:arouter-api:$rootProject.arouter_api",
arouter_compiler : "com.alibaba:arouter-compiler:$rootProject.arouter_compiler"
]
}
4.4、組件間通信實(shí)現(xiàn)
組件間通信的實(shí)現(xiàn)是采用阿里開源的Arouter路由通信。
github地址:https://github.com/alibaba/ARouter
在App工程中,初始化組件通信數(shù)據(jù)
private List<MainItemBean> getDefaultData() {
List<MainItemBean> result=new ArrayList<>();
MainItemBean mainItemBean=new MainItemBean();
mainItemBean.setName("校園");
mainItemBean.setPath("/news/main");
mainItemBean.setResId(R.mipmap.ic_launcher);
MainItemBean music=new MainItemBean();
music.setName("音樂");
music.setResId(R.mipmap.ic_launcher);
music.setPath("/music/main");
MainItemBean live=new MainItemBean();
live.setName("直播");
live.setResId(R.mipmap.ic_launcher);
live.setPath("/live/main");
MainItemBean chat=new MainItemBean();
chat.setName("聊天");
chat.setPath("/chat/splash");
chat.setResId(R.mipmap.ic_launcher);
result.add(mainItemBean);
result.add(music);
result.add(live);
result.add(chat);
return result;
}
然后在設(shè)置每個(gè)item的點(diǎn)擊事件時(shí),啟動(dòng)組件界面跳轉(zhuǎn)。
@Override
public void onItemClick(int position, View view) {
MainItemBean item=mainAdapter.getData(position);
ARouter.getInstance().build(item.getPath()).navigation();
}
每個(gè)組件入口界面的設(shè)置(比如直播Live組件,其它組件類似)
@Route(path = "/live/main")
public class MainActivity extends BaseActivity<List<CategoryLiveBean>, MainPresenter> implements View.OnClickListener {
5.組件合并時(shí)res資源和AndroidManifest配置的問題
我們通過判斷組件處于哪種模式來動(dòng)態(tài)設(shè)置項(xiàng)目res資源和Manifest、以及代碼的位置。以直播組件為例,其它組件類似。
直播組件的build.gradle文件對代碼資源等位置的配置
sourceSets {
main {
if (rootProject.ext.isAlone) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
java.srcDirs = ['src/main/java', 'src/main/module/java']
res.srcDirs = ['src/main/res', 'src/main/module/res']
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
6.組件全局application的實(shí)現(xiàn)和數(shù)據(jù)的初始化
采用類似于Glide在Manifest初始化配置的方式來初始化各個(gè)組件的Application,以直播組件為例,其它類似。
在BaseApplication中,初始化ApplicationDelegate代理類
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
applicationDelegate = new ApplicationDelegate();
applicationDelegate.attachBaseContext(base);
MultiDex.install(this);
}
ApplicationDelegate內(nèi)部是怎樣的呢?繼續(xù)看下去
public class ApplicationDelegate implements IAppLife {
private List<IModuleConfig> list;
private List<IAppLife> appLifes;
private List<Application.ActivityLifecycleCallbacks> liferecycleCallbacks;
public ApplicationDelegate() {
appLifes = new ArrayList<>();
liferecycleCallbacks = new ArrayList<>();
}
@Override
public void attachBaseContext(Context base) {
// 初始化Manifest文件解析器,用于解析組件在自己的Manifest文件配置的Application
ManifestParser manifestParser = new ManifestParser(base);
list = manifestParser.parse();
//解析得到的組件Application列表之后,給每個(gè)組件Application注入
context,和Application的生命周期的回調(diào),用于實(shí)現(xiàn)application的同步
if (list != null && list.size() > 0) {
for (IModuleConfig configModule :
list) {
configModule.injectAppLifecycle(base, appLifes);
configModule.injectActivityLifecycle(base, liferecycleCallbacks);
}
}
if (appLifes != null && appLifes.size() > 0) {
for (IAppLife life :
appLifes) {
life.attachBaseContext(base);
}
}
}
@Override
public void onCreate(Application application) {
// 相應(yīng)調(diào)用組件Application代理類的onCreate方法
if (appLifes != null && appLifes.size() > 0) {
for (IAppLife life :
appLifes) {
life.onCreate(application);
}
}
if (liferecycleCallbacks != null && liferecycleCallbacks.size() > 0) {
for (Application.ActivityLifecycleCallbacks life :
liferecycleCallbacks) {
application.registerActivityLifecycleCallbacks(life);
}
}
}
@Override
public void onTerminate(Application application) {
// 相應(yīng)調(diào)用組件Application代理類的onTerminate方法
if (appLifes != null && appLifes.size() > 0) {
for (IAppLife life :
appLifes) {
life.onTerminate(application);
}
}
if (liferecycleCallbacks != null && liferecycleCallbacks.size() > 0) {
for (Application.ActivityLifecycleCallbacks life :
liferecycleCallbacks) {
application.unregisterActivityLifecycleCallbacks(life);
}
}
}
}
組件Manifest中application的全局配置
<meta-data
android:name="com.example.live.LiveApplication"
android:value="IModuleConfig" />
ManifestParser會(huì)對其中value為IModuleConfig的meta-data進(jìn)行解析,并通過反射生成實(shí)例。
public final class ManifestParser {
private static final String MODULE_VALUE = "IModuleConfig";
private final Context context;
public ManifestParser(Context context) {
this.context = context;
}
public List<IModuleConfig> parse() {
List<IModuleConfig> modules = new ArrayList<>();
try {
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(
context.getPackageName(), PackageManager.GET_META_DATA);
if (appInfo.metaData != null) {
for (String key : appInfo.metaData.keySet()) {
//會(huì)對其中value為IModuleConfig的meta-data進(jìn)行解析,并通過反射生成實(shí)例
if (MODULE_VALUE.equals(appInfo.metaData.get(key))) {
modules.add(parseModule(key));
}
}
}
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException("Unable to find metadata to parse IModuleConfig", e);
}
return modules;
}
//通過類名生成實(shí)例
private static IModuleConfig parseModule(String className) {
Class<?> clazz;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Unable to find IModuleConfig implementation", e);
}
Object module;
try {
module = clazz.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException("Unable to instantiate IModuleConfig implementation for " + clazz, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to instantiate IModuleConfig implementation for " + clazz, e);
}
if (!(module instanceof IModuleConfig)) {
throw new RuntimeException("Expected instanceof IModuleConfig, but found: " + module);
}
return (IModuleConfig) module;
}
這樣通過以上步驟就可以在Manifest文件中配置自己組件的Application,用于初始化組件內(nèi)的數(shù)據(jù),比如在直播組件中初始化Dagger的全局配置
public class LiveApplication implements IModuleConfig,IAppLife {
private static MainComponent mainComponent;
@Override
public void injectAppLifecycle(Context context, List<IAppLife> iAppLifes) {
// 這里需要把本引用添加到Application的生命周期的回調(diào)中,以便實(shí)現(xiàn)回調(diào)
iAppLifes.add(this);
}
@Override
public void injectActivityLifecycle(Context context, List<Application.ActivityLifecycleCallbacks> lifecycleCallbackses) {
}
@Override
public void attachBaseContext(Context base) {
}
@Override
public void onCreate(Application application) {
// 在onCreate方法中對Dagger進(jìn)行初始化
mainComponent= DaggerMainComponent.builder().mainModule(new MainModule()).appComponent(BaseApplication.getAppComponent()).build();
}
@Override
public void onTerminate(Application application) {
if (mainComponent != null) {
mainComponent = null;
}
}
public static MainComponent getMainComponent() {
return mainComponent;
}
}
7.組件內(nèi)網(wǎng)絡(luò)請求和攔截器的實(shí)現(xiàn)
由于每個(gè)組件的BaseUrl和網(wǎng)絡(luò)配置等可能不一樣,所以每個(gè)組件可以在自己配置的dagger中的 MainConponent實(shí)現(xiàn)自己的網(wǎng)絡(luò)請求和攔截器。
以直播組件為例,其它類似。
MainComponent
@PerApplication
@Component(dependencies = AppComponent.class, modules = MainModule.class)
public interface MainComponent {
public DaoSession getDaoSession();
public MainRepositoryManager getMainRepositoryManager();
}
MainModule代碼
@Module
public class MainModule {
@Provides
@PerApplication
public MainRepositoryManager provideRepositoryManager(@Named("live") Retrofit retrofit, DaoSession daoSession) {
return new MainRepositoryManager(retrofit, daoSession);
}
@Provides
@Named("live")
@PerApplication
public Retrofit provideRetrofit(@Named("live") OkHttpClient okHttpClient,@Nullable Gson gson){
Retrofit.Builder builder=new Retrofit.Builder().baseUrl(LiveUtil.BASE_URL).addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson)).client(okHttpClient);
return builder.build();
}
@Provides
@Named("live")
@PerApplication
public OkHttpClient provideOkHttpClient(@Named("live")LiveInterceptor interceptor){
OkHttpClient.Builder builder=new OkHttpClient.Builder();
builder.connectTimeout(10, TimeUnit.SECONDS).readTimeout(10,TimeUnit.SECONDS);
builder.addInterceptor(interceptor);
return builder.build();
}
@Provides
@Named("live")
@PerApplication
public LiveInterceptor provideNewsInterceptor(){
return new LiveInterceptor();
}
}
8.組件化實(shí)現(xiàn)的技術(shù)難點(diǎn)
8.1.greendao數(shù)據(jù)庫的實(shí)現(xiàn)
greendao數(shù)據(jù)庫初始化代碼,在基類庫的NetClientModule.java中
public DaoSession provideDaoSession(Application application) {
DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(application, "common_library_db", null);
Database database = devOpenHelper.getWritableDb();
DaoMaster master = new DaoMaster(database);
return master.newSession();
}
其中的DaoMaster是通過APT生成的,由于DaoMaster給全局的組件使用,所以只能將greendao 數(shù)據(jù)庫放在基類庫中,并且各個(gè)組件的實(shí)體類bean的創(chuàng)建也只能在基類庫中進(jìn)行,以分包命名進(jìn)行區(qū)分,如下圖。因?yàn)槿绻诮M件內(nèi)創(chuàng)建bean 會(huì)重新生成另一個(gè)副本DaoMaster并且不能操控其他組件的數(shù)據(jù)庫實(shí)體,有很大的局限性。
8.2.資源命名沖突
官方說法是在每個(gè)module的build.gradle文件中配置資源文件名前綴
這種方法缺點(diǎn)就是,所有的資源名必須要以指定的字符串(moudle_prefix)做前綴,否則會(huì)異常報(bào)錯(cuò),而且這方法只限定xml里面的資源,對圖片資源并不起作用,所以圖片資源仍然需要手動(dòng)去修改資源名。
所以不是很推薦使用這種方法來解決資源名沖突。所以只能自己注意點(diǎn),在創(chuàng)建資源的時(shí)候,盡量不讓其重復(fù)。
resourcePrefix "moudle_prefix"
8.3.butterKnife不能使用的原因
雖然Butterknife支持在lib中使用,但是條件是用 R2 代替 R ,在組件模式和集成模式的切換中,R2<->R之間的切換是無法完成轉(zhuǎn)換的,切換一次要改動(dòng)全身,是非常麻煩的!所以不推薦在組件化中使用Butterknife。
8.4.library重復(fù)依賴問題
1、可能大家會(huì)認(rèn)為,每個(gè)組件都依賴基類庫,基類庫library次不是重復(fù)依賴了?其實(shí)并不會(huì)存在這樣的問題,因?yàn)樵跇?gòu)建APP的過程中Gradle會(huì)自動(dòng)將重復(fù)的arr包排除,也就不會(huì)存在重復(fù)依賴基類庫的情況。
2、但是第三方開源庫依賴的包可能會(huì)與我們自己引用的包重復(fù),所以我們需要將多余的包給排除出去。
基類庫(CommonLibrary)中build.gradle
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile(rootProject.ext.dependencies.appcompatV7) {
exclude module: "support-v4"
exclude module: "support-annotations"
}
compile rootProject.ext.dependencies.recycleview
compile rootProject.ext.dependencies.design
compile(rootProject.ext.dependencies.support_v4) {
exclude module: "support-annotations"
}
compile rootProject.ext.dependencies.annotations
compile(rootProject.ext.dependencies.butterknife) {
exclude module: 'support-annotations'
}
compile rootProject.ext.dependencies.rxjava2
compile(rootProject.ext.dependencies.rxjava2_android) {
exclude module: "rxjava"
}
compile(rootProject.ext.dependencies.rxlifecycle2) {
exclude module: 'rxjava'
exclude module: 'jsr305'
}
compile(rootProject.ext.dependencies.rxlifecycle2_components) {
exclude module: 'support-v4'
exclude module: 'appcompat-v7'
exclude module: 'support-annotations'
exclude module: 'rxjava'
exclude module: 'rxandroid'
exclude module: 'rxlifecycle'
}
compile(rootProject.ext.dependencies.retrofit) {
exclude module: 'okhttp'
exclude module: 'okio'
}
compile(rootProject.ext.dependencies.retrofit_converter_gson) {
exclude module: 'gson'
exclude module: 'okhttp'
exclude module: 'okio'
exclude module: 'retrofit'
}
compile(rootProject.ext.dependencies.retrofit_adapter_rxjava2) {
exclude module: 'rxjava'
exclude module: 'okhttp'
exclude module: 'retrofit'
exclude module: 'okio'
}
compile rootProject.ext.dependencies.greenDao
compile rootProject.ext.dependencies.okhttp3
compile rootProject.ext.dependencies.gson
compile rootProject.ext.dependencies.glide
compile rootProject.ext.dependencies.eventBus
compile rootProject.ext.dependencies.dagger2
compile(rootProject.ext.dependencies.rxpermission) {
exclude module: 'rxjava'
}
compile rootProject.ext.dependencies.retrofit_converter_scalars
annotationProcessor rootProject.ext.dependencies.dagger2_compiler
annotationProcessor rootProject.ext.dependencies.butterknife_compiler
compile rootProject.ext.dependencies.butterknife
compile rootProject.ext.dependencies.transformations
compile rootProject.ext.dependencies.arouter_api
}
9.組件化與熱修復(fù)的無縫連接
本開源項(xiàng)目是基于騰訊的bugly平臺(tái),用于監(jiān)控異常信息、熱修復(fù)和應(yīng)用升級。
具體實(shí)現(xiàn):
1、在工程的根目錄build.gradle配置
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "com.tencent.bugly:tinker-support:1.0.8"
}
}
然后在App 的build.gradle進(jìn)行以下配置
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
if (!rootProject.ext.isAlone) {
compile project(':chat')
compile project(':music')
compile project(':news')
compile project(':live')
apt rootProject.ext.dependencies.arouter_compiler
} else {
compile project(':commonlibrary')
}
testCompile 'junit:junit:4.12'
// 依賴bugly相關(guān)SDK
compile 'com.tencent.bugly:crashreport_upgrade:1.3.1'
compile 'com.tencent.bugly:nativecrashreport:latest.release'
}
apply from: 'tinker-support.gradle'
然后依賴其中的插件腳本
apply from: 'tinker-support.gradle'
其中的tinker-support.gradle文件如下:
apply plugin: 'com.tencent.bugly.tinker-support'
def bakPath = file("${buildDir}/bakApk/")
/**
* 此處填寫每次構(gòu)建生成的基準(zhǔn)包目錄
*/
def baseApkDir = "app-0831-17-50-44"
/**
* 對于插件各參數(shù)的詳細(xì)解析請參考
*/
tinkerSupport {
// 開啟tinker-support插件,默認(rèn)值true
enable = true
// 自動(dòng)生成tinkerId, 你無須關(guān)注tinkerId,默認(rèn)為false
autoGenerateTinkerId = true
// 指定歸檔目錄,默認(rèn)值當(dāng)前module的子目錄tinker
autoBackupApkDir = "${bakPath}"
// 是否啟用覆蓋tinkerPatch配置功能,默認(rèn)值false
// 開啟后tinkerPatch配置不生效,即無需添加tinkerPatch
overrideTinkerPatchConfiguration = true
// 編譯補(bǔ)丁包時(shí),必需指定基線版本的apk,默認(rèn)值為空
// 如果為空,則表示不是進(jìn)行補(bǔ)丁包的編譯
// @{link tinkerPatch.oldApk }
baseApk = "${bakPath}/${baseApkDir}/app-release.apk"
// 對應(yīng)tinker插件applyMapping
baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-release-mapping.txt"
// 對應(yīng)tinker插件applyResourceMapping
baseApkResourceMapping = "${bakPath}/${baseApkDir}/app-release-R.txt"
// 構(gòu)建基準(zhǔn)包跟補(bǔ)丁包都要修改tinkerId,主要用于區(qū)分
tinkerId = "1.0.5-base_patch"
// 打多渠道補(bǔ)丁時(shí)指定目錄
// buildAllFlavorsDir = "${bakPath}/${baseApkDir}"
// 是否使用加固模式,默認(rèn)為false
// isProtectedApp = true
// 是否采用反射Application的方式集成,無須改造Application
enableProxyApplication = true
}
/**
* 一般來說,我們無需對下面的參數(shù)做任何的修改
* 對于各參數(shù)的詳細(xì)介紹請參考:
* https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
*/
tinkerPatch {
tinkerEnable = true
ignoreWarning = false
useSign = true
dex {
dexMode = "jar"
pattern = ["classes*.dex"]
loader = []
}
lib {
pattern = ["lib/*/*.so"]
}
res {
pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
ignoreChange = []
largeModSize = 100
}
packageConfig {
}
sevenZip {
zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
// path = "/usr/local/bin/7za"
}
buildConfig {
keepDexApply = false
// tinkerId = "base-2.0.1"
}
}
然后需要在Manifest配置文件配置如下
<activity
android:name="com.tencent.bugly.beta.ui.BetaActivity"
android:configChanges="keyboardHidden|orientation|screenSize|locale"
android:theme="@android:style/Theme.Translucent" />
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
最后在Application中初始化bugly
public class App extends BaseApplication {
@Override
public void onCreate() {
super.onCreate();
setStrictMode();
// 設(shè)置是否開啟熱更新能力,默認(rèn)為true
Beta.enableHotfix = true;
// 設(shè)置是否自動(dòng)下載補(bǔ)丁
Beta.canAutoDownloadPatch = true;
// 設(shè)置是否提示用戶重啟
Beta.canNotifyUserRestart = true;
// 設(shè)置是否自動(dòng)合成補(bǔ)丁
Beta.canAutoPatch = true;
/**
* 全量升級狀態(tài)回調(diào)
*/
Beta.upgradeStateListener = new UpgradeStateListener() {
@Override
public void onUpgradeFailed(boolean b) {
}
@Override
public void onUpgradeSuccess(boolean b) {
}
@Override
public void onUpgradeNoVersion(boolean b) {
Toast.makeText(getApplicationContext(), "最新版本", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrading(boolean b) {
Toast.makeText(getApplicationContext(), "onUpgrading", Toast.LENGTH_SHORT).show();
}
@Override
public void onDownloadCompleted(boolean b) {
}
};
/**
* 補(bǔ)丁回調(diào)接口,可以監(jiān)聽補(bǔ)丁接收、下載、合成的回調(diào)
*/
Beta.betaPatchListener = new BetaPatchListener() {
@Override
public void onPatchReceived(String patchFileUrl) {
Toast.makeText(getApplicationContext(), patchFileUrl, Toast.LENGTH_SHORT).show();
}
@Override
public void onDownloadReceived(long savedLength, long totalLength) {
Toast.makeText(getApplicationContext(), String.format(Locale.getDefault(),
"%s %d%%",
Beta.strNotificationDownloading,
(int) (totalLength == 0 ? 0 : savedLength * 100 / totalLength)), Toast.LENGTH_SHORT).show();
}
@Override
public void onDownloadSuccess(String patchFilePath) {
Toast.makeText(getApplicationContext(), patchFilePath, Toast.LENGTH_SHORT).show();
// Beta.applyDownloadedPatch();
}
@Override
public void onDownloadFailure(String msg) {
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
}
@Override
public void onApplySuccess(String msg) {
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
}
@Override
public void onApplyFailure(String msg) {
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
}
@Override
public void onPatchRollback() {
Toast.makeText(getApplicationContext(), "onPatchRollback", Toast.LENGTH_SHORT).show();
}
};
long start = System.currentTimeMillis();
// 這里實(shí)現(xiàn)SDK初始化,appId替換成你的在Bugly平臺(tái)申請的appId,調(diào)試時(shí)將第三個(gè)參數(shù)設(shè)置為true
Bugly.init(this, "2e5309db50", true);
long end = System.currentTimeMillis();
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// you must install multiDex whatever tinker is installed!
MultiDex.install(base);
// 安裝tinker
Beta.installTinker();
}
@TargetApi(9)
protected void setStrictMode() {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().permitAll().build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
}
}
10.結(jié)束語
該組件框架是自己在暑假實(shí)習(xí)期間做的,由于實(shí)習(xí)公司的項(xiàng)目過于龐大和復(fù)雜,每次編譯都需要花費(fèi)10幾分鐘,心都碎了,所以才想嘗試下組件化框架,摸索了很長時(shí)間,最后還是做出來了,大概花費(fèi)2個(gè)多月的時(shí)間,由于最近項(xiàng)目上比較忙,所以沒什么時(shí)間來完善,界面有點(diǎn)簡陋,但邏輯基本實(shí)現(xiàn)了。歡迎fork and star。一起探討技術(shù)。
github上地址: https://github.com/HelloChenJinJun/NewFastFrame