Android框架基础知识梳理

转载自大神谷歌小弟的博客

经过几年的发展和沉淀,Android开发中涌现出许多优秀的框架,比如:Retrofit、Afinal、OKHttp、ButterKnife、AndFix等等。这些框架的出现极大地简化了开发流程,提高了工作效率。在项目开发的过程中我们主要是使用这些轮子完成项目,很难有时间去顾及框架的内部实现。在项目交付之后我们可能就要去看看这些框架的源码了。

这些主流框架的功能各不相同,但每当打开浩繁的源码时我们几乎都可以看到反射,注解,泛型的广泛应用;也正是这些技术使得框架具有了高度的灵活性,优良的扩展性和健壮的稳定性。鉴于这些框架必备知识的重要性故在此对这部分内容做一个全面的梳理和总结

主要内容:

  • ClassLoader
    • ClassLoader的分析
    • 几种不同ClassLoader的介绍
    • ClassLoader的应用
  • 泛型
    • 泛型的由来
    • 自定义泛型
    • 泛型的擦除
  • 反射
    • class
    • 常用反射技术
    • Type以及ParameterizedType
    • 反射与泛型的结合使用
  • 注解
    • 常用注解的介绍和使用
    • 元注解
    • 自定义注解及其使用

1. ClassLoader

在程序运行时首先要将类加载到内存中,这个加载工作就是由ClassLoader完成的,故在中文文档中将其翻译为“类加载器

那么我们代码中所用到的类有什么不同呢?——它们的“来源”是不一样的

有的类是属于系统提供的类,比如:String、Date、Object等
所以,在Android系统启动时会自动创建一个Boot类型的ClassLoader,该ClassLoader用于加载一些系统层级的类

有的类属于我们自己写的类,比如:User、Girl、Beauty等等
所以,每个APP会创建一个自己的ClassLoader实例,该ClassLoader用于加载dex

 private void getClassLoaders() {
        ClassLoader classLoader = getClassLoader();
        while (null != classLoader) {
            System.out.println("----> classLoader=" + classLoader);
            classLoader = classLoader.getParent();
        }
    }

这里写图片描述

此处一共展示了三个ClassLoader

第一个ClassLoader,如下:

dalvik.system.PathClassLoader[DexPathList[[zip_file”/data/app/com.lizi.classloaderdemo-1/base.apk”],nativeLibraryDirectories=[/data/app/com.lizi.classloaderdemo-1/lib/x86, /vendor/lib, /system/lib]]]

该PathClassLoader在应用启动时创建,用于加载/data/app/cc.testreflection-2/base.apk中的类

ClassLoader是一个抽象类,它有三个常用的子类:PathClassLoader、URLClassLoader、DexClassLoader

  • PathClassLoader
    • 它只能加载已经安装的apk中的资源,比如dex文件
  • URLClassLoader
    • 它只能用于加载jar文件中的资源。但是dalvik不能直接识别jar,所以这个加载器极少使用。
  • DexClassLoader
    • 它用于从.jar和.apk类型的文件内部加载classes.dex。该类加载器常用来完成动态加载apk的需求

第二个ClassLoader,我不是很清楚,嘿嘿,欢迎指点。

第三个ClassLoader,如下:

java.lang.BootClassLoader@112e4ef

该BootClassLoader在系统启动的时候创建,用于加载系统层级的类

看看解释的这两个类加载器的应用

    private void testClassLoader() {
        try {
            Class clazz = Class.forName("cc.testreflection.Girl");
            ClassLoader classLoader = clazz.getClassLoader();
            System.out.println("----> classLoader=" + classLoader);

            classLoader = mContext.getClass().getClassLoader();
            InputStream inputStream = classLoader.getResourceAsStream("assets/ic_launcher.png");
            System.out.println("----> classLoader=" + classLoader);

            clazz = Class.forName("java.lang.String");
            classLoader = clazz.getClassLoader();
            System.out.println("----> classLoader=" + classLoader);
        } catch (Exception e) {

        }
    }

这里写图片描述

