給大家推薦個開源項目,Android開發教程入門

mb612ed7a890c2b 2021-09-18 07:54:53 阅读数:836

android 教程

public void login(String name, String password) {
HashMap route = MyRouters.getRouteInfo(new HashMap);
LoginActivity.class classBean = route.get(“/login/login”);
Intent intent = new Intent(this, classBean);
intent.putExtra(“name”, name);
intent.putExtra(“password”, password);
startActivity(intent);
}


這樣是不是很簡單就實現了路由的跳轉,既沒有隱式意圖的繁瑣,也沒有反射對性能的損耗。用過ARouter的同學應該知道,用ARouter啟動Activity應該是下面這個寫法:

  • 1.
  • 2.
  • 3.

// 2. Jump with parameters
ARouter.getInstance().build(“/test/login”)
.withString(“password”, 666666)
.withString(“name”, “小三”)
.navigation();


那麼ARouter背後是怎麼樣實現跳轉的呢?實際上它的核心思想跟上面講解是一樣的,我們在代碼裏加入的@Route注解,會在編譯時期通過apt生成一些存儲path和activity.class映射關系的類文件,然後app進程啟動的時候會加載這些類文件,把保存這些映射關系的數據讀到內存裏(保存在map裏),然後在進行路由跳轉的時候,通過build()方法傳入要到達頁面的路由地址,ARouter會通過它自己存儲的路由錶找到路由地址對應的Activity.class(activity.class = map.get(path)),然後new Intent(context, activity.Class),當調用ARouter的withString()方法它的內部會調用intent.putExtra(String name, String value),調用navigation()方法,它的內部會調用startActivity(intent)進行跳轉,這樣便可以實現兩個相互沒有依賴的module順利的啟動對方的Activity了。
* 第二節:ARouter映射關系如何生成
通過上節我們知道在Activity類上加上@Route注解之後,便可通過apt生成對應的路由錶。那麼現在我們來搞清楚,既然路由和Activity的映射關系我們可以很容易地得到(因為代碼都是我們寫的,當然很容易得到),那麼為什麼我們要繁瑣的通過apt來生成類文件而不是自己直接寫一個契約類來保存映射關系呢。如果站在一個框架開發者的角度去理解,就不難明白了,因為框架是給上層業務開發者調用的,如果業務開發者在開發頁面的過程中還要時不時的更新或更改契約類文件,不免過於麻煩?如果有自動根據路由地址生成映射錶文件的技術該多好啊!
技術當然是有的,那就是被眾多框架使用的apt及javapoet技術,那麼什麼是apt,什麼是javapoet呢?我們先來看下圖:![給大家推薦個開源項目,Android開發教程入門_移動開發](https://s5.51cto.com/images/20210918/1631922282931274.jpg) 由圖可知,apt是在編譯期對代碼中指定的注解進行解析,然後做一些其他處理(如通過javapoet生成新的Java文件)。我們常用的ButterKnife,其原理就是通過注解處理器在編譯期掃描代碼中加入的@BindView、@OnClick等注解進行掃描處理,然後生成XXX\_ViewBinding類,實現了view的綁定。javapoet是鼎鼎大名的squareup出品的一個開源庫,是用來生成java文件的一個library,它提供了簡便的api供你去生成一個java文件。可以如下引入javapoet

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

implementation?‘com.squareup:javapoet:1.7.0’


下面我通過demo中的例子帶你了解如何通過apt和javapoet技術生成路由映射關系的類文件:
首先第一步,定義注解:

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Route {
// 路由的路徑
String path();

// 將路由節點進行分組,可以實現動態加載
String group() default "";

}


這裏看到Route注解裏有path和group,這便是仿照ARouter對路由進行分組。因為當項目變得越來越龐大的時候,為了便於管理和减小首次加載路由錶過於耗時的問題,我們對所有的路由進行分組。在ARouter中會要求路由地址至少需要兩級,如"/xx/xx",一個模塊下可以有多個分組,這裏我們就將路由地址定為必須大於等於兩級,其中第一級是group。
第二步,在Activity上使用注解:

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

@Route(path = “/main/main”)
public class MainActivity extends AppCompatActivity {}

@Route(path = “/main/main2”)
public class Main2Activity extends AppCompatActivity {}

@Route(path = “/show/info”)
public class ShowActivity extends AppCompatActivity {}


第三步,編寫注解處理器,在編譯期找到加入注解的類文件,進行處理,這裏我只展示關鍵代碼,具體的細節還需要你去demo中仔細研讀:

  • 1.
  • 2.
  • 3.

@AutoService(Processor.class)
// 處理器接收的參數
@SupportedOptions(Constant.ARGUMENTS_NAME)
// 注册給哪些注解的
@SupportedAnnotationTypes(Constant.ANNOTATION_TYPE_ROUTE)
public class RouterProcessor extends AbstractProcessor {

// key:組名 value:類名
private Map rootMap = new TreeMap<>();
// 分組 key:組名 value:對應組的路由信息
private Map> groupMap = new HashMap<>();
//...
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
//...
elementUtils = processingEnvironment.getElementUtils();
typeUtils = processingEnvironment.getTypeUtils();
filerUtils = processingEnvironment.getFiler();
//參數是模塊名 為了防止多模塊/組件化開發的時候 生成相同的 xx?ROOT?文件
Map options = processingEnvironment.getOptions();
if (!Utils.isEmpty(options)) {
moduleName = options.get(Constant.ARGUMENTS_NAME);
}
if (Utils.isEmpty(moduleName)) {
throw new RuntimeException("Not set processor moudleName option !");
}
log.i("init RouterProcessor " + moduleName + " success !");
}
@Override
public boolean process(Set set, RoundEnvironment roundEnvironment) {
if (!Utils.isEmpty(set)) {
//被Route注解的節點集合
Set rootElements = roundEnvironment.getElementsAnnotatedWith(Route.class);
if (!Utils.isEmpty(rootElements)) {
processorRoute(rootElements);
}
return true;
}
return false;
}
//...

}


