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

3.1 Component frame diagram

3.2 Project structure diagram

4. Concrete implementation of componentization framework

4.1. Base class library encapsulation

4.1 Base class library diagram



































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.

Run. PNG

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.

Live Component Framework


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.

Base library component entity subcontracting diagram

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…