fragmentargs 源碼解析

簡介

fragmentargs可以用來處理Fragment屬性的保存(Fragment.setArguments(Bundle bundle))和自動賦值(Fragment.getArguments())邏輯,以在編譯時自動生成源代碼的方式來減少一些重復代碼的編寫,可以查看這篇博文了解一下

流程圖

簡單的流程圖


無標題繪圖.jpg

編譯時注解

由于運行時注解用到了反射技術,在性能方面會有影響,而編譯時注解是在編譯期生成源代碼的方式,性能會好一些

如何Debug

在Android Studio中想在工程編譯時進行Debug會比較麻煩一些,要做一些配置,具體的可以查看這篇博文,下面簡要介紹下(小坑還挺多)
1、配置~/.gradle/gradle.properties

org.gradle.daemon=true
org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

然后啟動gradle daemon(mac平臺)

./gradlew --daemon

2、在AS中配置remote debugger,其實是新建一個Run/Debug Configuration(remote類型)。我們每次創建一個Android Project的時候AS都會默認創建一個Run/Debug Configuration(Android Application類型),名字叫做app,這個大家都挺眼熟的:)
Run/Debug Configuration信息基本上不用做任何更改,只要確保端口號跟上一個步驟的一致就行

0A350BEB-24B8-429F-AEA5-949D255F9F08.png

3、在自定義的annotation processor設置斷點,啟動remote debugger,在命令行中運行./gradlew clean assembleDebug

763A27C5-8F67-4B72-9977-DE680630ECBA.png

fragmentargs annotation processor分析

fragmentargs庫中的annotation processor主要生成兩種類型的java源代碼,首先會生成被注解過的Frament的FragmentBuilder.java文件(會有多個),然后會生成FragmentArgsInjector.java文件(僅一個)

MyFragmentBuilder.java

package com.tubb.argknife;

import android.os.Bundle;
public final class MyFragmentBuilder {
  private final Bundle mArguments = new Bundle();

  public MyFragmentBuilder(int dis) {
    mArguments.putInt("dis", dis);
  }
  public static MainActivity.MyFragment newMyFragment(int dis) {
    return new MyFragmentBuilder(dis).build();
  }
  public static final void injectArguments(MainActivity.MyFragment fragment) {
    Bundle args = fragment.getArguments();
    if (args == null) {
      throw new IllegalStateException("No arguments set");
    }
    if (!args.containsKey("dis")) {
      throw new IllegalStateException("required argument dis is not set");
    }
    fragment.dis = args.getInt("dis");
  }
  public MainActivity.MyFragment build() {
    com.tubb.argknife.MainActivity.MyFragment fragment = new com.tubb.argknife.MainActivity.MyFragment();
    fragment.setArguments(mArguments);
    return fragment;
  }
  public <F extends MainActivity.MyFragment> F build(F fragment) {
    fragment.setArguments(mArguments);
    return fragment;
  }
}

AutoFragmentArgInjector.java

package com.hannesdorfmann.fragmentargs;

import android.os.Bundle;
public final class AutoFragmentArgInjector
    implements com.tubb.argknife.annotation.FragmentArgsInjector {

  public void inject(Object target) {

    Class<?> targetClass = target.getClass();
    String targetName = targetClass.getSimpleName();

    if ( com.tubb.argknife.OuterFragment.class.getSimpleName().equals(targetName) ) {
      com.tubb.argknife.OuterFragmentBuilder.injectArguments( ( com.tubb.argknife.OuterFragment ) target);
      return;
    }

    if ( com.tubb.argknife.MainActivity.MyFragment.class.getSimpleName().equals(targetName) ) {
      com.tubb.argknife.MyFragmentBuilder.injectArguments( ( com.tubb.argknife.MainActivity.MyFragment ) target);
      return;
    }
  }
}

fragmentargs使用squareup的javawriter來生成java源文件的,只需要配置下就行

compile 'com.squareup:javawriter:2.2.1'

但我自己在實踐的過程中遇到一個很奇怪的問題,明明javawriter代碼已經導入了,但就是引用不了,后來參考作者的做法把JavaWriter類拷貝到本地repackage了一下,問題解決

