Android注解入门 并来自己写一个框架

Posted 不会写代码的丝丽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android注解入门 并来自己写一个框架相关的知识,希望对你有一定的参考价值。

介绍

这里我带大家来学习一下注解 并且用来写下一个模仿xUtils3 中View框架
此框架 可以省略activity或者fragment的 findViewById 或者设置点击事件的烦恼
我正参加2016CSDN博客之星的比赛 希望您能投下宝贵的一票,点击进入投票
我的github上的源码,包含doc和使用说明

如下代码:

fragment

package a.fmy.com.myapplication;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import a.fmy.com.mylibrary.FmyClickView;
import a.fmy.com.mylibrary.FmyContentView;
import a.fmy.com.mylibrary.FmyViewInject;
import a.fmy.com.mylibrary.FmyViewView;

//你的fragment的布局id  Your fragment's LayoutId
@FmyContentView(R.layout.fragment_blank)
public class BlankFragment extends Fragment {
    //你想实例化控件的id
    //Do you want to control instance id
    // 等价于 findViewByid
    //Equivalent to the findViewByid
    @FmyViewView(R.id.tv1)
    TextView tv1;
    @FmyViewView(R.id.tv2)
    TextView tv2;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
       //初始化fragment Initialize Fragement
        return FmyViewInject.injectfragment(this,inflater,container);
    }
    //你想给哪个控件添加 添加事件 的id
    //Do you want to add add event id to which controls
    @FmyClickView({R.id.tv1,R.id.tv2})
    public void myOnclick(View view){
        switch (view.getId()) {
            case R.id.tv1:
                tv1.setText("TV1  "+Math.random()*100);
                break;
            case R.id.tv2:
                tv2.setText("TV2  "+Math.random()*100);
                break;
            default:

        }

    }
}

Activity

package a.fmy.com.myapplication;

import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.widget.FrameLayout;
import a.fmy.com.mylibrary.FmyContentView;
import a.fmy.com.mylibrary.FmyViewInject;
import a.fmy.com.mylibrary.FmyViewView;

@FmyContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {

    @FmyViewView(R.id.fl)
    FrameLayout fl;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //initActivity
        // 初始化activity
        FmyViewInject.inject(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        fragmentTransaction.add(R.id.fl,new BlankFragment());
        fragmentTransaction.commit();
    }
}

java注解学习

java注解教学大家点击进入大致的看一下即可 不然我不知道这篇博客需要写多久

activity设置填充布局框架

这里我们先写一个用于activity框架 你学习完了之后其实你也会fragment了.
1. 实现activity不需要调用setContentView(R.layout.activity_main);此方法完成布局填充 我们看下效果
不使用框架:

package a.fmy.com.mylibrary;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

使用框架:

package a.fmy.com.mylibrary;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
@FmyContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
            FmyViewInject.inject(this);
    }
}

第一步:
创建一个注解类如下
@Target —>>此注解在什么地方可以使用 如类还是变量
ElementType.TYPE只能在类中使用此注解
@Retention(RetentionPolicy.RUNTIME) 注解可以在运行时通过反射获取一些信息(这里如果你疑惑那么请六个悬念继续向下看)

/**
 * 此方注解写于activity类上 可以免去 setContentView()步骤 
 * @author 范明毅
 * @version 1.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FmyContentView {  
    /**
     * 保存布局文件的id eg:R.layout.main
     * @return 返回 布局id
     */
    int value();
}

第二步:
写一个工具类 配合注解使用 当开发者使用此类时激活注解的作用

public class FmyViewInject {
    /**
     * 保存传入的activity
     */
    private static Class<?> activityClass;
    /**
     * 初始化activity和所有注解
     * 
     * @param obj
     *            你需要初始化的activity
     */
    public static void inject(Object obj) {
    }

    /**
     * 初始化activity布局文件 让其不用调用setContentView
     * 
     * @param activity
     */
    private static void injectContent(Object obj) {
    }
}

大家先不用着急看不懂为什么这样写原因

