preface
The componentization case mentioned in this article is based on their own open source componentization framework project github address github.com/HelloChenJi… Where the instant Messaging (Chat) module is a separate project at github address github.com/HelloChenJi…
1. What is componentization?
When the project develops to a certain stage, with the increase of requirements and frequent changes, the project will become bigger and bigger, the code will become more and more bloated, the coupling will become more and more, and the development efficiency will also be reduced. At this time, we need to reconstruct the old project, namely the separation of modules, the official word is componentization.
2. Why componentization and the benefits of componentization?
1. When the amount of code in Android projects reaches a certain level, compiling will be a very painful task, usually requiring 5 to 6 minutes of variation. Android Studio’s Launch of Instant Run is generally shut down due to various bugs and limitations (such as the use of hotfix Tinker). The componentized framework can make the module compile and debug separately, which can effectively reduce the compile time. 2. Parallel development can be better carried out through componentization, because we can carry out separate version control for each module, and even the person in charge of each module can choose his own design architecture without affecting the development of other modules. At the same time, componentization can also avoid the cross dependence between modules. Developers of each module can test, compile and run their own modules independently, and even implement separate deployments. Thus, the efficiency of parallel development is greatly improved.
3. Basic framework of componentization
4. Concrete implementation of componentization framework
4.1. Base class library encapsulation
4.2 Implementation of switch between component mode and integration mode
Build. gradle file under the music component, similar to other components.
// Control component patterns and integration patternsif (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) {// Set applicationId applicationId in component mode"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) {// Configure Arouter in integration mode, For the realization of communication between components javaCompileOptions {annotationProcessorOptions {the arguments = [moduleName: project.getName()] } } } } buildTypes { release { minifyEnabledfalse
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
sourceSets {main {// Controls the configuration of resources and code in both modesif (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 as a color picker'com. Afollestad. Material - dialogs: Commons: 0.9.1.0'
apt rootProject.ext.dependencies.dagger2_compiler
if(! RootProject. Ext isAlone) {/ / integration mode need to route traffic compiler generates code apt rootProject. Ext dependencies. Arouter_compiler}testCompile 'junit: junit: 4.12'
}Copy the code
Integration patterns
1. You need to set isAlone=false in config,gradle files
ext {
isAlone = false; //false: exists as a Lib component,true: exists as an applicationCopy the code
2. Sync. 3. Finally, select app to run.
The component patterns
1. You need to set isAlone=true in config,gradle files
ext {
isAlone = true; //false: exists as a Lib component,true: exists as an applicationCopy the code
2. Sync. 3. Finally, the corresponding modules (New, Chat, Live, Music, app) can be run.
4.3 Third-party open source library and component version management
Configuration of config.gradle files
ext {
isAlone = false; //false: exists as an integration pattern,trueAndroid = [compileSdkVersion: 24, buildToolsVersion:"25.0.2",
minSdkVersion : 16,
targetSdkVersion : 22,
versionCode : 1,
versionName : '1.0.0',] libsVersion = [// Manage the third-party library version number 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"] // Compatv7 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"// Route communication arouter_api:"com.alibaba:arouter-api:$rootProject.arouter_api",
arouter_compiler : "com.alibaba:arouter-compiler:$rootProject.arouter_compiler"]}Copy the code
4.4. Communication between components
The communication between components is realized by using Ali open source Arouter routing communication. Github address: github.com/alibaba/ARo… Initialize component communication data in App project
private List<MainItemBean> getDefaultData() {
List<MainItemBean> result=new ArrayList<>();
MainItemBean mainItemBean=new MainItemBean();
mainItemBean.setName("Campus");
mainItemBean.setPath("/news/main");
mainItemBean.setResId(R.mipmap.ic_launcher);
MainItemBean music=new MainItemBean();
music.setName("Music");
music.setResId(R.mipmap.ic_launcher);
music.setPath("/music/main");
MainItemBean live=new MainItemBean();
live.setName("Live");
live.setResId(R.mipmap.ic_launcher);
live.setPath("/live/main");
MainItemBean chat=new MainItemBean();
chat.setName("Chat");
chat.setPath("/chat/splash");
chat.setResId(R.mipmap.ic_launcher);
result.add(mainItemBean);
result.add(music);
result.add(live);
result.add(chat);
return result;
}Copy the code
It then launches the component interface jump when setting click events for each item.
@Override
public void onItemClick(int position, View view) {
MainItemBean item=mainAdapter.getData(position);
ARouter.getInstance().build(item.getPath()).navigation();
}Copy the code
Settings for each component entry interface (e.g. Live component, similar to other components)
@Route(path = "/live/main")
public class MainActivity extends BaseActivity<List<CategoryLiveBean>, MainPresenter> implements View.OnClickListener {Copy the code
5. Problems with RES resources and AndroidManifest configuration when components merge
We dynamically set the location of the project RES resource and Manifest, as well as the code, by determining which mode the component is in. Take the live component as an example. The other components are similar.
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'}}}Copy the code
6. Implement global application of components and initialize data
The Application of each component is initialized in a manner similar to the initial configuration of Glide in the Manifest. Take the livestreaming component as an example, and others are similar.
In BaseApplication, initialize the ApplicationDelegate proxy class
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
applicationDelegate = new ApplicationDelegate();
applicationDelegate.attachBaseContext(base);
MultiDex.install(this);
}Copy the code
What’s inside the ApplicationDelegate? Keep reading
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) {// Initialize the Manifest parser, Application ManifestParser = New ManifestParser(base); list = manifestParser.parse(); // After parsing the resulting list of component applications, inject each component Application with a context and lifecycle callback to synchronize the Applicationif(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) {// Call the onCreate method of the component Application proxy class accordinglyif(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) {// Call the onTerminate method of the component Application proxy classif(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); }}}}Copy the code
Global configuration of the application in the Manifest of the component
<meta-data
android:name="com.example.live.LiveApplication"
android:value="IModuleConfig" />Copy the code
ManifestParser parses meta-data whose value is IModuleConfig and generates an instance through reflection.
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 ()) {// Meta-data with value IModuleConfig is parsed and an instance is generated by reflectionif (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);
}
returnmodules; 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;
}Copy the code
This allows you to configure your own component’s Application in the Manifest file to initialize data within the component, such as the global configuration of the Dagger initialization in the live component
public class LiveApplication implements IModuleConfig,IAppLife { private static MainComponent mainComponent; @Override public void injectAppLifecycle(Context context, List<IAppLife> iAppLifes) {// We need to add this reference to the Application lifecycle callback to implement the callback 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) {// Initialize the Dagger mainComponent= DaggerMainComponent.builder().mainModule(new MainModule()).appComponent(BaseApplication.getAppComponent()).build(); } @Override public void onTerminate(Application application) {if(mainComponent ! = null) { mainComponent = null; } } public static MainComponentgetMainComponent() {
returnmainComponent; }}Copy the code
7. Implementation of network requests and interceptors within components
Since each component’s BaseUrl, network configuration, etc. may be different, each component can implement its own network request and interceptor in the MainConponent of its configured dagger. Take the live component for example, and the others are similar. MainComponent
@PerApplication
@Component(dependencies = AppComponent.class, modules = MainModule.class)
public interface MainComponent {
public DaoSession getDaoSession();
public MainRepositoryManager getMainRepositoryManager();
}Copy the code
MainModule code
@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() {returnnew LiveInterceptor(); }}Copy the code
8. Technical difficulties of componentization implementation
8.1. Implementation of Greendao database
Greendao database initialization code in the base class library’s 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();
}Copy the code
DaoMaster is generated by APT. Since DaoMaster is used by global components, greendao database can only be placed in the base class library, and entity class beans of each component can only be created in the base class library, distinguished by subcontracting names, as shown in the following figure. This is because creating a bean within a component regenerates another copy of the DaoMaster and does not control the database entities of other components, which is very limited.
8.2. Resource naming conflicts
Build. Gradle file name prefix = moudLE_prefix = moudle_prefix; gradle file name prefix = moudle_prefix = moudle_prefix; So image resources still need to be manually changed to the resource name. It is not recommended to use this method to resolve resource name conflicts. So be careful and try not to duplicate resources when you create them.
resourcePrefix "moudle_prefix"Copy the code
8.3. Why butterKnife cannot be used
Although Butterknife can be used in lib, the condition is that R2 is used instead of R. When switching between component mode and integration mode, switching between R2<->R cannot be completed, and it is very troublesome to change the whole body once switching! Therefore, the use of Butterknife in componentization is not recommended.
8.4. Library duplication dependency problem
1. You might think that every component depends on the base class library. There is no such problem because Gradle automatically excludes duplicate ARR packages during APP building, so there is no duplicate dependency on the base library. 2. However, third-party open source libraries may rely on packages that duplicate our own, so we need to eliminate unnecessary packages. Build. Gradle in CommonLibrary
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
}Copy the code
9. Seamless connection of componentization and thermal repair
This open source project is based on Tencent’s Bugly platform for monitoring abnormal information, hot repair and application upgrade. Build. Gradle in the root directory of the project
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "Com. Tencent. Bugly: tinker - support: 1.0.8." "}}Copy the code
Then do the following configuration in your App’s 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'// Compile the bugly related SDK'com. Tencent. Bugly: crashreport_upgrade: 1.3.1'
compile 'com.tencent.bugly:nativecrashreport:latest.release'
}
apply from: 'tinker-support.gradle'Copy the code
It then relies on the plug-in script within it
apply from: 'tinker-support.gradle'Copy the code
The tinker-support.gradle file is as follows:
apply plugin: 'com.tencent.bugly.tinker-support'
def bakPath = file("${buildDir}/bakApk/") /** * Enter the base package directory generated by each build */ def baseApkDir ="app-0831-17-50-44"/** * For details about plug-in parameters, see */ tinkerSupport {// Enable tinker-support. The default value is tinker-supporttrue
enable = true// Automatically generates tinkerId, you do not need to worry about tinkerId, default is tinkerIdfalse
autoGenerateTinkerId = true// Specify the archive directory. The default is the current module subdirectory tinker autoBackupApkDir ="${bakPath}"// Whether to enable overwriting tinkerPatch configuration. The default valuefalse/ / open tinkerPatch configuration is not effective after that without adding tinkerPatch overrideTinkerPatchConfiguration =trueWhen compiling the patch package, you must specify the apK of the baseline version. The default value is null. If this value is null, it indicates that the patch package is not compiled"${bakPath}/${baseApkDir}/app-release.apk"// Corresponding tinker plug-in applyMapping baseApkProguardMapping ="${bakPath}/${baseApkDir}/app-release-mapping.txt"// applyResourceMapping baseApkResourceMapping ="${bakPath}/${baseApkDir}/app-release-R.txt"TinkerId = tinkerId = tinkerId = tinkerId = tinkerId = tinkerId ="1.0.5 - base_patch"// buildAllFlavorsDir ="${bakPath}/${baseApkDir}"// Whether to use hardening mode. The default value is hardening modefalse
// isProtectedApp = true// Whether to integrate with reflection Application without modifying ApplicationenableProxyApplication = true} /** * Generally speaking, we do not need to make any changes to the following parameters * For detailed description of each parameter please refer to: * 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." "}}Copy the code
You then need to configure the following in the Manifest configuration file
<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>Copy the code
Finally, initialize bugly in the Application
public class App extends BaseApplication {
@Override
public void onCreate() {
super.onCreate();
setStrictMode(); // Set whether to enable hot updatetrue
Beta.enableHotfix = true; / / Settings are automatically download patch Beta. CanAutoDownloadPatch =true; / / set whether to prompt the user to restart the Beta. CanNotifyUserRestart =true; // Set whether to automatically compose patches beta.canautopatch =true; / * * * all state correction * / upgrade to Beta. UpgradeStateListener = newUpgradeStateListener() {
@Override
public void onUpgradeFailed(boolean b) {
}
@Override
public void onUpgradeSuccess(boolean b) {
}
@Override
public void onUpgradeNoVersion(boolean b) {
Toast.makeText(getApplicationContext(), "Latest version", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrading(boolean b) {
Toast.makeText(getApplicationContext(), "onUpgrading", Toast.LENGTH_SHORT).show(); } @Override public void onDownloadCompleted(boolean b) { } }; /** * Patch callback interface, can listen for patch received, downloaded, synthesized callback */ beta. betaPatchListener = newBetaPatchListener() {
@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(); // Implement SDK initialization here, replace the appId with the appId you applied for in Bugly platform, and set the third parameter totrue
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); // installTinker beta.installtinker (); } @TargetApi(9) protected voidsetStrictMode() { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().permitAll().build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build()); }}Copy the code
10. Reference items
MVPArms github.com/JessYanCodi… Live nationwide github.com/jenly1314/K… Music project github.com/hefuyicoder… Github.com/aa112901/re… Elephant: PHPHub client github.com/Freelander/… MvpApp github.com/Rukey7/MvpA… CloudReader github.com/youlookwhat… Many thanks to the authors of the above open source project! Thank you very much!
11. The conclusion
The component framework is made of yourself during the summer internship, as a result of the internship company project is too big and complex, and every time you compile it takes 10 minutes, the heart is broken, so we want to try the componentized framework, groping for a long time, finally did out, probably spent more than two months, because recently a bit busy, so it’s not the time to perfect, The interface is a bit crude, but the logic is basically there. Welcome to Fork and Star. Students who are interested in componentized framework can add myself QQ1981367757 to discuss the technology together. On Github: github.com/HelloChenJi…