[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.mzph.cn/news/390521.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

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…

剑指 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…

剑指 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; 所有…

amazeui学习笔记--css(基本样式3)--文字排版Typography

amazeui学习笔记--css&#xff08;基本样式3&#xff09;--文字排版Typography 一、总结 1、字体&#xff1a;amaze默认非 衬线字体&#xff08;sans-serif&#xff09; 2、引用块blockquote和定义列表&#xff1a;引用块blockquote和定义列表&#xff08;dl dt&#xff09;注意…

ELK学习记录三 :elasticsearch、logstash及kibana的安装与配置(windows)

注意事项&#xff1a; 1.ELK版本要求5.X以上 2.Elasticsearch5.x版本必须基于jdk1.8&#xff0c;安装环境必须使用jdk1.8 3.操作系统windows10作为测试环境&#xff0c;其他环境命令有差异&#xff0c;请注意 4.本教程适合完全离线安装 5.windows版本ELK安装包下载路径&#xf…

【quickhybrid】JSBridge的实现

前言 本文介绍quick hybrid框架的核心JSBridge的实现 由于在最新版本中&#xff0c;已经没有考虑iOS7等低版本&#xff0c;因此在选用方案时没有采用url scheme方式&#xff0c;而是直接基于WKWebView实现 交互原理 具体H5和Native的交互原理可以参考前文的H5和Native交互原理 …

面试题 10.02. 变位词组

编写一种方法&#xff0c;对字符串数组进行排序&#xff0c;将所有变位词组合在一起。变位词是指字母相同&#xff0c;但排列不同的字符串。 注意&#xff1a;本题相对原题稍作修改 示例: 输入: [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”], 输出: [ [“ate”,…

智能合约设计模式

2019独角兽企业重金招聘Python工程师标准>>> 设计模式是许多开发场景中的首选解决方案&#xff0c;本文将介绍五种经典的智能合约设计模式并给出以太坊solidity实现代码&#xff1a;自毁合约、工厂合约、名称注册表、映射表迭代器和提款模式。 1、自毁合约 合约自毁…

「CodePlus 2017 12 月赛」火锅盛宴

n<100000种食物&#xff0c;给每个食物煮熟时间&#xff0c;有q<500000个操作&#xff1a;在某时刻插入某个食物&#xff1b;查询熟食中编号最小的并删除之&#xff1b;查询是否有编号为id的食物&#xff0c;如果有查询是否有编号为id的熟食&#xff0c;如果有熟食删除之…

5815. 扣分后的最大得分

给你一个 m x n 的整数矩阵 points &#xff08;下标从 0 开始&#xff09;。一开始你的得分为 0 &#xff0c;你想最大化从矩阵中得到的分数。 你的得分方式为&#xff1a;每一行 中选取一个格子&#xff0c;选中坐标为 (r, c) 的格子会给你的总得分 增加 points[r][c] 。 然…

您有一个上云锦囊尚未领取!

前期&#xff0c;我们通过文章《确认过眼神&#xff1f;上云之路需要遇上对的人&#xff01;》向大家详细介绍了阿里云咨询与设计场景下的五款专家服务产品&#xff0c;企业可以通过这些专家服务产品解决了上云前的痛点。那么&#xff0c;当完成上云前的可行性评估与方案设计后…

Python os.chdir() 方法

概述 os.chdir() 方法用于改变当前工作目录到指定的路径。 语法 chdir()方法语法格式如下&#xff1a; os.chdir(path) 参数 path -- 要切换到的新路径。 返回值 如果允许访问返回 True , 否则返回False。 实例 以下实例演示了 chdir() 方法的使用&#xff1a; #!/usr/bin/pyth…

More DETAILS! PBR的下一个发展在哪里?

最近几年图形学社区对PBR的关注非常高&#xff0c;也许是由于Disney以及一些游戏引擎大厂的助推&#xff0c;也许是因为它可以被轻松集成进实时渲染的游戏引擎当中&#xff0c;也许是因为许多人发现现在只需要调几个参数就能实现具有非常精细细节的表面着色了。反正现在网络上随…

sql server 2008 身份验证失败 18456

双击打开后加上 ;-m 然后以管理员方式 打开 SQLSERVER 2008 就可以已window身份登录 不过还没有完 右键 属性 》安全性 更改为 sql server 和 window身份验证模式 没有sql server登陆账号的话创建一个 然后把-m去掉就可以用帐号登录了 转载于:https://www.cnblogs.com/R…

Java逆向基础之AspectJ的获取成员变量的值

注意&#xff1a;由于JVM优化的原因&#xff0c;方法里面的局部变量是不能通过AspectJ拦截并获取其中的值的&#xff0c;但是成员变量可以在逆向中&#xff0c;我们经常要跟踪某些类的成员变量的值&#xff0c;这里以获取ZKM9中的qs类的成员变量g为例进行说明在StackOverFlow上…

Spark 键值对RDD操作

https://www.cnblogs.com/yongjian/p/6425772.html 概述 键值对RDD是Spark操作中最常用的RDD&#xff0c;它是很多程序的构成要素&#xff0c;因为他们提供了并行操作各个键或跨界点重新进行数据分组的操作接口。 创建 Spark中有许多中创建键值对RDD的方式&#xff0c;其中包括…

Python高级网络编程系列之基础篇

一、Socket简介 1、不同电脑上的进程如何通信&#xff1f; 进程间通信的首要问题是如何找到目标进程&#xff0c;也就是操作系统是如何唯一标识一个进程的&#xff01; 在一台电脑上是只通过进程号PID&#xff0c;但在网络中是行不通的&#xff0c;因为每台电脑的IP可能都是不一…