如代碼中所示,要想在編譯期對注解做處理,就需要RouterProcessor繼承自AbstractProcessor並通過@AutoService注解進行注册,然後實現process()方法。process()方法裏的set集合就是編譯期掃描代碼得到的加入了Route注解的文件集合,@SupportedAnnotationTypes(Constant.ANNOTATION\_TYPE\_ROUTE)指定了需要處理的注解的路徑地址,在此就是Route.class的路徑地址。init()方法會在注解處理器初始化的時候拿到一些工具類,然後看這句代碼moduleName = options.get(Constant.ARGUMENTS\_NAME),會得到一個moduleName,這個moduleName需要我們在要使用注解處理器生成路由映射文件的模塊gradle文件裏配置,而@SupportedOptions(Constant.ARGUMENTS\_NAME)會拿到當前module的名字,用來生成唯一對應module下存放分組信息的類文件名。這裏變量Constant.ARGUMENTS\_NAME的值就是moduleName,在這之前,我們需要在每個組件module的gradle下配置如下

  • 1.
  • 2.
  • 3.

javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName()]
}
}


這裏的@AutoService是為了注册注解處理器,需要我們引入一個google開源的自動注册工具AutoService,如下依賴(當然也可以手動進行注册,不過略微麻煩,這裏不太推薦):

  • 1.
  • 2.
  • 3.

implementation?‘com.google.auto.service:auto-service:1.0-rc2’


第四步:通過javapoet生成java類:在第三步中process()方法裏有一句代碼:processorRoute(rootElements),processorRoute()方法裏會調用generatedGroup()和generatedRoot()方法分別去生成分組信息相關和路由映射相關Java文件,關於路由映射信息和分組信息的關系,我們下面會講到,這裏先不用理會,你只需要知道它們都是生成的存有映射關系的文件,這裏我只貼出generatedRoot()方法,因為生成類文件的原理都是一樣的,至於生成什麼功能的類,只要你會一個,舉一反三,這便沒有什麼難度。

  • 1.
  • 2.
  • 3.

/**

  • 生成Root類 作用:記錄<分組,對應的Group類>
    */
    private void generatedRoot(TypeElement iRouteRoot, TypeElement iRouteGroup) {
    //創建參數類型 Map> routes>
    //Wildcard 通配符
    ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(
    ClassName.get(Map.class),
    ClassName.get(String.class),
    ParameterizedTypeName.get(
    ClassName.get(Class.class),
    WildcardTypeName.subtypeOf(ClassName.get(iRouteGroup))
    ));
    //生成參數 Map> routes> routes
    ParameterSpec parameter = ParameterSpec.builder(parameterizedTypeName, “routes”).build();

    //生成函數 public void loadInfo(Map> routes> routes)
    MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constant.METHOD_LOAD_INTO)
    .addModifiers(Modifier.PUBLIC)
    .addAnnotation(Override.class)
    .addParameter(parameter);
    //生成函數體
    for (Map.Entry entry : rootMap.entrySet()) {
    methodBuilder.addStatement(“routes.put($S, $T.class)”, entry.getKey(), ClassName.get(Constant.PACKAGE_OF_GENERATE_FILE, entry.getValue()));
    }
    //生成XX_Root_XX類
    String className = Constant.NAME_OF_ROOT + moduleName;
    TypeSpec typeSpec = TypeSpec.classBuilder(className)
    .addSuperinterface(ClassName.get(iRouteRoot))
    .addModifiers(Modifier.PUBLIC)
    .addMethod(methodBuilder.build())
    .build();
    try {
    //生成java文件,PACKAGE_OF_GENERATE_FILE就是生成文件需要的路徑
    JavaFile.builder(Constant.PACKAGE_OF_GENERATE_FILE, typeSpec).build().writeTo(filerUtils);
    log.i(“Generated RouteRoot:” + Constant.PACKAGE_OF_GENERATE_FILE + “.” + className);
    } catch (IOException e) {
    e.printStackTrace();
    }
    }