核心源码位于injectContent 我们来实现此方法

    /**
     * 初始化activity布局文件 让其不用调用setContentView
     * 
     * @param activity
     */
    private static void injectContent(Object obj) {

        // 获取注解
        FmyContentView annotation = activityClass
                .getAnnotation(FmyContentView.class);

        if (annotation != null) {
            // 获取注解中的对应的布局id 因为注解只有个方法 所以@XXX(YYY)时会自动赋值给注解类唯一的方法
            int id = annotation.value();
            try {
                // 得到activity中的方法 第一个参数为方法名 第二个为可变参数 类型为 参数类型的字节码
                Method method = activityClass.getMethod("setContentView",
                        int.class);

                // 调用方法 第一个参数为哪个实例去掉用 第二个参数为 参数
                method.invoke(obj, id);
            } catch (Exception e) {

                e.printStackTrace();
            }
        }

此方法写完后工具类的inject()方法调用即可

    /**
     * 初始化activity和所有注解
     * 
     * @param obj
     *            你需要初始化的activity
     */
    public static void inject(Object obj) {
        activityClass = obj.getClass();
        // 初始化activity布局文件
        injectContent(obj);
    }

完整代码:

public class FmyViewInject {
    /**
     * 保存传入的activity
     */
    private static Class<?> activityClass;
    /**
     * 初始化activity和所有注解
     * 
     * @param obj
     *            你需要初始化的activity
     */
    public static void inject(Object obj) {
        activityClass = obj.getClass();
        // 初始化activity布局文件
        injectContent(obj);
    }
    /**
     * 初始化activity布局文件 让其不用调用setContentView
     * 
     * @param activity
     */
    private static void injectContent(Object obj) {

        // 获取注解
        FmyContentView annotation = activityClass
                .getAnnotation(FmyContentView.class);

        if (annotation != null) {
            // 获取注解中的对应的布局id 因为注解只有个方法 所以@XXX(YYY)时会自动赋值给注解类唯一的方法
            int id = annotation.value();
            try {
                // 得到activity中的方法 第一个参数为方法名 第二个为可变参数 类型为 参数类型的字节码
                Method method = activityClass.getMethod("setContentView",
                        int.class);

                // 调用方法 第一个参数为哪个实例去掉用 第二个参数为 参数
                method.invoke(obj, id);
            } catch (Exception e) {

                e.printStackTrace();
            }
        }
}

赶快去试试 我们继续写下一步 用法在开始的示例有

activity查找控件

效果如下

@FmyContentView(R.layout.activity_main)
public class MainActivity extends FragmentActivity {
    //直接实例化
    @FmyViewView(R.id.fl)
    private FrameLayout fl;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FmyViewInject.inject(this);


    }
    }

第一步:
继续写一个注解

/**
 * 此方注解写于activity类中 控件变量上 可以省去findViewId 的烦恼
 * @author 范明毅
 * @version 1.0
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FmyViewView {
    /**
     * 保存view控件的id
     * @return view控件id
     */
    int value();
}

第二步 继续第一节的”activity设置填充布局框架”中的工具类添加新的方法

 /**
     * 初始化activity中的所有view控件 让其不用一个findViewid 实例化
     *
     * @param activity
     */
    private static void injectView(Object activityOrFragment) {

        // 对象所有的属性
        Field[] declaredFields = null;


        // 健壮性
        if (activityClass != null) {
            // 获取du所有的属性 包含私有 保护 默认 共开 但不包含继承等
            // getFields可以获取到所有公开的包括继承的 但无法获取到私有的属性
            declaredFields = activityClass.getDeclaredFields();
        }


        // 健壮性
        if (declaredFields != null) {
            // 遍历所有的属性变量
            for (Field field : declaredFields) {

                // 获取属性变量上的注解
                FmyViewView annotation = field.getAnnotation(FmyViewView.class);

                // 如果此属性变量 包含FMYViewView
                if (annotation != null) {
                    // 获取属性id值
                    int id = annotation.value();

                    Object obj = null;
                    try {

                        // 获取activity中方法
                        obj = activityClass.getMethod("findViewById",
                                int.class).invoke(activityOrFragment, id);


                        Log.e("FMY", "" + field.getClass());
                        // 设置属性变量 指向实例

                        // 如果修饰符不为公共类 这里注意了 当activity
                        // 控件变量为private的时候 我们去访问会失败的 要么打破封装系 要么变量改为public
                        //如 private TextView tv 这种情况 如果不打破封装会直接异常
                        if (Modifier.PUBLIC != field.getModifiers()) {
                            // 打破封装性
                            field.setAccessible(true);
                        }
                        // 这里相当于 field= acitivity.obj
                        field.set(activityOrFragment, obj);
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                }
            }
        }

    }

第三步
在工具类中的inject ()方法调用

    /**
     * 初始化activity和所有注解
     *
     * @param obj 你需要初始化的activity
     */
    public static void inject(Object obj) {

        activityClass = obj.getClass();

        // 初始化activity布局文件
        injectContent(obj);

        // 初始化所有控件实例 省去findViewId的痛苦
        injectView(obj);

    }

activity设置控件的点击事件

这里需要的知识点 如动态代理等 这里大家可以自己百度看下
效果如下

@FmyContentView(R.layout.activity_main)
public class MainActivity extends FragmentActivity {

    @FmyViewView(R.id.fl)
    private FrameLayout fl;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FmyViewInject.inject(this);


    }

    //当填充的布局中 id为R.id.fl 被点击将调用如下方法
    @FmyClickView({R.id.fl})
    public void onClick(View v){
        Log.e("fmy", "===>>");
    }
}

