簡介
fragmentargs可以用來處理Fragment屬性的保存(Fragment.setArguments(Bundle bundle)
)和自動賦值(Fragment.getArguments()
)邏輯,以在編譯時自動生成源代碼的方式來減少一些重復代碼的編寫,可以查看這篇博文了解一下
流程圖
簡單的流程圖
編譯時注解
由于運行時注解
用到了反射
技術,在性能方面會有影響,而編譯時注解
是在編譯期生成源代碼的方式,性能會好一些
如何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
信息基本上不用做任何更改,只要確保端口號跟上一個步驟的一致就行
3、在自定義的
annotation processor
設置斷點,啟動remote debugger
,在命令行中運行./gradlew clean assembleDebug
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
,然后調用AutoFragmentArgInjector
的AutoFragmentArgInjector.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并不能用在工作項目中,只是測試代碼??