如上,我把每一塊代碼的作用注釋了出來,相信大家很容易就能理解每一個代碼段的作用。可見,其實生成文件只是調用一些api而已,只要我們熟知api的調用,生成java文件便沒有什麼難度。那麼大家現在想一個問題,只要我以統一的規則生成所有的映射文件,然後拿到這些映射文件,是不是就可以很輕易的進行路由跳轉了。好了,下一部分我們就來實現這個路由框架。
### 第二部分:動手實現一個路由框架
通過第一部分的講述,我相信大家對於ARouter的原理已經有了整體輪廓的理解,這一部分,我便會通過代碼帶你去實現一個自己的路由框架。上節我們講了如何生成路由映射文件,這節我們考慮下生成這些路由映射文件後,如何統一的去使用這些文件。
* 第一節:如何拿到和統一管理路由映射文件
通過第一部分的講述我們知道在Activity類上加上@Route注解之後,便可通過apt來生成對應的路由映射文件,那麼現在我們考慮一個問題,就是我們的路由映射文件是在編譯期間生成的,那麼在程序的運行期間我們要統一調用這些路由信息,便需要一個統一的調用方式。我們先來定義這個調用方式:

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

public interface IRouteGroup {
void loadInto(Map atlas);
}

public interface IRouteRoot {
void loadInto(Map> routes);
}


我們定義兩個接口來對生成的java文件進行約束,IRouteGroup是生成的分組關系契約,IRouteRoot是單個分組下路由信息契約,只要我們生成的java文件分別繼承自這兩個接口並實現loadInto()方法,在運行期間我們就可以統一的調用生成的java文件,獲取路由映射信息。在上文中我們時不時的提到分組信息和路由映射信息,其實兩者都是通過javapoet生成的類文件,理論上我們只需要生成一種文件,即記錄路由地址和Activity一一映射的文件,但是一個項目中眾多的路由地址沒有任何分組,會很難辨別,所以我們對路由進行分組,下面我用一張圖來說明分組和路由的關系: ![給大家推薦個開源項目,Android開發教程入門_移動開發_02](https://s4.51cto.com/images/20210918/1631922283790318.jpg) 由圖可知,xxx\_Root\_xxx就是記錄著此module下的分組信息,而每一個分組會單獨記錄此分組下的所有路由映射關系。這樣,只要我們拿到所有的分組文件,就能通過分組文件找到任一個分組,從而拿到所有的路由信息。
好了,生成映射文件我們上面已經講過了,現在我們編譯下項目就會在每個組件module的build/generated/source/apt目錄下生成相關映射文件。這裏我把app module編譯後生成的文件貼出來,app module編譯後會生成EaseRouter\_Root\_app文件和EaseRouter\_Group\_main、EEaseRouter\_Group\_show等文件,EaseRouter\_Root\_app文件對應於app module的分組,裏面記錄著本module下所有的分組信息,EaseRouter\_Group\_main、EaseRouter\_Group\_show文件分別記載著當前分組下的所有路由地址和ActivityClass映射信息。如下所示:

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

結語

網上高級工程師面試相關文章魚龍混雜,要麼一堆內容,要麼內容質量太淺, 鑒於此我整理了上述安卓開發高級工程師面試題以及答案。希望幫助大家順利進階為高級工程師。
目前我就職於某大廠安卓高級工程師職比特,在當下大環境下也想為安卓工程師出一份力,通過我的技術經驗整理了面試經常問的題,答案部分是一篇文章或者幾篇文章,都是我認真看過並且覺得不錯才整理出來。

大家知道高級工程師不會像剛入門那樣被問的問題一句話兩句話就能錶述清楚,所以我通過過濾好文章來幫助大家理解。

 CodeChina開源項目:《Android學習筆記總結+移動架構視頻+大廠面試真題+項目實戰源碼》

給大家推薦個開源項目,Android開發教程入門_Android_03

現在都說互聯網寒冬,其實只要自身技術能力够强,咱們就不怕!我這邊專門針對Android開發工程師整理了一套【Android進階學習視頻】、【全套Android面試秘籍】、【Android知識點PDF】。

版权声明:本文为[mb612ed7a890c2b]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/09/20210918075453119X.html