我们自己的类com.lizi.classloaderdemo和assets文件夹中的图片ic_launcher.png都是由PathClassLoader加载的,而java.lang.String是由BootClassLoader加载的

2. 泛型

2.1 概念

泛型始现于JDK1.5,从那以后大家在项目常常使用泛型,比如:

 ArrayList<Girl> arrayList=new ArrayList<Girl>();
    for(int i=0;i<10;i++){
        Girl girl =new Girl();
        arrayList.add(girl);
    }

在与此类似的场景中利用泛型限定了集合中的输入类型,从而让编译器屏蔽了源程序中的非法数据输入,比如此时往ArrayList中add一个Boy就无法通过编译器的编译

泛型主要是给编译器看的;那么在编译完成之后生成的字节码里泛型会发生什么变化呢?来看个例子:

private void testArraylistClass() {
    Class clazz1 = new ArrayList<Integer>().getClass();
    Class clazz2 = new ArrayList<String>().getClass();
    boolean isEqual=(clazz1 == clazz2);
    System.out.println("----> isEqual=" +isEqual);
}

输出结果:

-> isEqual=true

带不同泛型的ArrayList在编译后生成的Class是相同的!也就是说,泛型在编译生成字节码文件时会被”擦除”;不管ArrayList带什么泛型,在编译后都是ArrayList所对应的字节码文件

private void testArraylistGeneric(){
    try {
        ArrayList<Integer> arrayList =new ArrayList<Integer>();
        arrayList.add(9527);
        arrayList.add(9528);
        Method method=arrayList.getClass().getMethod("add",Object.class);
        method.invoke(arrayList,"hello,java");
        for (int i=0;i<arrayList.size();i++){
            System.out.println("----> arrayList.get("+i+")=" + arrayList.get(i));
        }
    }catch (Exception e){

    }

}

输出结果如下图所示:

这里写图片描述

看到了吧,之所以能把一个字符串add到该ArrayList中,究其原因还是因为泛型的擦除所致

2.2 自定义泛型方法

public static <T> T genericMethod1(T t) {
    return null;
}

public <K, V> K genericMethod2(K k, V v) {
    return null;
}

public <K, V> String genericMethod3(K k, V v) {
    return null;
}

在自定义泛型方法时,请注意在方法的返回值之前声明一个泛型,比如:这就表示该方法使用到了泛型T。在此之后,在方法的输入参数中和方法体中均可以使用该泛型

2.3 自定义泛型接口

public interface UserInfo<T> {
    public void printUserInfo(T t);
}


private class UserInfoImpl<T> implements UserInfo<T> {
    @Override
    public void printUserInfo(T t) {

    }
}

在自定义泛型接口时,请注意在接口名之后声明一个泛型,比如:这就表示该接口使用到了泛型T。在此之后,在接口定义方法时就可以使用该泛型了

2.4 自定义泛型类

public class Collection<K, V> {

    private K key;
    private V value;

    private K getValue(K k) {
        return null;
    }

    private void printValue(V v) {

    }
}

自定义泛型类与自定义泛型接口非常类似,不再赘述

3. 反射

我们知道Java代码会被编译成字节码文件当需要用一个类创建其对象的时候就会将其对应的字节码文件装载到内层,然后新建对象。

也就是说,当一个类编译完成后,在生成的.class文件中会产生一个Class对象,该对象用于表示这个类的信息,比如类的属性,字段,构造方法等等

既然Class中包含了这么多有用的信息,那么我们可以用什么方式获取Class呢?

3.1 获取 Class

           //第一种方式
            Class clazz = Girl.class;
            System.out.println("----> " + clazz.getName());

            //第二种方式
            Girl girl = new Girl();
            clazz = girl.getClass();
            System.out.println("----> " + clazz.getName());

            //第三种方式
            clazz = Class.forName("com.lizi.classloaderdemo.Girl");
            System.out.println("----> " + clazz.getName());

三种方式:

1.利用类名.class获取
2.利用对象.getClass()获取
3.利用Class.forName(“类名”)获取

