Android中布局通过xutils注解方式源码剖析

来源:互联网 发布:亚加装饰 知乎 编辑:程序博客网 时间:2024/06/04 18:37

  首先,如果我们自己不想用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,只是通过了一种框架的方式,简化了开发时的代码量,更简洁。

1 0