ARouter原理解析之仿ARouter自定義路由框架

ARouter是什么?

ARouter是阿里開源的一款android路由框架言询,幫助 Android App 進行組件化改造的路由框架 —— 支持模塊間的路由、通信夫啊、解耦;結(jié)合路由可以實現(xiàn)組件化辆憔。

ARouter接入指北

完整Arouter接入指南撇眯,ARouter重度用戶可以跳過熊榛,直接往后看

  • 第一步腕巡,根build.gradle設(shè)置使用arouter-register
apply plugin: 'com.alibaba.arouter'
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "com.alibaba:arouter-register:?"
    }
}
  • 第二步绘沉,創(chuàng)建baselib,并加入dependencies
api 'com.alibaba:arouter-api:x.x.x'
  • 第三步择懂,創(chuàng)建組件module困曙,例如login 或者setting 組件
android {
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
}

dependencies {
    // 替換成最新版本, 需要注意的是api
    // 要與compiler匹配使用日矫,均使用最新版可以保證兼容
    //compile 'com.alibaba:arouter-api:x.x.x' 此移動到baselib中
    api project(path: ':baselib')
    annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
    ...
}

  • 第四步哪轿,通過注解@Route 注冊頁面
// 在支持路由的頁面上添加注解(必選)
// 這里的路徑需要注意的是至少需要有兩級窃诉,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
    ...
}
  • 第五步,初始化
if (isDebug()) {           // 這兩行必須寫在init之前珊膜,否則這些配置在init過程中將無效
    ARouter.openLog();     // 打印日志
    ARouter.openDebug();   // 開啟調(diào)試模式(如果在InstantRun模式下運行车柠,必須開啟調(diào)試模式塑猖!線上版本需要關(guān)閉,否則有安全風(fēng)險)
}
ARouter.init(mApplication); // 盡可能早,推薦在Application中初始化
  • 第六步羊苟,使用ARouter
ARouter.getInstance().build("/test/activity").navigation();

ARouter比傳統(tǒng)Intent有哪些優(yōu)點

傳統(tǒng)intent的優(yōu)點

  • 輕量
  • 簡單

傳統(tǒng)intent的缺點

  • 跳轉(zhuǎn)過程無法控制,一旦調(diào)用了startActivity(Intent)便交由系統(tǒng)執(zhí)行令花,中間過程無法插手
  • 跳轉(zhuǎn)失敗無法捕獲兼都、降級俯抖,出現(xiàn)問題直接拋出異常
  • 顯示Intent中因為存在直接的類依賴關(guān)系瓦胎,導(dǎo)致耦合嚴重
startActivity(new Intent(MainActivity.this, LoginActivity.class));//強依賴LoginActivity
  • 隱式Intent中會出現(xiàn)規(guī)則集中式的管理搔啊,導(dǎo)致協(xié)作困難负芋,都需要在Manifest中進行配置,導(dǎo)致擴展性比較差
//隱式 比 顯式更強一點莽龟,可以在兩個無關(guān)子module 之間跳轉(zhuǎn)毯盈,由于顯式無法引入包搂赋,所以無法完成跳轉(zhuǎn)
Intent intent = new Intent();
intent.setClassName(MainActivity.this,"com.cnn.loginplugin.ui.login.LoginActivity");//設(shè)置包路徑
startActivity(intent);

ARouter優(yōu)點

  • 模塊間通信(后面講原理)
  • 支持url 跳轉(zhuǎn) build("/test/activity").navigation()
  • 支持攔截器
// 比較經(jīng)典的應(yīng)用就是在跳轉(zhuǎn)過程中處理登陸事件脑奠,這樣就不需要在目標頁重復(fù)做登陸檢查
// 攔截器會在跳轉(zhuǎn)之間執(zhí)行,多個攔截器會按優(yōu)先級順序依次執(zhí)行
@Interceptor(priority = 8, name = "測試用攔截器")
public class TestInterceptor implements IInterceptor {
    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {
    ...
    callback.onContinue(postcard);  // 處理完成轰豆,交還控制權(quán)
    // callback.onInterrupt(new RuntimeException("我覺得有點異常"));      // 覺得有問題秒咨,中斷路由流程

    // 以上兩種至少需要調(diào)用其中一種雨席,否則不會繼續(xù)路由
    }