在获取到Class之后,就可以利用newInstance()方法生成一个对象

Object object = clazz.newInstance();

其实,在调用newInstance()方法时实际上是调用了该类的无参构造方法

当然,我们的目的不仅仅是利用newInstance()生成一个对象,更重要的是要采用反射技术结合Class获取到该类的构造方法,属性,方法等信息

在该类中有一些简单的属性,比如年龄,姓名,国家,城市,腰围,胸围,臀围。还有一些简单的方法比如,构造方法Girl(String name,Integer age),获取电话号码getMobile();看到这里获取大家可能发现了:这些属性和方法有的是公有的,有的是私有的。访问属性的不同会带来哪些差异呢?带着这个小疑问,我们来看看常见的反射使用方法

3.2 获取 构造方法

    /**
     * 利用反射获取类的构造器
     *
     * 1 getConstructors()获取类的构造器,但获取不到私有构造器
     * 2 getDeclaredConstructors()获取类的所有构造器
     * 3 getDeclaredConstructor()获取指定的构造器
     */
    private void testGetConstructor() {
        try {
            Class clazz = Class.forName("com.lizi.classloaderdemo.Girl");
            Constructor[] Constructors = clazz.getConstructors();
            for (Constructor constructor : Constructors) {
                System.out.println("----> constructor=" + constructor);
            }
            System.out.println("----------------------------");

            Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
            for (Constructor declaredConstructor : declaredConstructors) {
                System.out.println("----> declaredConstructor=" + declaredConstructor);
            }
            System.out.println("-------------2---------------");

            Constructor constructor = clazz.getDeclaredConstructor(String.class, Integer.class);
            constructor.setAccessible(true);
            Girl girl = (Girl) constructor.newInstance("liuyan", Integer.valueOf(22));
            System.out.println("----> girl=" + girl);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

获取类所有的构造器,这个没啥可说的。那么怎么获取指定的构造器呢?一个类可能有多个重载的构造方法,它们的方法名都是一样的;所以此时需要从构造器的输入参数入手,比如:

clazz.getDeclaredConstructor(String.class, Integer.class);

就可以获取到如下的构造方法:

private Girl(String name,Integer age){ }

但是请注意该构造方法是private的,所以需要将该方法的accessible标志设置为true 表示取消语言访问检查。即:

constructor.setAccessible(true);

在获取构造方法后即可利用newInstance()创建对象,即:

Girl girl = (Girl) constructor.newInstance(“liuyan”,Integer.valueOf(22));

3.3 利用反射获取字段

/**
     * 利用反射操作类的字段
     * 1 getFields()获取类的字段,但是获取不到私有字段
     * 2 getDeclaredFields()获取类的所有字段
     * 3 获取指定的字段及其type
     * 4 获取指定对象的某个字段值
     * 5 设置指定对象的某个字段值
     */
    private void testGetField() {
        try {
            Class clazz = Class.forName("com.lizi.classloaderdemo.Girl");
            Field[] fields = clazz.getFields();
            for (Field field : fields) {
                System.out.println("----> field=" + field);
            }

            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                System.out.println("-----> declaredField=" + declaredField);
            }

            //获取指定的字段及其type
            Field field = clazz.getDeclaredField("name");
            Class type = field.getType();
            System.out.println("----> field=" + field + ",type=" + type);
            System.out.println("----------------------->");

            //获取指定对象的某个字段值
            Girl girl = new Girl("lucy", 100, 100, 100, 18);
            Field field2 = clazz.getDeclaredField("name");
            field2.setAccessible(true);
            String name = (String) field2.get(girl);
            System.out.println("----> name=" + name);


            //设置指定对象的某个字段值
            Field field3 = clazz.getDeclaredField("name");
            field3.setAccessible(true);
            field3.set(girl, "hanmeimei");
            System.out.println("----> girl=" + girl);
        } catch (Exception e) {

        }
    }

3.4 利用反射获取类中的方法

    /**
     * 利用反射获取类的方法
     * 1 getMethods()获取该类及其父类的方法,但不能获取到私有方法
     * 2 getDeclaredMethods()获取该类本身所声明的所有方法
     * 3 反射出类中的指定方法
     */
    private void testGetMethod() {
        try {
            Class clazz = Class.forName("com.lizi.classloaderdemo.Girl");
            Object object = clazz.newInstance();
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                System.out.println("----> method=" + method);
            }

            Method[] declaredMethods = clazz.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                System.out.println("----> declaredMethod=" + declaredMethod);
            }

            Method method = clazz.getDeclaredMethod("getMobile", String.class);
            Class returnType = method.getReturnType();
            System.out.println("----> method="+method+",returnType=" + returnType);
            method.setAccessible(true);
            String mobile = (String) method.invoke(object, "678");
            System.out.println("----> mobile=" + mobile);
        } catch (Exception e) {

        }
    }

