<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                [TOC] 不知不覺,筆者已經對Android主流三方庫中的網絡框架OkHttp、Retrofit,圖片加載框架Glide、數據庫框架GreenDao、響應式編程框架RxJava、內存泄露框架LeakCanary進行了詳細的分析,如果有朋友對這些開源框架的內部實現機制感興趣的話,可以在筆者的個人主頁選擇相應的文章閱讀。這篇,我將會對Android中的依賴注入框架ButterKnife的源碼實現機制進行詳細地講解。 ### 一、簡單示例 首先,我們先來看一下ButterKnife的基本使用(取自[Awesome-WanAndroid](https://github.com/JsonChao/Awesome-WanAndroid)),如下所示: ~~~ public class CollectFragment extends BaseRootFragment<CollectPresenter> implements CollectContract.View { @BindView(R.id.normal_view) SmartRefreshLayout mRefreshLayout; @BindView(R.id.collect_recycler_view) RecyclerView mRecyclerView; @BindView(R.id.collect_floating_action_btn) FloatingActionButton mFloatingActionButton; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(getLayoutId(), container, false); unBinder = ButterKnife.bind(this, view); initView(); return view; } @OnClick({R.id.collect_floating_action_btn}) void onClick(View view) { switch (view.getId()) { case R.id.collect_floating_action_btn: mRecyclerView.smoothScrollToPosition(0); break; default: break; } } @Override public void onDestroyView() { super.onDestroyView(); if (unBinder != null && unBinder != Unbinder.EMPTY) { unBinder.unbind(); unBinder = null; } } ~~~ 可以看到,我們使用了@BindView()替代了findViewById()方法,然后使用了@OnClick替代了setOnClickListener()方法。ButterKnife的初期版本是通過使用注解+反射這樣的運行時解析的方式實現上述功能的,后面,為了改善性能,便使用了**注解+APT編譯時解析技術并從中生成配套模板代碼的方式**來實現。 在開始分析之前,可能有同學對APT不是很了解,我這里普及一下,APT是Annotation Processing Tool的縮寫,即注解處理工具。它的使用步驟通常為如下三個步驟: * 1、**首先,聲明注解的生命周期為CLASS,即@Retention(CLASS)**。 * 2、**然后,通過繼承AbstractProcessor自定義一個注解處理器**。 * 3、**最后,在編譯的時候,編譯器會掃描所有帶有你要處理的注解的類,最后再調用AbstractProcessor的process方法,對注解進行處理**。 下面,我們正式來解剖一下ButterKnife的心臟。 ### 二、源碼分析 #### 1、模板代碼解析 首先,在我們編寫好上述的示例代碼之后,調用 gradle build 命令,在app/build/generated/source/apt下將可以找到APT為我們生產的配套模板代碼CollectFragment\_ViewBinding,如下所示: ~~~ public class CollectFragment_ViewBinding implements Unbinder { private CollectFragment target; private View view2131230812; @UiThread public CollectFragment_ViewBinding(final CollectFragment target, View source) { this.target = target; View view; // 1 target.mRefreshLayout = Utils.findRequiredViewAsType(source, R.id.normal_view, "field 'mRefreshLayout'", SmartRefreshLayout.class); target.mRecyclerView = Utils.findRequiredViewAsType(source, R.id.collect_recycler_view, "field 'mRecyclerView'", RecyclerView.class); view = Utils.findRequiredView(source, R.id.collect_floating_action_btn, "field 'mFloatingActionButton' and method 'onClick'"); target.mFloatingActionButton = Utils.castView(view, R.id.collect_floating_action_btn, "field 'mFloatingActionButton'", FloatingActionButton.class); view2131230812 = view; // 2 view.setOnClickListener(new DebouncingOnClickListener() { @Override public void doClick(View p0) { target.onClick(p0); } }); } @Override @CallSuper public void unbind() { CollectFragment target = this.target; if (target == null) throw newIllegalStateException("Bindings already cleared."); this.target = null; target.mRefreshLayout = null; target.mRecyclerView = null; target.mFloatingActionButton = null; view2131230812.setOnClickListener(null); view2131230812 = null; } } ~~~ 生成的配套模板CollectFragment\_ViewBinding中,在注釋1處,使用了ButterKnife內部的工具類Utils的findRequiredViewAsType()方法來尋找控件。在注釋2處,使用了view的setOnClickListener()方法來添加了一個去抖動的DebouncingOnClickListener,這樣便可以防止重復點擊,在重寫的doClick()方法內部,直接調用了CollectFragment的onClick方法。最后,我們在深入看下Utils的findRequiredViewAsType()方法內部的實現。 ~~~ public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who, Class<T> cls) { // 1 View view = findRequiredView(source, id, who); // 2 return castView(view, id, who, cls); } public static View findRequiredView(View source, @IdRes int id, String who) { View view = source.findViewById(id); if (view != null) { return view; } ... } public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) { try { return cls.cast(view); } catch (ClassCastException e) { ... } } ~~~ 在注釋1處,**最終也是通過View的findViewById()方法找到相應的控件**,在注釋2處,**通過相應Class對象的cast方法強轉成對應的控件類型**。 #### 2、ButterKnife 是怎樣實現代碼注入的 接下來,為了使用這套模板代碼,我們必須調用ButterKnife的bind()方法實現代碼注入,即自動幫我們執行重復繁瑣的findViewById和setOnClicklistener操作。下面我們來分析下bind()方法是如何實現注入的。 ~~~ @NonNull @UiThread public static Unbinder bind(@NonNull Object target, @NonNull View source) { return createBinding(target, source); } ~~~ 在bind()方法中調用了createBinding(), ~~~ @NonNull @UiThread public static Unbinder bind(@NonNull Object target, @NonNull View source) { Class<?> targetClass = target.getClass(); // 1 Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass); if (constructor == null) { return Unbinder.EMPTY; } try { // 2 return constructor.newInstance(target, source); // 3 } catch (IllegalAccessException e) { ... } ~~~ 首先,在注釋1處,通過 findBindingConstructorForClass() 方法從 Class 中查找 constructor,這里constructor即上文生成的CollectFragment\_ViewBinding類。然后,在注釋2處,**利用反射來新建 constructor 對象**。最后,如果新建 constructor 對象失敗,則會在注釋3后面捕獲一系列對應的異常進行自定義異常拋出處理。 下面,我們來詳細分析下 findBindingConstructorForClass() 方法的實現邏輯。 ~~~ @VisibleForTesting static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>(); @Nullable @CheckResult @UiThread private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) { // 1 Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls); if (bindingCtor != null || BINDINGS.containsKey(cls)) { return bindingCtor; } // 2 String clsName = cls.getName(); if (clsName.startsWith("android.") || clsName.startsWith("java.") || clsName.startsWith("androidx.")) { return null; } try { // 3 Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding"); bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class); } catch (ClassNotFoundException e) { // 4 bindingCtor = findBindingConstructorForClass(cls.getSuperclass()); } catch (NoSuchMethodException e) { throw new RuntimeException("Unable to find binding constructor for " + clsName, e); } // 5 BINDINGS.put(cls, bindingCtor); return bindingCtor; } ~~~ 這里,我把多余的log代碼刪除并把代碼格式優化了一下,可以看到,findBindingConstructorForClass() 這個方法中的邏輯瞬間清晰不少,這里**建議以后大家自己在分析源碼的時候可以進行這樣的優化重整**,會帶來不少好處。 重新看到 findBindingConstructorForClass() 方法,在注釋1處,我們首先從緩存BINDINGS中獲取CollectFragment類對象對應的模塊類CollectFragment\_ViewBinding的構造器對象,這里的BINDINGS是一個LinkedHashMap對象,它保存了上述兩者的映射關系。在注釋2處,如果是 android,androidx,java 原生的文件,不進行處理。在注釋3處,先**通過CollectFragment類對象的類加載器加載出對應的模塊類CollectFragment\_ViewBinding的類對象**,再通過自身的getConstructor()方法獲得相應的構造對象。如果在步驟3中加載不出對應的模板類對象,則會在注釋4處使用類似遞歸的方法重新執行findBindingConstructorForClass()方法。最后,如果找到了bindingCtor模板構造對象,則將它保存在BINDINGS這個LinkedHashMap對象中。 **這里總結一下findBindingConstructorForClass()方法的處理:** * 1、**首先從緩存BINDINGS中獲取CollectFragment類對象對應的模塊類CollectFragment\_ViewBinding的構造器對象,獲取不到,則繼續執行下面的操作**。 * 2、**如果不是android,androidx,java 原生的文件,再進行后面的處理**。 * 3、**通過CollectFragment類對象的類加載器加載出對應的模塊類CollectFragment\_ViewBinding的類對象,再通過自身的getConstructor()方法獲得相應的構造對象,如果獲取不到,會拋出異常,在異常的處理中,我們會從當前 class 文件的父類中再去查找。如果找到了,最后會將bindingCtor對象緩存進在BINDINGS對象中**。 #### 3、ButterKnife是如何在編譯時生成代碼的? 在編譯的時候,ButterKnife會通過自定義的注解處理器ButterKnifeProcessor的process方法,對編譯器掃描到的要處理的類中的注解進行處理,然后,**通過javapoet這個庫來動態生成綁定事件或者控件的模板代碼**,最后在運行的時候,直接調用bind方法完成綁定即可。 首先,我們先來分析下ButterKnifeProcessor的重寫的入口方法init()。 ~~~ @Override public synchronized void init(ProcessingEnvironment env) { super.init(env); String sdk = env.getOptions().get(OPTION_SDK_INT); if (sdk != null) { try { this.sdk = Integer.parseInt(sdk); } catch (NumberFormatException e) { ... } } typeUtils = env.getTypeUtils(); filer = env.getFiler(); ... } ~~~ 可以看到,**ProcessingEnviroment對象提供了兩大工具類 typeUtils和filer。typeUtils的作用是用來處理TypeMirror,而Filer則是用來創建生成輔助文件**。 接著,我們再來看看被重寫的getSupportedAnnotationTypes()方法,這個方法的作用主要是用于指定ButterknifeProcessor注冊了哪些注解的。 ~~~ @Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new LinkedHashSet<>(); for (Class<? extends Annotation> annotation : getSupportedAnnotations()) { types.add(annotation.getCanonicalName()); } return types; } ~~~ 這里面首先創建了一個LinkedHashSet對象,然后將getSupportedAnnotations()方法返回的支持注解集合進行遍歷一一并添加到types中返回。 接著我們看下getSupportedAnnotations()方法, ~~~ private Set<Class<? extends Annotation>> getSupportedAnnotations() { Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>(); annotations.add(BindAnim.class); annotations.add(BindArray.class); annotations.add(BindBitmap.class); annotations.add(BindBool.class); annotations.add(BindColor.class); annotations.add(BindDimen.class); annotations.add(BindDrawable.class); annotations.add(BindFloat.class); annotations.add(BindFont.class); annotations.add(BindInt.class); annotations.add(BindString.class); annotations.add(BindView.class); annotations.add(BindViews.class); annotations.addAll(LISTENERS); return annotations; } ~~~ 可以看到,這里注冊了一系列的Bindxxx注解類和監聽列表LISTENERS,接著看一下LISTENERS中包含的監聽方法: ~~~ private static final List<Class<? extends Annotation>> LISTENERS = Arrays.asList( OnCheckedChanged.class, OnClick.class, OnEditorAction.class, OnFocusChange.class, OnItemClick.class, OnItemLongClick.class, OnItemSelected.class, OnLongClick.class, OnPageChange.class, OnTextChanged.class, OnTouch.class ); ~~~ 最后,我們來分析下整個ButterKnifeProcessor中最關鍵的方法process()。 ~~~ @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { // 1 Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env); for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingSet binding = entry.getValue(); // 2 JavaFile javaFile = binding.brewJava(sdk, debuggable); try { javaFile.writeTo(filer); } catch (IOException e) { ... } } return false; } ~~~ 首先,在注釋1處通過**findAndParseTargets()方法**,知名見義,它應該就是**找到并解析注解目標的關鍵方法**了,繼續看看它內部的處理: ~~~ private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) { Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>(); Set<TypeElement> erasedTargetNames = new LinkedHashSet<>(); // 1、一系列處理每一個@Bindxxx元素的for循環代碼塊 ... // Process each @BindView element. for (Element element : env.getElementsAnnotatedWith(BindView.class)) { try { // 2 parseBindView(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindView.class, e); } } // Process each @BindViews element. ... // Process each annotation that corresponds to a listener. for (Class<? extends Annotation> listener : LISTENERS) { findAndParseListener(env, listener, builderMap, erasedTargetNames); } // 2 Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries = new ArrayDeque<>(builderMap.entrySet()); Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>(); while (!entries.isEmpty()) { Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst(); TypeElement type = entry.getKey(); BindingSet.Builder builder = entry.getValue(); TypeElement parentType = findParentType(type, erasedTargetNames); if (parentType == null) { bindingMap.put(type, builder.build()); } else { BindingSet parentBinding = bindingMap.get(parentType); if (parentBinding != null) { builder.setParent(parentBinding); bindingMap.put(type, builder.build()); } else { entries.addLast(entry); } } } return bindingMap; } ~~~ findAndParseTargets()方法的代碼非常多,我這里盡可能做了精簡。首先,在注釋1處,**掃描并處理所有具有@Bindxxx注解和符合LISTENERS監聽方法集合的代碼,然后在每一個@Bindxxx對應的for循環代碼中的parseBindxxx()或findAndParseListener()方法中將解析出的信息放入builderMap這個LinkedHashMap對象中**,其中builderMap是一個key為TypeElement,value為BindingSet.Builder的映射集合,這個 BindSet 是指**的一個類型請求的所有綁定的集合**。在注釋3處,首先使用上面的builderMap對象去構建了一個entries對象,它是一個雙向隊列,能實現兩端存取的操作。接著,又新建了一個key為TypeElement,value為BindingSet的LinkedHashMap對象,最后使用了一個while循環從entries的第一個元素開始,這里會判斷當前元素類型是否有父類,如果沒有,直接構建builder放入bindingMap中,如果有,則將parentBinding添加到BindingSet.Builder這個建造者對象中,然后再創建BindingSet再添加到bindingMap中。 接著,我們分析下注釋2處parseBindView是如何對每一個@BindView注解的元素進行處理。 ~~~ private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) { TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // 1、首先驗證生成的常見代碼限制 ... // 2、驗證目標類型是否繼承自View。 ... // 3 int id = element.getAnnotation(BindView.class).value(); BindingSet.Builder builder = builderMap.get(enclosingElement); Id resourceId = elementToId(element, BindView.class, id); if (builder != null) { String existingBindingName = builder.findExistingBindingName(resourceId); if (existingBindingName != null) { ... return; } } else { // 4 builder = getOrCreateBindingBuilder(builderMap, enclosingElement); } String name = simpleName.toString(); TypeName type = TypeName.get(elementType); boolean required = isFieldRequired(element); // 5 builder.addField(resourceId, new FieldViewBinding(name, type, required)); // Add the type-erased version to the valid binding targets set. erasedTargetNames.add(enclosingElement); } ~~~ 首先,在注釋1、2處均是一些驗證處理操作,如果不符合則會return。然后,我們看到注釋3處,這里獲取了BindView要綁定的View的id,然后先從builderMap中獲取BindingSet.Builder對象,如果存在,直接return。如果不存在,則會在注釋4處的 getOrCreateBindingBuilder()方法生成一個。我們看一下getOrCreateBindingBuilder()方法: ~~~ private BindingSet.Builder getOrCreateBindingBuilder( Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) { BindingSet.Builder builder = builderMap.get(enclosingElement); if (builder == null) { builder = BindingSet.newBuilder(enclosingElement); builderMap.put(enclosingElement, builder); } return builder; } ~~~ 可以看到,這里會再次從buildMap中獲取BindingSet.Builder對象,如果沒有則直接調用BindingSet的newBuilder()方法新建一個BindingSet.Builder對象并保存在builderMap中,然后,再將新建的builder對象返回。 回到parseBindView()方法的注釋5處,這里根據view的信息生成一個FieldViewBinding,最后添加到上邊生成的builder對象中。 最后,再回到我們的process()方法中,現在**所有的綁定的集合數據都放在了bindingMap對象中,這里使用for循環取出每一個BindingSet對象,調用它的brewJava()方法**,看看它內部的處理: ~~~ JavaFile brewJava(int sdk, boolean debuggable) { TypeSpec bindingConfiguration = createType(sdk, debuggable); return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration) .addFileComment("Generated code from Butter Knife. Do not modify!") .build(); } private TypeSpec createType(int sdk, boolean debuggable) { TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName()) .addModifiers(PUBLIC); if (isFinal) { result.addModifiers(FINAL); } if (parentBinding != null) { result.superclass(parentBinding.bindingClassName); } else { result.addSuperinterface(UNBINDER); } if (hasTargetField()) { result.addField(targetTypeName, "target", PRIVATE); } if (isView) { result.addMethod(createBindingConstructorForView()); } else if (isActivity) { result.addMethod(createBindingConstructorForActivity()); } else if (isDialog) { result.addMethod(createBindingConstructorForDialog()); } if (!constructorNeedsView()) { // Add a delegating constructor with a target type + view signature for reflective use. result.addMethod(createBindingViewDelegateConstructor()); } result.addMethod(createBindingConstructor(sdk, debuggable)); if (hasViewBindings() || parentBinding == null) { result.addMethod(createBindingUnbindMethod(result)); } return result.build(); } ~~~ 在createType()方法里面使用了java中的[javapoet](https://github.com/square/javapoet)技術生成了一個bindingConfiguration對象,很顯然,它里面**保存了所有的綁定配置信息。然后,通過javapoet的builder構造器將上面得到的bindingConfiguration對象構建生成一個JavaFile對象,最終,通過javaFile.writeTo(filer)生成了java源文件**。 至此,ButterKnife的源碼分析就結束了。 ### 三、總結 從上面的源碼分析來看,ButterKnife的執行流程總體可以分為如下兩步: * 1、**在編譯的時候掃描注解,并通過自定義的ButterKnifeProcessor做相應的處理解析得到bindingMap對象,最后,調用 javapoet 庫生成java模板代碼**。 * 2、**當我們調用 ButterKnife的bind()方法的時候,它會根據類的全限定類型,找到相應的模板代碼,并在其中完成 findViewById 和 setOnClick ,setOnLongClick 等操作**。 接下來,筆者會對Android中的依賴注入框架Dagger2的源碼實現流程進行詳細的講解,敬請期待~ ##### 參考鏈接: * * * 1、ButterKnife V10.0.0 源碼 2、Android進階之光 3、[ButterKnife源碼分析](https://www.jianshu.com/p/0f3f4f7ca505) 4、[butterknife 源碼分析](https://blog.csdn.net/gdutxiaoxu/article/details/71512754) 鏈接:https://juejin.im/post/5e55d38d518825491753ae39
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看