Android元件化開發實踐(八):元件生命週期如何實現自動註冊管理
1. 前言
前面有一章講過元件生命週期管理,參見 ofollow,noindex">Android元件化開發實踐(五):元件生命週期管理 。之前只是為了講解元件生命週期的概念,以及這樣做的原因,但是這樣實施過程中,會發現在殼工程裡會出現很多硬編碼,如果你引入的一個元件裡有實現BaseAppLike的類,那麼你就得在殼工程的Application.onCreate()方法裡手動例項化該類,如果你刪除一個類似的元件,同樣你也得刪除與之相應的程式碼。這顯然是不靈活的,因為這要求殼工程的維護者必須知道,該工程引入的元件裡有多少類是實現了BaseAppLike的,如果忘記一個或若干個,應用就可能出現問題。所以我們現在的目標就是,怎麼去自動識別所有元件的BaseAppLike類,增加或刪除元件時,不用修改任何程式碼。
2. 實現的思路
那麼應用執行時怎麼去識別所有實現了BaseAppLike的類,先講講我自己的思路,思路理清了之後我們再一步步去技術實現。
初步思路:
- 定義一個註解來標識實現了BaseAppLike的類。
- 通過APT技術,在元件編譯時掃描和處理前面定義的註解,生成一個BaseAppLike的代理類,姑且稱之為BaseAppLikeProxy,所有的代理類都在同一個包名下,這個包名下必須只包含代理類,且類名由固定的規則來生成,保證不同元件生成的代理類的類名不會重複。
- 需要有一個元件生命週期管理類,初始化時能掃描到固定包名下有多少個類檔案,以及類檔案的名稱,這個固定包名就是前面我們生成的BaseAppLikeProxy的包名,代理類都放在同一個包名下,是為了通過包名找到我們所有的目標類。
- 元件整合後在應用的Application.onCreate()方法裡,呼叫元件生命週期管理類的初始化方法。
- 元件生命週期管理類的內部,掃描到所有的BaseAppLikeProxy類名之後,通過反射進行類例項化。
初步技術難點:
- 需要了解APT技術,怎麼在編譯時動態生成java程式碼;
- 應用在執行時,怎麼能掃描到某個包名下有多少個class,以及他們的名稱呢;
更進一步的思考:
前面的思路里,應用在執行時,可能需要掃描所有的class,然後通過class檔案的包名來判斷是不是我們的目標類,但是我們的要用到的可能只有幾個,這顯然效率是不高的。能不能在執行時,不掃描所有class檔案,就已經知道了所有的BaseAppLikeProxy類名呢?首先想到的就是採用gradle外掛技術,在應用打包編譯時,動態插入位元組碼來實現。這裡又會碰到幾個技術難點:
- 怎麼製作gradle外掛。
- 怎麼在打包時動態插入位元組碼。
3. 從0開始實現
接下來我們按照步驟來一步步實現,碰到問題就解決問題,看怎麼來實現元件生命週期自動註冊管理。這裡面用到的技術會有:APT、groovy語言、gradle外掛技術、ASM動態生成位元組碼,平時我們開發應用時一般不需要了解這些,所以會有一定的難度。
3.1 註解定義
在Android Studio中,新建一個Java Library module,我命名為lifecycle-annotation,在該module中建立一個註解類,同時建立一個後面要生成代理類的相關配置,如下圖所示:

相關程式碼如下:
//註解類,只能用來對類進行註解 @Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) public @interface AppLifeCycle { } public class LifeCycleConfig { /** * 要生成的代理類的包名,該包名下不要有其他不相關的業務類 */ public static final String PROXY_CLASS_PACKAGE_NAME = "com.hm.iou.lifecycle.apt.proxy"; /** * 生成代理類統一的字尾 */ public static final String PROXY_CLASS_SUFFIX = "$$Proxy"; /** * 生成代理類統一的字首 */ public static final String PROXY_CLASS_PREFIX = "Heima$$"; }
3.2 重新定義IAppLike介面
新建一個Android Library module,命名為lifecycle-api,在這個module裡定義IAppLike介面,以及一個生命週期管理類。

