Simple example of Android component development

Android componentized sample code github address: https://github.com/respost/ModuleDemo

1. Componentized initial model

1. Understand componentization through a simple initial architecture diagram of the android project, as shown below:

To make a vivid analogy, consider our APP as a computer host, then the app shell is the host shell, the main component is the motherboard, and the other components are similar to hard disks, network cards, graphics cards and the like. Each component is connected to the motherboard. Then it is installed in the mainframe and displayed as a complete computer mainframe. 

2. The app shell and main component are an essential part of our app. Together they form a complete app that can be released to the outside world. Other components can be integrated or not. It will only increase or decrease the functions of our app, but not Affect the final release of our app.

Two, create a Module module (component)

In our actual project, the effect displayed by the component is probably like this:

1. We start to create the Module module, right click on the project’s APP → New → Module

2. Choose  Android Library  → Next

3. Enter the module name → Finish

Three, the build.gradle file description of the component

1. Through the creation of the above Module module, there are 5 components in total, including the main component, so there are 5 component build.gradle files, as shown below:

2. In the gradle file of the main component, apply plugin uses com.android.application

3. For other business modules (component A, component B, component C, common component, etc.), apply plugin uses com.android.library 

Fourth, component integration

After the components are established, the components can be integrated into the main component. The integration is very simple. Just add the dependencies{} configuration in the gradle file of the main component and add the following statement:

dependencies {
     ...

    //集成组件A
    implementation project(':modulea')
    //集成组件B
    implementation project(':moduleb')
    //集成组件C
    implementation project(':modulec')
}

As shown below: 

Five, component resource sharing

1. In the build.gradle file of the common component, add the android  configuration as follows:

android {

    //省略前面的代码...

    repositories {
        flatDir {
            dirs 'libs'
        }
    }
}

In the build.gradle file of each component that needs to call the common common component, also add the android  configuration, as follows:

android {
   
     //省略前面的代码..

    repositories {
        flatDir {
            dirs '../common/libs/', 'libs'
        }
    }

}

2. When introducing various libraries in the common component, api must be used instead of implementation . The reason:

The dependency of implementation compilation only acts on the current module, that is, the tripartite library compiled with implementation in the common component module only works on the common module, and the tripartite library cannot be used in the main component module.

 

3. Regarding component resource sharing, a simple example: For example, pictures are stored in the res of the common common component, so how to use it in component A, component B, and component C?

The method of use is as follows:

  • Open the build.gradle file of each component and add the following code in dependencies{}:
dependencies {
    
    ...

    implementation project(':common')
}
  • In this way, the image resource of the common component can be called in component A

4. In the same way, the color codes of component A, component B, and component C can also directly call the colors.xml code in the common common component

5. We can put other third-party libraries, custom views, tool classes, and public resources into the common common component, which means that the class libraries introduced in build.gradle in component A, component B, and component C can all be Put it in the dependencies{} in the common component

6. After simplifying the dependencies{} configuration of the build.gradle file in each business component, it becomes the following:

7. Through the above explanation, everyone should understand that pictures and xml (various xml files in the value directory) can be placed in the common common component and then referenced by other components. For the globally shared style.xml file, we should put it in the common component. For example, our project theme is originally placed in the style of the main component. We can move it to common so that other components can be debugged. As a separate project, it can have the same theme as the main project. All in all, all resources that you think can be shared by various components can be placed in the common component.

Six, add Fragment to the component

1. Take component A as an example, add a package fragment to component A

2. Right click on the fragment package → New → Fragment → Fragment (Blank)

3. Fill in the name of the Fragment and check to create an xml file, as follows:

4. The corresponding fragment_module_a.xml file code:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        android:src="@drawable/a" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="5dp"
        android:text="组件A"
        android:background="@color/green"
        android:textColor="@android:color/white"
        android:textSize="24dp" />

</FrameLayout>

5. Other components are similar to component A. Create a Fragment and add different background pictures.