    @Override
    public void init(Context context) {
    // 攔截器的初始化陡厘,會在sdk初始化的時候調(diào)用該方法特占,僅會調(diào)用一次
    }
}
  • 參數(shù)注入是目,@Autowired注解實現(xiàn)懊纳,更方便,需要配合ARouter.getInstance().inject(this);一起使用
      @Autowired
    public String name;
    @Autowired
    int age;
    // 通過name來映射URL中的不同參數(shù)
    @Autowired(name = "girl") 
    boolean boy;
    // 支持解析自定義對象冤今,URL中使用json傳遞
    @Autowired
    TestObj obj;
// 使用 withObject 傳遞 List 和 Map 的實現(xiàn)了
    // Serializable 接口的實現(xiàn)類(ArrayList/HashMap)
    // 的時候戏罢,接收該對象的地方不能標注具體的實現(xiàn)類類型
    // 應(yīng)僅標注為 List 或 Map龟糕,否則會影響序列化中類型
    // 的判斷, 其他類似情況需要同樣處理        
    @Autowired
    List<TestObj> list;
    @Autowired
    Map<String, List<TestObj>> map;
  • 支持外部url 跳轉(zhuǎn)
<activity android:name=".SchemeFilterActivity">
            <!-- Scheme -->
            <intent-filter>
                <data
                    android:host="www.nativie.com"
                    android:scheme="arouter"/>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="android.intent.category.BROWSABLE"/>
            </intent-filter>
</activity>
  • 簡單demo讲岁,github做簡單靜態(tài)界面服務(wù)器催首,并部署到https://oslanka.github.io/statichtml.github.io/郎任,手機瀏覽器打開备籽,并點擊href實現(xiàn)html打通原生车猬,按道理來說珠闰,所有未攔截的ARouter路徑伏嗜,均可被web瀏覽器跳轉(zhuǎn),html代碼如下:
<html>
<body>
<p><a >測試跳轉(zhuǎn)</a> </p>
<p><a href="arouter://www.nativie.com/login/login">跳轉(zhuǎn)登錄android-ARouter</a></p>
<p><a href="arouter://www.nativie.com/login/login?username=admin&password=123456">跳轉(zhuǎn)登錄android-ARouter 帶參數(shù)</a></p>
<p><a href="arouter://www.nativie.com/setting/setting">跳轉(zhuǎn)android-ARouter 設(shè)置界面</a></p>
<p><a href="arouter://www.nativie.com/web/web">跳轉(zhuǎn)android-ARouter 設(shè)置界面</a></p>
<p><a href="arouter://www.nativie.com/test/test">跳轉(zhuǎn)android-ARouter 錯誤路徑</a></p>
</body>
</html>

關(guān)于攔截器

  • 攔截器(攔截跳轉(zhuǎn)過程,面向切面編程)
  • 什么是面向切面編程AOP军熏?AOP為Aspect Oriented Programming的縮寫荡澎,意為:面向切面編程摩幔,通過預(yù)編譯方式和運行期間動態(tài)代理實現(xiàn)程序功能的統(tǒng)一維護的一種技術(shù)热鞍。AOP是OOP的延續(xù),是軟件開發(fā)中的一個熱點偷办,也是Spring框架中的一個重要內(nèi)容椒涯,是函數(shù)式編程的一種衍生范型废岂。利用AOP可以對業(yè)務(wù)邏輯的各個部分進行隔離湖苞,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性镐作,同時提高了開發(fā)的效率
// 攔截器會在跳轉(zhuǎn)之前執(zhí)行该贾,多個攔截器會按優(yōu)先級順序依次執(zhí)行
@Interceptor(priority = 8, name = "測試用攔截器")
public class TestInterceptor implements IInterceptor {
    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {
    ...
    callback.onContinue(postcard);  // 處理完成杨蛋,交還控制權(quán)
    // callback.onInterrupt(new RuntimeException("我覺得有點異常"));      // 覺得有問題六荒,中斷路由流程

    // 以上兩種至少需要調(diào)用其中一種掏击,否則不會繼續(xù)路由
    }

    @Override
    public void init(Context context) {
    // 攔截器的初始化秩铆,會在sdk初始化的時候調(diào)用該方法殴玛,僅會調(diào)用一次
    }
}

動態(tài)路由

  • 動態(tài)注冊路由信息 適用于部分插件化架構(gòu)的App以及需要動態(tài)注冊路由信息的場景滚粟,可以通過 ARouter 提供的接口實現(xiàn)動態(tài)注冊 路由信息凡壤,目標頁面和服務(wù)可以不標注 @Route 注解亚侠,注意:同一批次僅允許相同 group 的路由信息注冊
