关闭

[置顶] android注解入门 并来自己写一个框架

3598人阅读 评论(4) 收藏 举报
分类:

介绍

这里我带大家来学习一下注解 并且用来写下一个模仿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);
    }
4
0
查看评论

打造自己的注解框架

打造自己的注解框架 github上有很多注解框架,加个注解就会完成各种各样的任务,这样的方法 既减少了代码量,又方便项目的统一管理,清晰明了 今天,我就用java中的反射+自定义注解,为大家简单介绍下这种框架的设计思路,并和大家一起实现3个比较常用的功能 首先给大家简单说下反...
  • u012926924
  • u012926924
  • 2015-09-04 10:06
  • 916

【Android开发经验】来,咱们自己写一个Android的IOC框架!

到目前位置,afinal开发框架也是用了hao'chang'shi
  • bz419927089
  • bz419927089
  • 2014-07-18 14:54
  • 2771

教你自己写Android第三方库

其实Android studio的出现很大程度上方便了我们Android开发人员,今天我们说说怎么构建我们自己的库。 依次按File->New Moudle->android Library就好了,这就构建了我们自己的第三方库,正好最近项目用到了沉侵式状态栏,我们就以这个未为例子来说明吧...
  • xiangzhihong8
  • xiangzhihong8
  • 2016-08-18 10:45
  • 1829

《自己动手写框架1》:缘起

和很多的朋友一样,在没有开发框架之前,可能我们只有一些落在纸面上的编码规范,或者说是开发约定。但不管怎么说,这些规范和约定对于我们软件开发过程还是有相当的指导与规范作用的。再后来,随着时间的推移,我们的软件代码写得越来越多,它们中的一部分具有相当的通用性,可能就会变成一个公共库;它们其中的一部分,在...
  • cndes
  • cndes
  • 2015-06-04 13:07
  • 2616

Android注解原理与常用注解框架

Android注解原理与常用注解框架 注解基本介绍 编写注解 Android注解框架 注解的基本介绍 注解的定义 能够添加到 Java 源代码的语法元数据。类、方法、变量、参数、包都可以被注解,可用来将信息元数据与程序元素进行关联,常见的注解如@Override, @Deprecated。 元注解...
  • k1457047898
  • k1457047898
  • 2016-09-18 17:19
  • 2908

3.自己动手写Java Web框架-MVC初体验

jw的github地址是https://github.com/menyouping/jw第一次看到这篇文章时非常惊讶,介绍FastPHP如何实现一个简易的MVC。我的大脑中总是将Servlet与Spring MVC割裂开来,总认为是两个事物,看到这篇文章后茅塞顿开!Java版的MVC,我也可以玩了!...
  • beautiful5200
  • beautiful5200
  • 2016-09-16 22:53
  • 2088

@深入注解,在Java中设计和使用自己的注解

我们用过 JDK给我们提供的  @Override  @Deprecated @SuppressWarning 注解  ,这些注解是JDK给我们提供的 ,我们只是在用别人写好的东西 ,那么我们是否可以自己写注解呢?当然是可以的  。 我们写的注解包括三个环节...
  • yue7603835
  • yue7603835
  • 2012-02-05 15:16
  • 6350

自已动手写ORM框架(1)——幼儿园

自已动手写ORM框架(1)——幼儿园     ORM即Object,Relation,Mapping的缩写,什么意思呢?先从一个例子说起。     现在需要开发一个学生成绩管理系统,要求可以进行信息的增删改查,首先进行需求分析:一个学校有不同的年级,一...
  • fanwenjieok
  • fanwenjieok
  • 2015-07-14 13:10
  • 1487

[php]开发属于自己的PHP API框架(1)

大家,第一次写博客,希望大家多多支持本人是一名未毕业(在找实习)的菜鸟,大神勿喷主题: 利用composer搭建项目 安装composer 安装composer (详情请看官方文档) 创建文件夹和文件 创建项目路径,然后创建文件夹和composer.json文件目录解释 : 1.a...
  • baidu_35420937
  • baidu_35420937
  • 2017-09-12 13:45
  • 198

自己写一个MVC框架(一)

MVC是三个单词的缩写,分别为: 模型(Model),视图(View)和控制(Controller)。          MVC模式如今大量应用与web开发中,其优势明显,收到了众多开发者的欢迎和支持,st...
  • lixinstudio
  • lixinstudio
  • 2011-11-21 21:34
  • 4240
    个人资料
    • 访问:491592次
    • 积分:6793
    • 等级:
    • 排名:第4126名
    • 原创:274篇
    • 转载:63篇
    • 译文:0篇
    • 评论:51条