6. Add navigation and Fragment containers to the main component. The code of activity_main.xml in the main component is as follows:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/container"
    android:orientation="vertical">

    <FrameLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">
    </FrameLayout>

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:background="?android:attr/windowBackground"
        app:menu="@menu/navigation" />
</LinearLayout>

7. Create a menu directory under res and add a navigation.xml file in it, the code is as follows:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/navigation_a"
        android:icon="@drawable/home"
        android:title="组件A" />
    <item
        android:id="@+id/navigation_b"
        android:icon="@drawable/video"
        android:title="组件B" />
    <item
        android:id="@+id/navigation_c"
        android:icon="@drawable/me"
        android:title="组件C" />
</menu>

 The icon images called in navigation.xml are placed in the drawable and drawable-24 directories respectively, and the final MainActivity interface of the main APP is as follows:

8. The code of MainActivity.java is as follows:

package net.zy13.module.demo;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;

import android.os.Bundle;
import android.view.MenuItem;

import com.google.android.material.bottomnavigation.BottomNavigationView;

import net.zy13.module.modulea.fragment.ModuleAFragment;
import net.zy13.module.moduleb.fragment.ModuleBFragment;
import net.zy13.module.modulec.fragment.ModuleCFragment;

public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener {

    //定义碎片集合
    private Fragment[] fragments = new Fragment[3];
    //当前显示的fragment的索引位置
    private int currentIndex = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initFragment();
        BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
        navigation.setOnNavigationItemSelectedListener(this);
    }

    /**
     * 初始化Fragment碎片
     */
    private void initFragment() {
        if (fragments[0] == null) {
            fragments[0] = new ModuleAFragment();
            getSupportFragmentManager().beginTransaction().add(R.id.content, fragments[0], "moduleA").commit();
        } else {
            getSupportFragmentManager().beginTransaction().show(fragments[0]);
        }
    }

    /**
     * 导航选择事件
     * @param item
     * @return
     */
    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case R.id.navigation_a:
                if (currentIndex == 0) return true;//如果已经是当前的fragment,不用切换
                FragmentTransaction transition0 = getSupportFragmentManager().beginTransaction();
                hideAndShow(0,transition0);
                return true;

            case R.id.navigation_b:
                if (currentIndex == 1) return true;//如果已经是当前的fragment,不用切换
                FragmentTransaction transition1 = getSupportFragmentManager().beginTransaction();
                if (fragments[1] == null) {
                    fragments[1] = new ModuleBFragment();
                    transition1.add(R.id.content, fragments[1], "moduleB");
                }
                hideAndShow(1,transition1);
                return true;

            case R.id.navigation_c:
                if (currentIndex == 2) return true;//如果已经是当前的fragment,不用切换
                FragmentTransaction transition2 = getSupportFragmentManager().beginTransaction();
                if (fragments[2] == null) {
                    fragments[2] = new ModuleCFragment();
                    transition2.add(R.id.content, fragments[2], "modulec");
                }
                hideAndShow(2,transition2);
                return true;
        }
        return false;
    }

    /**
     * 除了指定的fragment不hide,其他fragment全hide
     * @param expectIndex 指定的fragment在fragments中的位置
     * @param transition
     */
    private void hideAndShow(int expectIndex,FragmentTransaction transition) {
        for (int i = 0; i < fragments.length; i++) {
            if (i != expectIndex && fragments[i] != null) {
                transition.hide(fragments[i]);
            }
        }
        transition.show(fragments[expectIndex]);
        transition.commit();
        currentIndex = expectIndex;
    }
}

9. Run the project. The final effect is as shown in the figure below. Click the navigation button to switch to the corresponding component:

Seven, each component is developed separately (tested)

 Earlier we integrated the components into the main component. Now we split the components and develop them separately. After the development and testing are completed, the components are integrated into the main component and finally released. The method to develop components separately is: in the build.gradle file, change apply plugin:'com.android.library' to apply plugin:'com.android.application', that is, change its library mode to application mode , Because only application can run alone, library must rely on application to run.

So the question is coming?