ARouter.getInstance().addRouteGroup(new IRouteGroup() {
        @Override
        public void loadInto(Map<String, RouteMeta> atlas) {
            atlas.put("/dynamic/activity",      // path
                RouteMeta.build(
                    RouteType.ACTIVITY,         // 路由信息
                    TestDynamicActivity.class,  // 目標的 Class
                    "/dynamic/activity",        // Path
                    "dynamic",                  // Group, 盡量保持和 path 的第一段相同
                    0,                          // 優(yōu)先級硝烂,暫未使用
                    0                           // Extra,用于給頁面打標
                )
            );
        }
    });

ARouter詳細API


// 構(gòu)建標準的路由請求除抛,并指定分組
ARouter.getInstance().build("/home/main", "ap").navigation();
// 構(gòu)建標準的路由請求母截,通過Uri直接解析
Uri uri;
ARouter.getInstance().build(uri).navigation();

// 構(gòu)建標準的路由請求微酬,startActivityForResult
// navigation的第一個參數(shù)必須是Activity颗管,第二個參數(shù)則是RequestCode
ARouter.getInstance().build("/home/main", "ap").navigation(this, 5);

// 指定Flag
ARouter.getInstance()
    .build("/home/main")
    .withFlags();
    .navigation();

// 獲取Fragment
Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
                    
// 對象傳遞
ARouter.getInstance()
    .withObject("key", new TestObj("Jack", "Rose"))
    .navigation();

// 使用綠色通道(跳過所有的攔截器)
ARouter.getInstance().build("/home/main").greenChannel().navigation();

原理探索

  • ARouter.init 時垦江,通過獲取/data/app/包名/base.apk來篩選出ARouter生成的類比吭,如下圖衩藤。
    image
  • 對于Activity類型赏表,跳轉(zhuǎn)ARouter.getInstance().build("/login/login").navigation();瓢剿,最終執(zhí)行的是间狂,如下:
**
     * Start activity
     *
     * @see ActivityCompat
     */
    private void startActivity(int requestCode, Context currentContext, Intent intent, Postcard postcard, NavigationCallback callback) {
        if (requestCode >= 0) {  // Need start for result
            if (currentContext instanceof Activity) {//啟動context 為Activity
                ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
            } else {
              // 啟動context 為Application 時鉴象,不支持requestCode
                logger.warning(Consts.TAG, "Must use [navigation(activity, ...)] to support [startActivityForResult]");
            }
        } else {//啟動context 為Application
            ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
        }

        if ((-1 != postcard.getEnterAnim() && -1 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
            ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
        }

        if (null != callback) { // Navigation over.
            callback.onArrival(postcard);
        }
    }
  • 兩個無關(guān)的module 如何跳轉(zhuǎn)的呢炼列?我們發(fā)現(xiàn)最終執(zhí)行startActivity時俭尖,所用的context為Application,思路是這樣的焰望,子module啟動另外無關(guān)子module時熊赖,將執(zhí)行權(quán)震鹉,交還給主進程/主程序去處理
image
  • 打開生成路由文檔,AROUTER_GENERATE_DOC="enable",會生成arouter-map-of-xx.json和3個java文件
// 更新 build.gradle, 添加參數(shù) AROUTER_GENERATE_DOC = enable
// 生成的文檔路徑 : build/generated/ap_generated_sources/(debug or release)/com/alibaba/android/arouter/docs/arouter-map-of-${moduleName}.json
android {
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName(), AROUTER_GENERATE_DOC: "enable"]
            }
        }
    }
}
//ARouter映射關(guān)系如何生成?Generated出三個文件
//ARouter$$Group$$login
//ARouter$$Providers$$loginplugin
//ARouter$$Root$$loginplugin
image
    atlas.put("/login/login", RouteMeta.build(RouteType.ACTIVITY, LoginActivity.class, "/login/login", "login", new java.util.HashMap<String, Integer>(){{put("password", 8); put("username", 8); }}, -1, -2147483648));

//map 存映射關(guān)系
//static Map<String, RouteMeta> routes = new HashMap<>();
  • 以上三個文件是如何生成的呢浆兰?APT是Annotation Processing Tool的簡稱,即注解處理工具簸呈,apt是在編譯期對代碼中指定的注解進行解析蜕便,然后做一些其他處理(如通過javapoet生成新的Java文件)ARouter使用了兩個庫auto-service javapoet轿腺,來實現(xiàn)從注解到代碼的注入段直,其中auto-service為注解處理器的庫鸯檬,javapoet為代碼生成器
