新手避坑指南:Android元件化開發詳解
學習目標:
熟練使用元件化開發,路由配置
學習內容:
**在使用元件化開發前首先要明確專案整體框架,劃分模組及業務(重點),好的開始才會有好的結果。**模組劃分明確後開始配置Module。
如圖我們要完成以下功能:
1.點選商城進入ShoppingModule
2.點選登入進入LoginModule
3.點選賬單紅色區域展示賬單列表(其他Module中的Fragment)
(shareModule為公共模組)
根據業務需求建立如下:
在App的gradle.properties檔案下新增,用於控制module是否獨立執行。
#配置某個元件是否可以獨立執行 isShoppingRunAlone = true isLoginRunALone = true
然後配置App build.gradle。
apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 30 buildToolsVersion "29.0.3" defaultConfig { applicationId "com.example.moduledemo" minSdkVersion 16 targetSdkVersion 30 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.3.0' implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' //匯入公共模組 implementation project(':ShareModule') // 根據gradle中的配置來決定是否引用module if (!isLoginRunALone.toBoolean()){ implementation project(':LoginModule') } if (!isShoppingRunAlone.toBoolean()){ implementation project(':ShoppingModule') } }
繼續配置其他Module的build.gradle檔案。
if (isShoppingRunAlone.toBoolean()){ apply plugin: 'com.android.application' }else { apply plugin: 'com.android.library' } apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 30 buildToolsVersion "29.0.3" defaultConfig { minSdkVersion 16 targetSdkVersion 30 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } sourceSets{ main{ // 在獨立執行或者作為Libarary除錯時,使用不同的AndroidManifest.xml檔案 if (isShoppingRunAlone.toBoolean()){ manifest.srcFile 'src/main/manifest/AndroidManifest.xml' }else { manifest.srcFile 'src/main/AndroidManifest.xml' } } } } dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.3.0' implementation 'androidx.appcompat:appcompat:1.1.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' //匯入公共模組 implementation project(':ShareModule') }
在不同執行模式下使用不同的Manifest檔案。
需要在對應module的Main目錄下新建manifest資料夾(不然單獨執行會找不到Manifest檔案)。
單獨執行的manifest檔案設定如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.loginmodule">
<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">
<activity android:name=".LoginActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
併入主Module執行時manifest檔案設定如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.shoppingmodule">
<application>
<activity android:name=".ShoppingActivity"/>
</application>
</manifest>
全部配置完成之後,可以在gradle.properties中修改變數的值,編譯檢視配置是否正確,manifest檔案是否替換。執行檢視是否正常。
接下來開始配置路由。
好多人心中有疑惑,在引用Module之後是可以直接獲取到子Module的Activity的為什麼還要使用路由跳轉。是因為元件化開發是為了使單獨Module可以獨自編譯,執行如果主Module引用子Module的類名,當子Module單獨執行時主Module會編譯異常。
我們要知道一個專案不可能只有一個子Module,當我們其他子Module要進行相互跳轉時如何使用路由呢?所以我們要在ShareModule進行路由的配置,在之前的配置中我們將ShareModule匯入了每個Module。
第一步
我們建立對應Module的跳轉模板
import android.content.Context;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
public interface ILoginService {
void launch(Context ctx, String targetClass);
}
import android.content.Context;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
public interface IShoppingService {
void launch(Context ctx, String string);
Fragment newBillFragment(FragmentManager fragmentManager, int viewId, Bundle bundle);
}
第二步
在對應的moudle中實現跳轉邏輯及傳值操作
package com.example.loginmodule;
import android.content.Context;
import android.content.Intent;
import com.example.sharemodule.ILoginService;
public class LoginService implements ILoginService {
@Override
public void launch(Context ctx, String targetClass) {
Intent intent = new Intent(ctx, LoginActivity.class);
ctx.startActivity(intent);
}
}
package com.example.shoppingmodule;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.example.sharemodule.IShoppingService;
public class ShoppingService implements IShoppingService {
@Override
public void launch(Context ctx, String string) {
Intent intent = new Intent(ctx, ShoppingActivity.class);
ctx.startActivity(intent);
}
@Override
public Fragment newBillFragment(FragmentManager fragmentManager, int viewId, Bundle bundle) {
BillFragment fragment = new BillFragment();
fragment.setArguments(bundle);
fragmentManager.beginTransaction().replace(viewId, fragment).commit();
return fragment;
}
}
第三步
接下來我們建立一個ServiceFactory,為我們提供跳轉例項,並且處理單獨執行時可能會出現的異常
package com.example.sharemodule;
public class ServiceFactory {
private static final ServiceFactory instance = new ServiceFactory();
private ILoginService mLoginService;
private IShoppingService mShoppingService;
private ServiceFactory(){}
public static ServiceFactory getInstance() {
return instance;
}
public ILoginService getLoginService() {
if (mLoginService == null){
mLoginService = new EmptyLoginService();
}
return mLoginService;
}
public void setLoginService(ILoginService mLoginService) {
this.mLoginService = mLoginService;
}
public IShoppingService getSignService() {
if (mShoppingService == null){
mShoppingService = new EmptyShoppingService();
}
return mShoppingService;
}
public void setSignService(IShoppingService mSignService) {
this.mShoppingService = mSignService;
}
}
package com.example.mylibrarySharedLibrary;
import android.content.Context;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
public class EmptyLoginService implements ILoginService {
@Override
public void launch(Context ctx, String targetClass) {
}
@Override
public Fragment newUserInfoFragment(FragmentManager fragmentManager, int viewId, Bundle bundle) {
return null;
}
}
package com.example.mylibrarySharedLibrary;
import android.content.Context;
public class EmptySignService implements ISignService {
@Override
public void launch(Context ctx, String userId) {
}
}
這樣處理即使我們單獨執行主Moudle時也不會發生異常。
以上我們跳轉的程式碼就寫完了接下來就是對serviceFactory中
private ILoginService mLoginService;
private IShoppingService mSignService;
進行賦值
package com.example.sharemodule;
import android.app.Application;
public interface IComponentApplication {
void initialize(Application application);
}
提供統一初始化的介面
package com.example.moduledemo;
import android.app.Application;
import android.util.Log;
import com.example.sharemodule.AppConfig;
import com.example.sharemodule.IComponentApplication;
public class MainApplication extends Application implements IComponentApplication {
private static Application application;
public static Application getApplication(){
return application;
}
@Override
public void onCreate() {
super.onCreate();
initialize(this);
}
@Override
public void initialize(Application application) {
for (String cpnt : AppConfig.Components){
try{
Class<?> clz = Class.forName(cpnt);
Object obj = clz.newInstance();
if (obj instanceof IComponentApplication){
((IComponentApplication) obj).initialize(this);
}
}catch (Exception e){
Log.e("TAG", e.getMessage());
}
}
}
}
package com.example.loginmodule;
import android.app.Application;
import com.example.sharemodule.IComponentApplication;
import com.example.sharemodule.ServiceFactory;
public class LoginApplication extends Application implements IComponentApplication {
private static Application application;
public static Application getApplication(){
return application;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void initialize(Application app) {
application = app;
ServiceFactory.getInstance().setLoginService(new LoginService());
}
}
package com.example.shoppingmodule;
import android.app.Application;
import com.example.sharemodule.IComponentApplication;
import com.example.sharemodule.ServiceFactory;
public class ShoppingApplication extends Application implements IComponentApplication {
private static Application application;
public static Application getApplication() {
return application;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void initialize(Application app) {
application = app;
ServiceFactory.getInstance().setSignService(new SignService());
}
}
package com.example.sharemodule;
public class AppConfig {
public static final String[] Components = {
"com.example.shoppingmodule.ShoppingApplication",
"com.example.loginmodule.LoginApplication"
};
}
進入App時進行初始化,通過反射獲取子Module的Application例項進行初始化。
最終結果:
本文在開源專案:https://github.com/Android-Alvin/Android-LearningNotes中已收錄,裡面包含了Android元件化最全開源專案(美團App、得到App、支付寶App、微信App、蘑菇街App、有贊APP…)等,資源持續更新中…