When the component is developed separately, we need to rebuild the apply plugin mode of .gradle, and when it is integrated into the main component, we have to change it back again. If you change it manually like this, if there are many components, the modification will be troublesome and not elegant. The elegant solution is to set a switch. When it is turned on, it is in application mode and can be developed independently; when it is off, it is in library mode, which can be integrated into the main component. Now follow my steps below to achieve:

1. In the root directory of the project, there is a build.gradle file, add an ext {} configuration at the end of this file, and then set a constant isDebug in the ext configuration with the value set to true

ext {
    /**
     * 组件调试模式
     * isDebug = true 是组件模块,说明是单独的App
     * isDebug = false是集成模式,说明是依赖Lib
     * 每次更改“isDebug”的值后,需要点击 "Sync Project" 按钮
     *
     */
    isDebug = true
}

 

2. After the isDebug constant is set in build.gradle, other build.gradle files in our project can read this constant, so we can read the value of the constant in the build.gradle file of other components, Dynamically set apply plugin, the code is as follows:

if(isDebug){
    apply plugin: 'com.android.application'
}else{
    apply plugin: 'com.android.library'
}

3. After this setting, when we need to switch the mode, we only need to modify the value of the isDebug constant in the build.gradle file in the project root directory. After the modification is completed, click the Project sync button to synchronize. If there is an error, there is another place that needs to be modified, that is, the build.gradle file of the main component. We change the module mode to application. The main component cannot be imported into the application. If it is imported, an error will be reported, so it is debug debugging. In the mode, the component is not introduced here to avoid errors. So before integrating components, we must first determine what mode it is, as shown in the figure below:

4. Next, you have to modify AndroidManifest.xml. When a module is set to application, AndroidManifest.xml needs to include the attributes required by an app, such as the app’s icon, theme, launch Activity, and these attribute settings, and when the module is library When, these attributes are not needed, so when we are in different modes, the configuration of the AndroidManifest.xml file is also different. Methods as below:

(1), Android directory mode switch to Project directory mode

(2) Create a new debug directory in the src folder of each component, and then put the AndroidManifest.xml file we used for debugging into it

(3) The AndroidManifest.xml file used for debugging can be directly copied from the manifests directory, and then add the basic information of the application, as follows:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="net.zy13.module.modulea">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
    </application>
</manifest>

There will be a lot of error prompts in the above content. In fact, the prompt is nothing more than the resource cannot be found. Since we have created the common common component before, we only need to move the corresponding resource in the main component to the common component.

5. Next, in the build.gradle file of each component, specify the AndroidManifest.xml file used in different modes, and add the following code in android {}:

sourceSets {
        main {
            if (isDebug) {
                manifest.srcFile 'src/debug/AndroidManifest.xml'
            }else{
                manifest.srcFile 'src/main/AndroidManifest.xml'
                //集成开发模式下排除debug文件夹中的所有Java文件
                java {
                    exclude 'debug/**'
                }
            }
        }
}

6. After the above settings are completed and sync project (synchronization project), each component will have this directory structure:

7. Create a MainActivity activity for debugging startup in each component, and then load the fragment into activity_main.xml. The corresponding activity_main.xml layout file is as follows:

<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:name="net.zy13.module.modulea.fragment.ModuleAFragment"
android:layout_width="match_parent"
android:layout_height="match_parent">
</fragment>

8. After adding the MainActivity activity, we also need to manually set the activity as the entrance, open the AndroidManifest.xml file in the src/debug/ directory, and modify the MainActivity configuration as follows:

<activity
    android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

9. After the above steps are completed, the component can be developed and tested separately as an app. In the running app of android studio, there are several more runnable projects at the same time, as shown below:

8. Unified project version number

There are many version numbers in the build.gradle file of each component. In order to avoid having to modify multiple build.gradle files at the same time for each modification, and to avoid different versions of different components, which may cause conflicts, we can manage these version numbers in a unified manner as follows:

1. Define the version number constant in the build.gradle file under the project root directory