image

通過例子了解APT

首先我們了解一下元注解喧务,meta-annotation(元注解)

  • @Target
  TYPE, // 類功茴、接口坎穿、枚舉類 
  FIELD, // 成員變量(包括:枚舉常量)
  METHOD, // 成員方法
  PARAMETER, // 方法參
  CONSTRUCTOR, // 構(gòu)造方法
  LOCAL_VARIABLE, // 局部變量
  ANNOTATION_TYPE, // 注解類
  PACKAGE, // 可用于修飾:包
  TYPE_PARAMETER, // 類型參數(shù),JDK 1.8 新增
  TYPE_USE // 使用類型的任何地方篮绿,JDK 1.8 新增
  ```

-  @Retention

```java
  SOURCE,    只在本編譯單元的編譯過程中保留亲配,并不寫入Class文件中惶凝。
  CLASS,       在編譯的過程中保留并且會寫入Class文件中,但是JVM在加載類的時候不需要將其加載為運行時可見的(反射可見)的注解==是JVM在加載類時反射不可見苍鲜。
  RUNTIME   在編譯過程中保留坡贺,會寫入Class文件遍坟,并且JVM加載類的時候也會將其加載為反射可見的注解愿伴。
  ```

-  @Documented 注解的作用是:描述在使用 javadoc 工具為類生成幫助文檔時是否要保留其注解信息.

-  @Inherited 注解的作用是:使被它修飾的注解具有繼承性(如果某個類使用了被@Inherited修飾的注解隔节,則其子類將自動具有該注解)

- 通過元注解我們定義自己的注解

- [AutoService 注解處理器](https://github.com/google/auto/tree/master/service)

      注解處理器是一個在javac中的怎诫,用來編譯時掃描和處理的注解的工具贷痪。你可以為特定的注解劫拢,注冊你自己的注解處理器舱沧。到這里熟吏,我假設(shè)你已經(jīng)知道什么是注解,并且知道怎么申明的一個注解類型哆料。

一個注解的注解處理器东亦,以Java代碼(或者編譯過的字節(jié)碼)作為輸入唬渗,生成文件(通常是.java文件)作為輸出镊逝。

- 虛處理器`AbstractProcessor`
- `init(ProcessingEnvironment env)`: 【核心】
  每一個注解處理器類都必須有一個空的構(gòu)造函數(shù)撑蒜。然而座菠,這里有一個特殊的init()方法浴滴,它會被注解處理工具調(diào)用升略,并輸入`ProcessingEnviroment`參數(shù)品嚣。`ProcessingEnviroment`提供很多有用的工具類`Elements`,`Types`和`Filer`
- `process(Set< ? extends TypeElement> annotations, RoundEnvironment env)`:【核心】
  這相當于每個處理器的主函數(shù)main()翰撑。你在這里寫你的掃描额嘿、評估和處理注解的代碼册养,以及生成Java文件
- `getSupportedAnnotationTypes()`
  這里你必須指定,這個注解處理器是注冊給哪個注解的
- `getSupportedSourceVersion()`
  用來指定你使用的Java版本靠闭。通常這里返回`SourceVersion.latestSupported()`

- APT 所用的代碼生成器:**[JavaPoet](https://github.com/square/javapoet)** is a Java API for generating `.java` source files.(JavaPoet 是一個java api 愧膀,為了生成 .java源文件的)

- 官方helloworld

```java
MethodSpec main = MethodSpec.methodBuilder("main")
  .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
  .returns(void.class)
  .addParameter(String[].class, "args")
  .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
  .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
  .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
  .addMethod(main)
  .build();

JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
  .build();

javaFile.writeTo(System.out);
  • 通過以上可生成以下java 文件
package com.example.helloworld;

public final class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, JavaPoet!");
  }
}
  • JavaPoet 主要api
- JavaFile 用于構(gòu)造輸出包含一個頂級類的Java文件 
- TypeSpec 生成類檩淋,接口蟀悦,或者枚舉  
- MethodSpec 生成構(gòu)造函數(shù)或方法 
- FieldSpec 生成成員變量或字段 
- ParameterSpec  用來創(chuàng)建參數(shù)  
- AnnotationSpec 用來創(chuàng)建注解
  • JavaPoet 主要占位符