為了生成代理類,我們這裡定義了一個介面IAppLike,元件只需實現該介面即可,同時定義了一個元件生命週期管理類AppLifeCycleManager,該類負責載入應用內所有實現了IAppLike的類。
public interface IAppLike { int MAX_PRIORITY = 10; int MIN_PRIORITY = 1; int NORM_PRIORITY = 5; int getPriority(); void onCreate(Context context); void onTerminate(); }
再來看看生命週期管理類,邏輯很簡單,通過一個List來儲存所有的IAppLike,初始化時會根據優先順序排序:
public class AppLifeCycleManager { private static List<IAppLike> APP_LIKE_LIST = new ArrayList<>(); /** * 註冊IAppLike類 */ public static void registerAppLike(IAppLike appLike) { APP_LIKE_LIST.add(appLike); } /** * 初始化,需要在Application.onCreate()裡呼叫 * * @param context */ public static void init(Context context) { Collections.sort(APP_LIKE_LIST, new AppLikeComparator()); for (IAppLike appLike : APP_LIKE_LIST) { appLike.onCreate(context); } } public static void terminate() { for (IAppLike appLike : APP_LIKE_LIST) { appLike.onTerminate(); } } /** * 優先順序比較器,優先順序大的排在前面 */ static class AppLikeComparator implements Comparator<IAppLike> { @Override public int compare(IAppLike o1, IAppLike o2) { int p1 = o1.getPriority(); int p2 = o2.getPriority(); return p2 - p1; } } }
3.3 使用APT來生成IAppLike的代理類
新建一個Java Library module,命名為lifecycle-apt,在該module裡實現我們自己的註解處理器。
在build.gradle裡修改配置為:
apply plugin: 'java-library' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) //這是谷歌提供的一個自動服務註冊框架,需要用到 implementation 'com.google.auto.service:auto-service:1.0-rc2' implementation project(':lifecycle-annotation') } sourceCompatibility = "1.7" targetCompatibility = "1.7"
接下來就是實現我們自己的註解處理器了,工程結構如下圖所示:

主要程式碼如下,程式碼邏輯加在註釋裡:
//核心的註解處理類,在這裡我們可以掃描原始碼裡所有的註解,找到我們需要的註解,然後做出相應處理 @AutoService(Processor.class) public class AppLikeProcessor extends AbstractProcessor { private Elements mElementUtils; private Map<String, AppLikeProxyClassCreator> mMap = new HashMap<>(); @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); mElementUtils = processingEnvironment.getElementUtils(); } /** * 返回該註解處理器要解析的註解 * * @return */ @Override public Set<String> getSupportedAnnotationTypes() { Set<String> set = new LinkedHashSet<>(); //返回註解類的全限定類名,我們這裡要識別的註解類是 AppLifeCycle set.add(AppLifeCycle.class.getCanonicalName()); return set; } //支援的原始碼 java 版本號 @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.RELEASE_7; } //所有邏輯都在這裡完成 @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { //這裡返回所有使用了 AppLifeCycle 註解的元素 Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(AppLifeCycle.class); mMap.clear(); //遍歷所有使用了該註解的元素 for (Element element : elements) { //如果該註解不是用在類上面,直接丟擲異常,該註解用在方法、欄位等上面,我們是不支援的 if (!element.getKind().isClass()) { throw new RuntimeException("Annotation AppLifeCycle can only be used in class."); } //強制轉換為TypeElement,也就是類元素,可以獲取使用該註解的類的相關資訊 TypeElement typeElement = (TypeElement) element; //這裡檢查一下,使用了該註解的類,同時必須要實現com.hm.lifecycle.api.IAppLike介面,否則會報錯,因為我們要實現一個代理類 List<? extends TypeMirror> mirrorList = typeElement.getInterfaces(); if (mirrorList.isEmpty()) { throw new RuntimeException(typeElement.getQualifiedName() + " must implements interface com.hm.lifecycle.api.IAppLike"); } boolean checkInterfaceFlag = false; for (TypeMirror mirror : mirrorList) { if ("com.hm.lifecycle.api.IAppLike".equals(mirror.toString())) { checkInterfaceFlag = true; } } if (!checkInterfaceFlag) { throw new RuntimeException(typeElement.getQualifiedName() + " must implements interface com.hm.lifecycle.api.IAppLike"); } //該類的全限定類名 String fullClassName = typeElement.getQualifiedName().toString(); if (!mMap.containsKey(fullClassName)) { System.out.println("process class name : " + fullClassName); //建立代理類生成器 AppLikeProxyClassCreator creator = new AppLikeProxyClassCreator(mElementUtils, typeElement); mMap.put(fullClassName, creator); } } System.out.println("start to generate proxy class code"); for (Map.Entry<String, AppLikeProxyClassCreator> entry : mMap.entrySet()) { String className = entry.getKey(); AppLikeProxyClassCreator creator = entry.getValue(); System.out.println("generate proxy class for " + className); //生成代理類,並寫入到檔案裡,生成邏輯都在AppLikeProxyClassCreator裡實現 try { JavaFileObject jfo = processingEnv.getFiler().createSourceFile(creator.getProxyClassFullName()); Writer writer = jfo.openWriter(); writer.write(creator.generateJavaCode()); writer.flush(); writer.close(); } catch (Exception e) { e.printStackTrace(); } } return true; } }
public class AppLikeProxyClassCreator { private Elements mElementUtils; private TypeElement mTypeElement; private String mProxyClassSimpleName; public AppLikeProxyClassCreator(Elements elements, TypeElement typeElement) { mElementUtils = elements; mTypeElement = typeElement; //代理類的名稱,用到了之前定義過的字首、字尾 mProxyClassSimpleName = LifeCycleConfig.PROXY_CLASS_PREFIX + mTypeElement.getSimpleName().toString() + LifeCycleConfig.PROXY_CLASS_SUFFIX; } /** * 獲取要生成的代理類的完整類名 * * @return */ public String getProxyClassFullName() { String name = LifeCycleConfig.PROXY_CLASS_PACKAGE_NAME + "."+ mProxyClassSimpleName; return name; } /** * 生成java程式碼,其實就是手動拼接,沒有什麼技術含量,比較繁瑣,且容易出錯 * 可以採用第三方框架javapoet來實現,看自己需求了 */ public String generateJavaCode() { StringBuilder sb = new StringBuilder(); //設定包名 sb.append("package ").append(LifeCycleConfig.PROXY_CLASS_PACKAGE_NAME).append(";\n\n"); //設定import部分 sb.append("import android.content.Context;\n"); sb.append("import com.hm.lifecycle.api.IAppLike;\n"); sb.append("import ").append(mTypeElement.getQualifiedName()).append(";\n\n"); sb.append("public class ").append(mProxyClassSimpleName) .append(" implements ").append("IAppLike ").append(" {\n\n"); //設定變數 sb.append("private ").append(mTypeElement.getSimpleName().toString()).append(" mAppLike;\n\n"); //建構函式 sb.append("public ").append(mProxyClassSimpleName).append("() {\n"); sb.append("mAppLike = new ").append(mTypeElement.getSimpleName().toString()).append("();\n"); sb.append("}\n\n"); //onCreate()方法 sb.append("public void onCreate(Context context) {\n"); sb.append("mAppLike.onCreate(context);\n"); sb.append("}\n\n"); //getPriority()方法 sb.append("public int getPriority() {\n"); sb.append("return mAppLike.getPriority();\n"); sb.append("}\n\n"); //onTerminate方法 sb.append("public void onTerminate() {\n"); sb.append("mAppLike.onTerminate();\n"); sb.append("}\n\n"); sb.append("\n}"); return sb.toString(); } }
那我們來實踐一下,看看效果如何。
在app module裡,建立2個類ModuleAAppLike、ModuleBAppLike,分別實現IAppLike介面,並採用AppLifeCycle註解。
在build.gradle裡增加依賴引用:
dependencies { //---------其他依賴------------ implementation project(':lifecycle-annotation') implementation project(':lifecycle-api') //需要注意這裡是使用 annotationProcessor,即我們剛定義的註解處理器 annotationProcessor project(':lifecycle-apt') }
//實現了IAppLike介面,並且採用了AppLifeCycle註解,二者缺一不可,否則APT處理時會報錯 @AppLifeCycle public class ModuleAAppLike implements IAppLike { @Override public int getPriority() { return NORM_PRIORITY; } @Override public void onCreate(Context context) { Log.d("AppLike", "onCreate(): this is in ModuleAAppLike."); } @Override public void onTerminate() { Log.d("AppLike", "onTerminate(): this is in ModuleAAppLike."); } }
將整個工程編譯一下,可以看到在build目錄下已經生成了我們定義的註解類,具體路徑如下所示:

看看程式碼是不是如我們所定義的一樣:
package com.hm.iou.lifecycle.apt.proxy; import android.content.Context; import com.hm.lifecycle.api.IAppLike; import com.hm.iou.lifecycle.demo.ModuleAAppLike; public class Heima$$ModuleAAppLike$$Proxy implements IAppLike{ private ModuleAAppLike mAppLike; public Heima$$ModuleAAppLike$$Proxy() { mAppLike = new ModuleAAppLike(); } public void onCreate(Context context) { mAppLike.onCreate(context); } public int getPriority() { return mAppLike.getPriority(); } public void onTerminate() { mAppLike.onTerminate(); } }
關於APT技術,我這裡不詳解了,不瞭解的需要自行搜尋相關資料來學習。
3.4 掃描固定包下面的所有class
現在終於到了比較關鍵的一步了,在元件化開發過程中,如果有十多個元件裡都有實現IAppLike介面的類,最終我們也會生成10多個代理類,這些代理類都是在同一個包下面。在元件整合到一個工程後,實際上只有一個apk安裝包,所有編譯後的class檔案都被打包到dex檔案裡,應用執行時實際上是dalvik虛擬機器(或者是ART)從dex檔案里加載出class資訊來執行的。
所以我們的思路是,執行時讀取手機裡的dex檔案,從中讀取出所有的class檔名,根據我們前面定義的代理類包名,來判斷是不是我們的目標類,這樣掃描一遍之後,就得到了固定包名下面所有類的類名了。具體實現,我採用了 Arouter 框架裡的程式碼,節選出部分核心程式碼說下:
public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException { final Set<String> classNames = new HashSet<>(); //獲取所有的class原始檔,通常為classes.dex檔案 List<String> paths = getSourcePaths(context); final CountDownLatch parserCtl = new CountDownLatch(paths.size()); for (final String path : paths) { //如果有多個dex檔案,我們開啟多個執行緒併發掃描 DefaultPoolExecutor.getInstance().execute(new Runnable() { @Override public void run() { DexFile dexfile = null; try { if (path.endsWith(EXTRACTED_SUFFIX)) { //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache" dexfile = DexFile.loadDex(path, path + ".tmp", 0); } else { dexfile = new DexFile(path); } Enumeration<String> dexEntries = dexfile.entries(); while (dexEntries.hasMoreElements()) { //遍歷讀取出所有的class名稱,類的全限定名稱 String className = dexEntries.nextElement(); //如果以我們指定的包名開頭,則表示是我們的目標類 if (className.startsWith(packageName)) { classNames.add(className); } } } catch (Throwable ignore) { } finally { if (null != dexfile) { try { dexfile.close(); } catch (Throwable ignore) { } } parserCtl.countDown(); } } }); } parserCtl.await(); return classNames; }
接下來,我們看看效果如何,修改 AppLifeCycleManager 類,在初始化時,增加掃描class的邏輯,主要程式碼邏輯如下:
private static void scanClassFile(Context context) { try { //掃描到所有的目標類 Set<String> set = ClassUtils.getFileNameByPackageName(context, LifeCycleConfig.PROXY_CLASS_PACKAGE_NAME); if (set != null) { for (String className : set) { try { //通過反射來載入例項化所有的代理類 Object obj = Class.forName(className).newInstance(); if (obj instanceof IAppLike) { APP_LIKE_LIST.add((IAppLike) obj); } } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } catch (Exception e) { e.printStackTrace(); } } public static void init(Context context) { scanClassFile(context); Collections.sort(APP_LIKE_LIST, new AppLikeComparator()); for (IAppLike appLike : APP_LIKE_LIST) { appLike.onCreate(context); } }
到這裡基本的功能已經實現了,我們可以自動載入註冊所有元件的IAppLike類了,但是這裡有個明顯的效能問題,需要掃描dex檔案裡的所有class,通常一個安裝包裡,加上第三方庫,class檔案可能數以千計、數以萬計,這讓人有點殺雞用牛刀的感覺。
每次應用冷啟動時,都要讀取一次dex檔案並掃描全部class,這個效能損耗是很大的,我們可以做點優化,在掃描成功後將結果快取下來,下次進來時直接讀取快取檔案。
3.5 通過gradle外掛來動態插入位元組碼
前面介紹到的方法,不管怎樣都需要在執行時讀取dex檔案,全量掃描所有的class。那麼我們能不能在應用編譯成apk時,就已經全量掃描過一次所有的class,並提取出所有實現了IAppLike介面的代理類呢,這樣在應用執行時,效率就大大提升了。答案是肯定的,這就是gradle外掛、動態插入java位元組碼技術。
關於gradle外掛技術,具體實現請接著看下一章。