ext {
    /**
     * 组件调试模式
     * isDebug = true 是组件模块,说明是单独的App
     * isDebug = false是集成模式,说明是依赖Lib
     * 每次更改“isDebug”的值后,需要点击 "Sync Project" 按钮
     *
     */
    isDebug = true
    //版本号
    android = [
            compileSdkVersion: 30,
            buildToolsVersion: "30.0.0",
            minSdkVersion    : 15,
            targetSdkVersion : 30,
            versionCode      : 1,
            versionName      : "1.0"
    ]
}

2. Then make the following modifications in the build.gradle file of each component:

android {
    compileSdkVersion rootProject.ext.android.compileSdkVersion
    buildToolsVersion rootProject.ext.android.buildToolsVersion
    defaultConfig {
        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode rootProject.ext.android.versionCode
        versionName rootProject.ext.android.versionName
    }
}

Nine, use routing to achieve communication between components 

Through the previous study, we already know that the components can be split, but when they are integrated into the main component, a certain amount of communication is still required. For example, business A needs to call a page of business B or even pass parameters. However, in the "componentization" mode, business A and business B are completely separated. In the cognition of business A, there is no business B at all, and there is no way to call them directly. Of course, if business A and business B can communicate directly, they can introduce each other, but in this case, the project coupling is too high, the structure is chaotic, and all the advantages of "componentization" will be eliminated one by one, so we should Use another way to deal with it.

Here we need to introduce a concept: routing. Just like we actually access the network, the requests sent by our computer are forwarded by the router. In the "componentization", we can also set up such a transfer station to uniformly process the different components. Calling relationship. Regarding the usage of routing, I will add later, here is the simplest method to implement the call between components (page jump), the code is as follows:

try {
    Class c= Class.forName("net.zy13.module.modulea.MainActivity");
    Intent intent = new Intent(context,c);
    startActivity(intent);
} catch (ClassNotFoundException e) {
    Log.e("组件","组件未集成,无法跳转");
}

The above code is to jump through the complete class name. In debug mode, when calling other components, the corresponding component is not found, and no error is reported directly, but the prompt "Not integrated, cannot jump". We can write this method as a tool class and place it in the common component. The method is as follows:

1. Create a tool class PageUtils.java in the common component, the code is as follows:

import android.content.Context;
import android.content.Intent;
import android.util.Log;

/**
 * @author 安阳 QQ:15577969
 * @version 1.0
 * @team 美奇软件开发工作室
 * @date 2020/11/12 11:55
 */
public class PageUtils {
    /**
     * 页面跳转
     * @param context
     * @param className
     */
    public static void jump(Context context, String className){
        try {
            Class c = Class.forName(className);
            Intent intent = new Intent(context,c);
            context.startActivity(intent);
        } catch (ClassNotFoundException e) {
            Log.e("组件","未集成,无法跳转");
        }
    }

    /**
     * 页面跳转,可以传参,参数放在intent中,所以需要传入一个intent
     * @param context
     * @param className
     * @param intent
     */
    public static void jump(Context context,String className,Intent intent){
        try {
            Class c = Class.forName(className);
            intent.setClass(context,c);
            context.startActivity(intent);
        } catch (ClassNotFoundException e) {
            Log.e("组件","未集成,无法跳转");
        }
    }
}

2. In the place where you need to jump, directly use the following method to call:

PageUtils.jump(context,"net.zy13.module.modulea.MainActivity");

There is also a communication situation where the change of one component will affect the change of other components. For example, the user's login status (whether logged in) will affect the display status of some controls in other components. At this time, we can use the android broadcast mechanism or use EventBus to solve it. In actual development, if it is a change like this that will affect the overall situation, it should be placed in the common common component. The user's login situation affects the global existence, so you can define a user singleton in common, and other components use this singleton to control the display of its interface, especially after databingding is introduced, it is more convenient to operate. For the specific implementation, you can refer to other tutorials on Baidu, I will not implement it here.

Ten, android routing framework

Regarding the specific usage of the routing framework, I have compiled an article separately, and the sample code is also written in the componentized project.

Android routing framework usage: https://blog.csdn.net/qq15577969/article/details/109690111

Guess you like

Origin blog.csdn.net/qq15577969/article/details/109596071