3.5 利用反射操作数组

 /**
 * 利用反射操作数组
 * 1 利用反射修改数组中的元素
 * 2 利用反射获取数组中的每个元素
 */
private void testArrayClass() {
    int[] intArray = new int[]{5,7,9};
    Array.set(intArray,0,9527);
    Class clazz = intArray.getClass();
    if (clazz.isArray()) {
        int length = Array.getLength(intArray);
        for (int i = 0; i < length; i++) {
            Object object = Array.get(intArray, i);
            String className=object.getClass().getName();
            System.out.println("----> object=" + object+",className="+className);
        }
    }
}

3.6 利用反射获取泛型的参数类型

在许多框架中有这样的需求:根据不同的泛型参数响应不同的操作。

一说到泛型参数类型,可能大家立马就想到了刚才说的泛型擦除,比如ArrayList在编译后就变成了ArrayList,它原本的泛型被”擦除”了。但是我们有时确实需要知道泛型的参数类型,又该怎么来实现呢?按照刚才的那些思路恐怕是走不通了,得另辟蹊径了

第一步:

定义getGenericHelper()方法其输入参数为带泛型的参数,比如
ArrayList<String,Integer>

第二步:

利用反射获取到该getGenericHelper()方法,即:
Method method=getClass().getDeclaredMethod(“getGenericHelper”,HashMap.class);

第三步: 获取到该方法的带泛型的输入参数,即:

Type[] genericParameterTypes = method.getGenericParameterTypes();

注意getGenericParameterTypes()方法返回的是一个数组,因为方法可能有多个参数,但是依据我们的需求这个数组中是仅有一个元素的

第四步: 获取到该带泛型参数的所有泛型的类型,即:

Type[] actualTypeArguments =parameterizedType.getActualTypeArguments();

因为一个参数的泛型可能有多个,所以getActualTypeArguments()的返值是一个数组。
比如,此处的ArrayList<String,Integer>该参数就有两个泛型

第五步:
获取每个泛型的类型,即:

Type type = actualTypeArguments[i];

4. Type

Type是Java编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型

在JDK1.5之前还没有泛型时,我们的数据都是这样的:int,double,String,User,Girl…….在泛型出来之后又多了这些东西:,HashMap<String,Number>List<String>[]…..这些新出来的数据类型又该用什么去表示呢?为了兼容和统一以前的JDK版本,这才出现了Type

Type一共有四个子接口:

TypeVariable,ParameterizedType,GenericArrayType,WildcardType

现在这四个子接口都洗干净整整齐齐地摆在这里了,我们来挨个解读。

4.1 TypeVariable

TypeVariable称为类型变量,比如、中的变量T、C都是TypeVariable

private HashMap<K, Integer> generichashMap = null;
public  void testTypeVariable() throws Exception{
    Class clazz=GenericTest.class;
    Field field=clazz.getDeclaredField("generichashMap");
    Type genericType = field.getGenericType();
    if(genericType instanceof ParameterizedType){
        ParameterizedType parameterizedType= (ParameterizedType) genericType;
        Type[] types = parameterizedType.getActualTypeArguments();
        for (int i = 0; i < types.length; i++) {
            Type type=types[i];
            if( type instanceof TypeVariable){
                System.out.println("----> type="+type +"是TypeVariable");
            }else{
                System.out.println("----> type="+type+"不是TypeVariable");
            }
        }
    }
}

