中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

5. Android 框架ButterKnife源代碼分析

發布時間:2020-07-04 07:58:57 來源:網絡 閱讀:820 作者:rongwei84n 欄目:移動開發

一. ButterKnife介紹

在Android編程過程中,我們會寫大量的布局和點擊事件,像初始view、設置view監聽這樣簡單而重復的操作,這些代碼繁瑣而又不雅觀,比如:

TextView tvSetName = findViewById(R.id.xxx);
tvSetName.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //xxxx
    }
});
TextView tvSetAge = findViewById(R.id.xxx);
tvSetAge.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //xxxx
    }
});
TextView tvSetArea = findViewById(R.id.xxx);
tvSetArea.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //xxxx
    }
});



Activity中這種代碼多了之后,很不雅觀。


二. 使用簡介

ButterKnife使用方法比較簡單,主要包括以下步驟:


  1. 引用ButterKnife包

  2. 在onCreate里面bind(setContentView之后)

  3. 綁定各種事件

  4. onDestroy里面解綁釋放資源


ButterKnife使用方法


三. ButterKnife源代碼下載


ButterKnife github源代碼地址


直接git clone或者下載zip即可。


四. 編譯

  1. Android studio打開ButterKnife源代碼

    AndroidStudio->File->open->ButterKnife源代碼路徑->確認

  2. Build->Rebuild Project



五. 生成的aar和jar包

生成的包主要有兩個

  1. butterknife-annotations-8.5.2-SNAPSHOT.jar

    路徑:butterknife-annotations->build->libs


  2. butterknife-release.aar

    路徑: butterknife->build->outputs->aar



六. 其他應用引用自定義ButterKnife包 

  1. 刪除原來ButterKnife包引用,因為要使用自己編譯的包

  2. //    compile 'com.jakewharton:butterknife:8.4.0'




  3. 拷貝文件

    拷貝上面兩個文件到自己項目app模塊的libs 目錄


  4. 添加aar的關聯

    打開app模塊的build.gradle文件,添加:


  5. compile fileTree(include: ['*.jar'], dir: 'libs')
    compile(name: 'butterknife-release', ext: 'aar')
  6. Build->Rebuild Project



七. 源代碼分析


1. ButterKnife.bind(Activity target)過程


文件名:ButterKnife.java

static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();

public static Unbinder bind(@NonNull Activity target) {
    Log.d("Sandy", "ButterKnife bind.. target: " + target);
    View sourceView = target.getWindow().getDecorView();
    return createBinding(target, sourceView);
}




private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

    if (constructor == null) {
      return Unbinder.EMPTY;
    }

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      return constructor.newInstance(target, source);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InstantiationException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InvocationTargetException e) {
      Throwable cause = e.getCause();
      if (cause instanceof RuntimeException) {
        throw (RuntimeException) cause;
      }
      if (cause instanceof Error) {
        throw (Error) cause;
      }
      throw new RuntimeException("Unable to create binding instance.", cause);
    }
}


private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
    }
    try {
        Log.d("Sandy", "findBindingConsForClass: " + clsName + " vindBinding name: " +
                clsName + "_ViewBinding");
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //noinspection unchecked
      bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
      if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
    } catch (ClassNotFoundException e) {
      if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
      bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
    } catch (NoSuchMethodException e) {
      throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
    }
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
}




上面這段代碼有幾個注意點:

a. sourceView代表是DecorView,也就是我們窗口的頂級View。


b. findBindingConstructorForClass有個BINDINGS緩存,key是class,value是緩存的Unbinder對象,這樣做可以加快bind速度。

因為每個類的ButterKnife注解在運行期間是不會變的,比如MainActivity有3個ButterKnife注解,那么它就是3個。除非有新的apk安裝。

所以適合用緩存來實現。


c. findBindingConstructorForClass使用了遞歸的方法

這個方法使用了遞歸,不斷調用父類,也就是


catch (ClassNotFoundException e) {
      if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
      bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
}



那為什么要這么處理呢?


因為有些Activity沒有ButterKnife的注解,但是它的父類可能有,比如BaseActivity。所以需要往上遞歸。那什么時候遞歸結束呢?


if (bindingCtor != null) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
}



如果緩存里面找到了結果,那么結束,同時返回結果;


或者類名以"android."或者"java."開頭,也結束,返回null;

以Activity為例,Activity的類名是android.app.Activity,所以你的MainActivity如果遞歸到Activity還沒有找到ButterKnife注解,那就說明你的MainActivity是沒有包含ButterKnife注解的。


d. 如果子Activity和父Activity都有ButterKnife注解怎么辦?

答案是返回子Activity以及其對應的 Constructor<? extends Unbinder> bindingCtor對象


那它的父Activity如果也有ButterKnife注解怎么辦?怎么解析父Activity的ButterKnife注解呢? 這個問題我們待會再講。

記為問題1


e. Constructor<? extends Unbinder> 是個什么東西?

調用ButterKnife.bind(Activity target)方法后會返回一個Unbinder對象,可以在onDestroy中調用unbind()方法,那個Unbinder是什么東西呢?這個問題待會再講。

記為問題2.


f. clsName + "_ViewBinding"是什么類?

Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");

這個問題記為問題3.


2. ButterKnifeProcessor.java

這個類是ButterKnife里面很重要的一個類了,它繼承自AbstractProcessor。