第一步 :
同样写下一个注解

/**
 * 
 * 设置点击事件的注解 只需要在某方法 上写上此注解即可 如@FmyClickView({R.id.bt1,R.id.bt2})
 * @version 1.0
 * @author 范明毅
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FmyClickView {
    /**
     * 保存所有需要设置点击事件控件的id
     * @return 
     */
    int [] value();
}

第二步:
写下一个代理处理类(我写在工具类中)

/**
     * 代理处理点击逻辑代码
     * 
     * @author 范明毅
     *
     */
    static class MInvocationHandler implements InvocationHandler {
        //这里我们到时候回传入activity
        private Object target;

        // 用户自定义view 的点击事件方法
        private Method method;

        public MInvocationHandler(Object target, java.lang.reflect.Method method) {
            super();
            this.target = target;
            this.method = method;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            // 调用用户自定义方法的点击事件 让activity调用中开发者设定的方法 
            return this.method.invoke(target, args);
        }

    }

第三步:
在工具类中写下一个方法用于初始化点击事件

    /**
     * 初始化所有控件的点击事件 只需要某方法上写上对应注解和id即可
     * 
     * @param activity
     */
    private static void inijectOnClick(Object activityOrFragment) {

        //获得所有方法
        Method[] methods  = null;


             methods = activityClass.getMethods();



        // 遍历所有的activity下的方法
        for (Method method : methods) {
            // 获取方法的注解
            FmyClickView fmyClickView = method
                    .getAnnotation(FmyClickView.class);
            // 如果存在此注解
            if (fmyClickView != null) {

                // 所有注解的控件的id
                int[] ids = fmyClickView.value();

                // 代理处理类
                MInvocationHandler handler = new MInvocationHandler(activityOrFragment,
                        method);

                // 代理实例 这里也可以返回     new Class<?>[] { View.OnClickListener.class }中的接口类
                //第一个参数用于加载其他类 不一定要使用View.OnClickListener.class.getClassLoader() 你可以使用其他的
                //第二个参数你所实现的接口
                Object newProxyInstance = Proxy.newProxyInstance(
                        View.OnClickListener.class.getClassLoader(),
                        new Class<?>[] { View.OnClickListener.class }, handler);

                // 遍历所有的控件id 然后设置代理
                for (int i : ids) {
                    try {
                        Object view = null;

                    //如果对象是activity

                             view = activityClass.getMethod("findViewById",
                                        int.class).invoke(activityOrFragment, i);


                        if (view != null) {
                            Method method2 = view.getClass().getMethod(
                                    "setOnClickListener",
                                    View.OnClickListener.class);
                            method2.invoke(view, newProxyInstance);
                        }
                    } catch (Exception e) {

                        e.printStackTrace();
                    }

                }

            }
        }

    }

第四部:
在工具类的inject()调用即可


    /**
     * 初始化activity和所有注解
     * 
     * @param obj
     *            你需要初始化的activity
     */
    public static void inject(Object obj) {

        activityClass = obj.getClass();

        // 初始化activity布局文件
        injectContent(obj);

        // 初始化所有控件实例 省去findViewId的痛苦
        injectView(obj);

        // 初始化所有控件的点击事件
        inijectOnClick(obj);
    }

以上是关于Android注解入门 并来自己写一个框架的主要内容,如果未能解决你的问题,请参考以下文章

想自己写框架?不会写Java注解可不行

Android编程入门--开源框架ButterKnife

android中利用注解来实现findViewById

告别XML,Android新声明式UI框架《Jetpack Compose入门到精通》最全开发指南

Android进阶之光学习记录——注解与依赖注入框架ButterKnife的尝试

Android进阶之光学习记录——注解与依赖注入框架ButterKnife的尝试