- $L(for Literals) 執(zhí)行結(jié)構(gòu)的字符或常見類型日戈,或TypeSpec, $S(for Strings) 字符, $T(for Types) 類, $N(for Names) 方法 等標識符
  $L>$S
//1.Pass an argument value for each placeholder in the format string to `CodeBlock.add()`. In each example, we generate code to say "I ate 3 tacos"
CodeBlock.builder().add("I ate $L $L", 3, "tacos")
 //2.When generating the code above, we pass the hexDigit() method as an argument to the byteToHex() method using $N:
  MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex")
    .addParameter(int.class, "b")
    .returns(String.class)
    .addStatement("char[] result = new char[2]")
    .addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit)
    .addStatement("result[1] = $N(b & 0xf)", hexDigit)
    .addStatement("return new String(result)")
    .build();
//=======================
public String byteToHex(int b) {
  char[] result = new char[2];
  result[0] = hexDigit((b >>> 4) & 0xf);
  result[1] = hexDigit(b & 0xf);
  return new String(result);
}

//$T for Types
//We Java programmers love our types: they make our code easier to understand. And JavaPoet is on board. It has rich built-in support for types, including automatic generation of import statements. Just use $T to reference types:
.addStatement("return new $T()", Date.class)== return new Date();

實戰(zhàn)-自定義簡易版路由-CRouter

  • 新建name-annotation javaLib份氧,定義CRoute注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface CRoute {
    String path();
}
  • 新建name-compiler javaLib
1.
dependencies {
    implementation project(path: ':TestRouter-annotation')
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
    compileOnly 'com.google.auto.service:auto-service-annotations:1.0-rc7'

    implementation 'com.squareup:javapoet:1.8.0'
}
2.@AutoService(Processor.class)
public class TestRouteProcessor extends AbstractProcessor {
  @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
       //dosomething
    }
   @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
      //dosomething
    }
}
 
  • 業(yè)務(wù)module執(zhí)行順序如下
 1. annotationProcessor project(':TestRouter-compiler')
implementation project(':TestRouter-annotation')
2.添加注解@CRoute(path = "/csetting/csetting")
3.編譯運行
4.業(yè)務(wù)module apt 生成的java 文件,如下:
public final class C$csettingC$csettingHelloWorld {
  public static String holder = "/csetting/csetting:com.cnn.settingplugin.SettingsActivity";

