首先,如果我们自己不想用setContentView()和findViewById的方式加载布局和查找控件,那思路必须是要有控件的id,然后通过反射加载方法,实现控件的初始化。
很自然,我们会在用到setContenView时使用一个我们自己写的方法,这个自己写的方法的参数要有布局的id,还要有activity,在通过反射实现即可,在想一下,如果在这个方法中,将所有成员变量view初始化,岂不更妙,思路就是通过反射获取field,再得到field的注解的属性值,在通过view = activity.findviewbyid(注解的value),field.set(activity,view)完成初始化。
通过这个例子开始理解
public class YunBooksActivity extends AppCompatActivity {
@ViewInject(R.id.gridView)
private GridView gridView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_yun_books);
x.view().inject(this);
}
}
我们看到gridview上加了个注解@ViewInject(R.id.gridView),通过上一篇博客,我们可以知道有一个注解类@ViewInject,它有一个值是R.id.gridView,很明显,是个整型。
再看x.view().inject(this),x.view()代码如下
public static ViewInjector view() {
if (Ext.viewInjector == null) {
ViewInjectorImpl.registerInstance();
}
return Ext.viewInjector;
}
注意这里的viewInjector不是@ViewInject,这是个接口,实现的类是ViewInjectorImpl。Ext是x的一个静态内部类,代码如下
public static class Ext {
private static boolean debug;
private static Application app;
private static TaskController taskController;
private static HttpManager httpManager;
private static ImageManager imageManager;
private static ViewInjector viewInjector;
private Ext() {
}
static {
TaskControllerImpl.registerInstance();
// 默认信任所有https域名
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
}
public static void init(Application app) {
if (Ext.app == null) {
Ext.app = app;
}
}
public static void setDebug(boolean debug) {
Ext.debug = debug;
}
public static void setTaskController(TaskController taskController) {
if (Ext.taskController == null) {
Ext.taskController = taskController;
}
}
public static void setHttpManager(HttpManager httpManager) {
Ext.httpManager = httpManager;
}
public static void setImageManager(ImageManager imageManager) {
Ext.imageManager = imageManager;
}
public static void setViewInjector(ViewInjector viewInjector) {
Ext.viewInjector = viewInjector;
}
}
可以看到Ext有个成员变量viewInject,
ViewInjectorImpl.registerInstance(); //就是给Ext的静态成员设置ViewInjector
再看ViewInjectorImpl.regiserInstance()
public static void registerInstance() {
if (instance == null) {
synchronized (lock) {
if (instance == null) {
instance = new ViewInjectorImpl();
}
}
}
x.Ext.setViewInjector(instance);
}
通过x.view()得到viewInjecor,之后是viewInjecorImpl.inject(this)
@Override
public void inject(Activity activity) {
//获取Activity的ContentView的注解
Class<?> handlerType = activity.getClass();
try {
ContentView contentView = findContentView(handlerType);
if (contentView != null) {
int viewId = contentView.value();
if (viewId > 0) {
Method setContentViewMethod = handlerType.getMethod("setContentView", int.class);
setContentViewMethod.invoke(activity, viewId);
}
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
injectObject(activity, handlerType, new ViewFinder(activity));
}
在这个方法中,我们看到了通过反射执行方法,setContentView,activity传进来了,再看viewId如何获取的,通过一个findContentView(handlerType)获取到contentView,大概可以知道ContentView也是一个自定义的注解,里面的属性value,就是布局的id。在这个演示的例子中,没有使用,想要使用,很简单在当前类加个注解@ContentView(R.layout.xxx),则在activity中就可以不用使用setContentView()了。再看这个方法,findContentView(handlerType)
/**
* 从父类获取注解View
*/
private static ContentView findContentView(Class<?> thisCls) {
if (thisCls == null || IGNORED.contains(thisCls)) {
return null;
}
ContentView contentView = thisCls.getAnnotation(ContentView.class);
if (contentView == null) {
return findContentView(thisCls.getSuperclass());
}
return contentView;
}
如果thisCls为空,返回空,如果IGNORED中包含thisCls,返回空,IGNORED包含的值有Object.class和Activity.class,因为这里使用了递归,如果当前activity没有使用@ContentView,判断父类有没有使用,如果有,获取返回,如果没有,一直递归直到父类是activity,同时为了方法不限于activity这个家族,还使用了Object,使该方法有更大的通用性。获取到这个contentview后,就通过反射调用setContentView设置布局。
接着有injectObject(activity,handlerType,new ViewFider(activity)),开始为成员变量view赋值
private static void injectObject(Object handler, Class<?> handlerType, ViewFinder finder) {
if (handlerType == null || IGNORED.contains(handlerType)) {
return;
}
// inject view
Field[] fields = handlerType.getDeclaredFields();
if (fields != null && fields.length > 0) {
for (Field field : fields) {
Class<?> fieldType = field.getType();
if ( // 不注入静态字段
Modifier.isStatic(field.getModifiers()) ||
// 不注入final字段
Modifier.isFinal(field.getModifiers()) ||
// 仅注入view字段
(!fieldType.isInterface() &&
!View.class.isAssignableFrom(fieldType))) {
continue;
}
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
try {
View view = finder.findViewById(viewInject.value(), viewInject.parentId());
if (view != null) {
field.setAccessible(true);
field.set(handler, view);
} else {
throw new RuntimeException("Invalid id(" + viewInject.value() + ") for @ViewInject!"
+ handlerType.getSimpleName());
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
}
}
}
这里就是得到每个控件的注解,得到id值,通过反射给控件设置值,field.set(handler,view),这个view又是如何获取的?也很简单,在类ViewFinder中
public View findViewById(int id, int pid) {
View pView = null;
if (pid > 0) {
pView = this.findViewById(pid);
}
View view = null;
if (pView != null) {
view = pView.findViewById(id);
} else {
view = this.findViewById(id);
}
return view;
}
如果有父控件,先获取父控件,在获取view,否则直接获取view。
直到这里获取控件结束,初始化布局初步完成,当然在
private static void injectObject(Object handler, Class<?> handlerType, ViewFinder finder)
方法中还有其它的操作,比如完成带注解的方法,此次不在讨论,只用了解加载布局和控件的流程即可
下面是ContentView和ViewInject代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
int value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
int value();
/* parent view id */
int parentId() default 0;
}
其实还是调用了setContView和findviewbyid,只是通过了一种框架的方式,简化了开发时的代码量,更简洁。