看來不懂的問題越來越多,那么有必要來學習下Java注解的知識。



八. Java注解


在分析ButterKnife代碼前,需要了解Java的注解,需要了解Annotation Processor,因為ButterKnifer用到這個知識。


這里面很重要的一個知識點就是你可以編寫一定的規則,讓它在應用程序編譯時執行你的規則,然后生成Java代碼;并且生成的Java還可以參與編譯。


Java注解



九. 自己定義的注解框架


1. Eclipse實現


主要是參考這篇帖子完成的,大家可以參考這篇帖子:

Eclipse中使用Java注解Processor


主要說下不同的地方:

a. source folder的創建,直接File->New->source folder一直創建不成功,后面用另外一種方法創建成功了。

項目->右擊->Properties->Java Build Path->Source->Add Folder->Create New Folder->輸入resources/META-INF/services->finish->ok->ok

5. Android 框架ButterKnife源代碼分析



2. Android studio實現


AndroidStudio下面使用Java注解Processor




十. 調試自己的自定義框架

有個時候需要調試自己寫的框架是否正常運行,下面介紹下調試:


1. 在項目gradle.properties里面添加

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


2.Edit Configureations

5. Android 框架ButterKnife源代碼分析


3. 增加遠程調試

5. Android 框架ButterKnife源代碼分析


4. 啟動遠程調試

5. Android 框架ButterKnife源代碼分析



下面的控制臺會出現下面的提示:

Connected to the target VM, address: 'localhost:8011', transport: 'socket'


5. 打斷點

在Processor里面打上斷點,比如init, process


6. 連上手機,項目根目錄命令行下執行

gradle clean connectedCheck



十一. ButterKnife使用Java注解

理解了Java注解Processor之后,ButterKnife就比較好理解了。


首先它的ButterKnifeProcessor.java繼承自AbstractProcessor,重寫了init和process之類的方法。

也就是說它在編譯的時候會被執行,生成輔助代碼。


它的輔助代碼生成到哪里了呢?


在我們自己的應用程序里面搜索_ViewBinding,就可以找到已經生成好的輔助類,如下:

public class xxxx_ViewBinding<T extends LoginCloudActivity> extends BaseActivity_ViewBinding<T> {
  private View view2131689593;

  @UiThread
  public xxxx_ViewBinding(final T target, View source) {
    super(target, source);

    View view;
    target.mEtUser = Utils.findRequiredViewAsType(source, R.id.et_user, "field 'mEtUser'", EditText.class);
    target.mEtPwd = Utils.findRequiredViewAsType(source, R.id.et_pwd, "field 'mEtPwd'", EditText.class);
    view = Utils.findRequiredView(source, R.id.btn_login, "method 'btn_login' and method 'btn_login_long'");
    view2131689593 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.btn_login();
      }
    });
    view.setOnLongClickListener(new View.OnLongClickListener() {
      @Override
      public boolean onLongClick(View p0) {
        return target.btn_login_long();
      }
    });
  }

  @Override
  public void unbind() {
    T target = this.target;
    super.unbind();

    target.mEtUser = null;
    target.mEtPwd = null;

    view2131689593.setOnClickListener(null);
    view2131689593.setOnLongClickListener(null);
    view2131689593 = null;
  }
}


這個類在編譯的時候會被自動生成,那么在運行的時候,它會被調用。

這個類的構造函數會去初始化那些控件,設置監聽。


回到第七步 ButterKnife.bind()的過程


在createBinding的時候,它會初始化這個xxx_ViewBinding類,如下:

private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

    if (constructor == null) {
      return Unbinder.EMPTY;
    }

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      return constructor.newInstance(target, source);
      ...
      
  }


那么就會走到xxx_ViewBinding的構造函數,那么就會初始化控件,同時也會設置監聽。如下:

target.mEtUser = Utils.findRequiredViewAsType(source, R.id.et_user, "field 'mEtUser'", EditText.class);
    target.mEtPwd = Utils.findRequiredViewAsType(source, R.id.et_pwd, "field 'mEtPwd'", EditText.class);
    view = Utils.findRequiredView(source, R.id.btn_login, "method 'btn_login' and method 'btn_login_long'");
    view2131689593 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.btn_login();
      }
    });
    view.setOnLongClickListener(new View.OnLongClickListener() {
      @Override
      public boolean onLongClick(View p0) {
        return target.btn_login_long();
      }
    });



它的調用方式直接是target.mEtpwd,所以也就是說Activity的mEtpwd控件不能是private的,否則會引用不到。



參考網址:

butterknife github源代碼下載


ButterKnife源代碼解析


Java注解處理器分析


Eclipse中使用Java注解處理器


Android studio使用java注解處理器


JavaPoet介紹


調試Java注解處理器出錯


向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

泌阳县| 班戈县| 扎囊县| 太原市| 百色市| 泰顺县| 儋州市| 满洲里市| 五常市| 榆社县| 恩平市| 津南区| 张家港市| 江孜县| 葵青区| 金堂县| 乌拉特中旗| 名山县| 和政县| 伊通| 西吉县| 玛纳斯县| 革吉县| 元阳县| 绿春县| 北流市| 嫩江县| 凤山市| 保山市| 吉水县| 太谷县| 神木县| 来安县| 丰台区| 江北区| 札达县| 灌云县| 云安县| 铁力市| 富源县| 黄骅市|