  public static void main(String[] args) {
    System.out.println("Hello, JavaPoet!");
  }
}
  • 參考ARouter-init 方法钮糖,寫出我們CRouter-init
 /**
     * Init, it must be call before used router.
     */
    public static void init(Application application) {
        if (!hasInit) {
            CRouter.application=application;
            hasInit=true;
            try {
                getFileNameByPackageName(application, ROUTE_ROOT_PAKCAGE);
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
  • 利用反射獲取到注解對應(yīng)映射關(guān)系,并參考ARouter存入HashMap


    image
  • 通過隱式啟動Activity模擬跳轉(zhuǎn)


    image
  • 到此我們模擬出簡易版本的ARouter,完整自定義CRouter

/**
 * Created by caining on 7/29/21 16:09
 * E-Mail Address:cainingning@#
 */
public class CRouter {
    private volatile static CRouter instance = null;
    private volatile static boolean hasInit = false;
    private static Application application;
    public static final String ROUTE_ROOT_PAKCAGE = "com.cnn.crouter";
    private static Map<String ,String> mapHolder = new HashMap<>();

    /**
     * Init, it must be call before used router.
     */
    public static void init(Application application) {
        if (!hasInit) {
            CRouter.application=application;
            hasInit=true;
            try {
                getFileNameByPackageName(application, ROUTE_ROOT_PAKCAGE);
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    /**
     * Get instance of router. A
     * All feature U use, will be starts here.
     */
    public static CRouter getInstance() {
        if (!hasInit) {
            throw new InitException("ARouter::Init::Invoke init(context) first!");
        } else {
            if (instance == null) {
                synchronized (CRouter.class) {
                    if (instance == null) {
                        instance = new CRouter();
                    }
                }
            }
            return instance;
        }
    }


    public void navigation(String path) {
         startActivity(path);
    }

    private void startActivity(String path) {
        String classPath
                = mapHolder.get(path);
        if (!TextUtils.isEmpty(classPath)) {
            Intent intent = new Intent();
            intent.setClassName(application, classPath);//設(shè)置包路徑
            ActivityCompat.startActivity(application, intent, null);
        }else {
            Toast.makeText(application, "路徑空啦", Toast.LENGTH_SHORT).show();
        }
    }


    /**
     * 通過指定包名,掃描包下面包含的所有的ClassName
     *
     * @param context     U know
     * @param packageName 包名
     * @return 所有class的集合
     */
    private static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
        final Set<String> classNames = new HashSet<>();

        List<String> paths = getSourcePaths(context);
        final CountDownLatch parserCtl = new CountDownLatch(paths.size());

        for (final String path : paths) {
            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()) {
                            String className = dexEntries.nextElement();
                            if (className.startsWith(packageName)) {
                                classNames.add(className);
                                try {
                                    Class clazz = Class.forName(className);
                                    Object obj = clazz.newInstance();
                                    Field field03 = clazz.getDeclaredField("holder"); // 獲取屬性為id的字段
                                    String value= (String) field03.get(obj);
                                    String[] split = value.split(":");
                                    if (split!=null&&split.length==2) {
                                        mapHolder.put(split[0],split[1]);
                                    }
                                    Log.i("test-->",mapHolder.toString());
                                } catch (ClassNotFoundException e) {
                                    e.printStackTrace();
                                } catch (IllegalAccessException e) {
                                    e.printStackTrace();
                                } catch (InstantiationException e) {
                                    e.printStackTrace();
                                } catch (SecurityException e) {
                                    e.printStackTrace();
                                } catch (NoSuchFieldException e) {
                                    e.printStackTrace();
                                } catch (IllegalArgumentException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    } catch (Throwable ignore) {
                        Log.e("ARouter", "Scan map file in dex files made error.", ignore);
                    } finally {
                        if (null != dexfile) {
                            try {
                                dexfile.close();
                            } catch (Throwable ignore) {
                            }
                        }

                        parserCtl.countDown();
                    }
                }
            });
        }

        parserCtl.await();

        return classNames;
    }
    private static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
        ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
        List<String> sourcePaths = new ArrayList<>();
        sourcePaths.add(applicationInfo.sourceDir); //add the default apk path
        return sourcePaths;
    }
}

總結(jié)

  • ARouter使用指南
  • ARouter攔截器
  • SchemeFilte 實現(xiàn)外部html 跳轉(zhuǎn)Native穆趴,打通WEB&Native
  • 了解 JavaPoet &AutoService 注解處理器 apt原理
  • 寫出簡易版CRouter未妹,通過實戰(zhàn)我們了解ARouter實現(xiàn)原理
  • 項目demo地址

問題

  • 除了ARouter络它,你知道利用apt 實現(xiàn)的框架都有哪些化戳?
  • ARouter有沒有什么缺點?

引用

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末点楼,一起剝皮案震驚了整個濱河市看尼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌盟步,老刑警劉巖藏斩,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異却盘,居然都是意外死亡狰域,警方通過查閱死者的電腦和手機黄橘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進店門兆览,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人塞关,你說我怎么就攤上這事抬探。” “怎么了帆赢?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵小压,是天一觀的道長。 經(jīng)常有香客問我椰于,道長怠益,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任瘾婿,我火速辦了婚禮蜻牢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘偏陪。我一直安慰自己抢呆,他們只是感情好,可當我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布笛谦。 她就那樣靜靜地躺著抱虐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪揪罕。 梳的紋絲不亂的頭發(fā)上梯码,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天宝泵,我揣著相機與錄音,去河邊找鬼轩娶。 笑死儿奶,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的鳄抒。 我是一名探鬼主播闯捎,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼许溅!你這毒婦竟也來了瓤鼻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤贤重,失蹤者是張志新(化名)和其女友劉穎茬祷,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體并蝗,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡祭犯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了滚停。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沃粗。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖键畴,靈堂內(nèi)的尸體忽然破棺而出最盅,到底是詐尸還是另有隱情,我是刑警寧澤起惕,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布涡贱,位于F島的核電站,受9級特大地震影響疤祭,放射性物質(zhì)發(fā)生泄漏盼产。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一勺馆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧侨核,春花似錦草穆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至些己,卻和暖如春豌鸡,著一層夾襖步出監(jiān)牢的瞬間嘿般,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工涯冠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留炉奴,地道東北人。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓蛇更,卻偏偏與公主長得像瞻赶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子派任,可洞房花燭夜當晚...
    茶點故事閱讀 44,665評論 2 354

推薦閱讀更多精彩內(nèi)容