此处的HashMap

4.2 ParameterizedType

ParameterizedType称为参数化类型,比如HashMap<K, Integer>

private HashMap<K, Integer> hashMap = null;
public  void testParameterizedType() throws Exception{
    Class clazz=GenericTest.class;
    Field field=clazz.getDeclaredField("hashMap");
    Type genericType =field.getGenericType();
    if(genericType instanceof ParameterizedType){
        ParameterizedType parameterizedType= (ParameterizedType) genericType;
        Type rawType = parameterizedType.getRawType();
        Type ownerType = parameterizedType.getOwnerType();
        System.out.println("---->rawType="+rawType+",ownerType="+ownerType);
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        for (int i = 0; i < actualTypeArguments.length; i++) {
            Type type=actualTypeArguments[i];
            System.out.println("---->i="+i+",type="+type);
        }
    }
}

由于HashMap<K, Integer>是参数化类型,所以可用getRawType()获取其原始的类型,即:

Type rawType = parameterizedType.getRawType();

HashMap<K, Integer>的原始类型就是class java.util.HashMap
除此以外,还可以获得参数的实际类型,即:

Type[] actualTypeArguments =parameterizedType.getActualTypeArguments();

其中,第一个参数为K,第二个参数为class java.lang.Integer

4.3 GenericArrayType

GenericArrayType称为数组类型,比如T[] tArray,List[] listArray这些带有泛型的数组称为GenericArrayType

class Test<T> {
    public void test(List<String>[] stringList) {

    }
}

public  void testGenericArrayType() throws Exception{
    Class clazz=Test.class;
    Method method = clazz.getDeclaredMethods()[0];
    Type[] types = method.getGenericParameterTypes();
    for (Type type : types) {
        boolean isGenericArrayType=(type instanceof GenericArrayType);
        System.out.println("------> isGenericArrayType="+isGenericArrayType);
    }
}

那么String[] stringArray,int[] intArray是GenericArrayType么?请自行验证,将其传入此处的test( ) 方法即可

4.5 WildcardType

WildcardType称为通配符类型,比如:? extends Integer 和 ? super String都是WildcardType

private List<? extends Number> numberList;
private List<? super String> stringList;
public  void testWildcardType() throws Exception{
    Class clazz=GenericTest.class;
    Field numberField = clazz.getDeclaredField("numberList");
    Field stringField = clazz.getDeclaredField("stringList");
    ParameterizedType numberParameterizedType=(ParameterizedType)numberField.getGenericType();
    ParameterizedType stringParameterizedType=(ParameterizedType)stringField.getGenericType();
    Type [] numberActualTypeArguments=numberParameterizedType.getActualTypeArguments();
    WildcardType numberWildcardType = (WildcardType) numberActualTypeArguments[0];
    Type [] StringActualTypeArguments=stringParameterizedType.getActualTypeArguments();
    WildcardType stringWildcardType = (WildcardType) StringActualTypeArguments[0];
    System.out.println("----> numberWildcardType="+numberWildcardType);
    System.out.println("----> stringWildcardType="+stringWildcardType);
    Type numberUpperBound=numberWildcardType.getUpperBounds()[0];
    Type stringLowerBound=stringWildcardType.getLowerBounds()[0];
    System.out.println("----> numberUpperBound="+numberUpperBound);
    System.out.println("----> stringLowerBound="+stringLowerBound);
}

我们知道在泛型中 extends 表示上限,super表示下限。比如此处:

  • ? extends Number 的上限是class java.lang.Number
  • ? super String 的下限是class java.lang.String

所以,可利用getUpperBounds()和getLowerBounds()获取WildcardType的上限或下限

Type除了这四个子接口,它还有一个实现类Class

Class所表示的是原始类型,或者说是泛型出现之前的类型。比如String,User,Girl,Integer等等

5. 注解

注解内容有些多,单独写一章:注解解析

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值