具體代碼的生成過程挺繁瑣和抽象的,首先會去得到注解的類(Fragment)和注解的屬性(Fragment中的屬性)的Element信息,然后根據這些Elements來動態生成類和類中的方法和屬性,具體的源碼大家可以看下我剝離出來的代碼

fragmentargs的調用過程

fragmentargs通過annotation processor在工程編譯完成后生成很多的FragmentBuilder.java源文件和一個AutoFragmentArgInjector。java源文件,當然最終也會編譯成相應的class文件
fragmentargs的使用非常簡單README,那它內部是如何工作的呢
通過FragmentArgs.inject(this)的調用,會進入到FragmentArgs類中的injectFromBundle(Object target)方法

package com.hannesdorfmann.fragmentargs;

/**
 * The root class to inject arguments to a fragment
 *
 * @author Hannes Dorfmann
 */
public class FragmentArgs {

  public static final String AUTO_MAPPING_CLASS_NAME = "AutoFragmentArgInjector";
  public static final String AUTO_MAPPING_PACKAGE = "com.hannesdorfmann.fragmentargs";
  public static final String AUTO_MAPPING_QUALIFIED_CLASS =
      AUTO_MAPPING_PACKAGE + "." + AUTO_MAPPING_CLASS_NAME;

  private static FragmentArgsInjector autoMappingInjector;

  public static void inject(Object fragment) {
    injectFromBundle(fragment);
  }

  static void injectFromBundle(Object target) {

    if (autoMappingInjector == null) {
      // Load the automapping class
      try {
        Class<?> c = Class.forName(AUTO_MAPPING_QUALIFIED_CLASS);
        autoMappingInjector = (FragmentArgsInjector) c.newInstance();
      } catch (Exception e) {
        // Since 2.0.0 we don't throw an exception because of android library support.
        // Instead we print this exception as warning message

        /*
        Exception wrapped = new Exception("Could not load the generated automapping class. "
            + "However, that may be ok, if you use FragmentArgs in library projects", e);
        wrapped.printStackTrace();
        */
      }
    }

    if (autoMappingInjector != null) {
      autoMappingInjector.inject(target);
    }
  }
}

通過源碼我們可以發現,會去反射(注意是static,性能上基本上不會有影響)實例化一個實現了FragmentArgsInjector接口的類的實例,其實就是我們之前介紹的AutoFragmentArgInjector,然后調用AutoFragmentArgInjectorAutoFragmentArgInjector.inject(target)方法,我們可以來看看這個方法中具體做了些啥

  public void inject(Object target) {

    Class<?> targetClass = target.getClass();
    String targetName = targetClass.getSimpleName();

    if ( com.tubb.argknife.OuterFragment.class.getSimpleName().equals(targetName) ) {
      com.tubb.argknife.OuterFragmentBuilder.injectArguments( ( com.tubb.argknife.OuterFragment ) target);
      return;
    }

    if ( com.tubb.argknife.MainActivity.MyFragment.class.getSimpleName().equals(targetName) ) {
      com.tubb.argknife.MyFragmentBuilder.injectArguments( ( com.tubb.argknife.MainActivity.MyFragment ) target);
      return;
    }
  }

看到這些代碼是不是突然明悟了:) 在這個方法中把FragmentBuilder和我們自己的Fragment聯系起來了,其實就是通過Fragment.getArguments()來為我們的屬性賦值,也就是Auto Inject

    public static final void injectArguments(MyFragment fragment) {
        Bundle args = fragment.getArguments();
        if(args == null) {
            throw new IllegalStateException("No arguments set");
        } else if(!args.containsKey("dis")) {
            throw new IllegalStateException("required argument dis is not set");
        } else {
            fragment.dis = args.getInt("dis");
        }
    }

Note

在自己使用fragmentargs的過程中遇到一個比較坑的東西,發現當Fragment為內部類時不能正常工作,自己測試用的代碼嘗試修復了這個問題,也給作者提了一個issuse,估計作者下個版本會進行修復的

還有一點,ArgKnife并不能用在工作項目中,只是測試代碼??

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容