Java反射以及在Android中的特殊应用

反射的定义以及组成java

关于反射,通常书上的定义是这样的:JAVA反射机制是在运行状态中,对于任意一个类,都可以知道这个类的全部属性和方法;对于任意一个对象,都可以调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制,这几句解释说明了反射的做用,动态的跟类进行交互,好比获取隐藏属性,修改属性,获取对象,建立对象或者方法等等,总之就一句话:android

反射是一种具备与类进行动态交互能力的一种机制 为何要强调动态交互呢?由于通常状况下都是动态加载,也就是在运行的时候才会加载,而不是在编译的时候,在须要的时候才进行加载获取,或者说你能够在任什么时候候加载一个不存在的类到内存中,而后进行各类交互,或者获取一个没有公开的类的全部信息,换句话说,开发者能够随时随意的利用反射的这种机制动态进行一些特殊的事情。设计模式

反射的组成数组

因为反射最终也必须有类参与,所以反射的组成通常有下面几个方面组成:bash

1.java.lang.Class.java:类对象;app

2.java.lang.reflect.Constructor.java:类的构造器对象;框架

3.java.lang.reflect.Method.java:类的方法对象;ide

4.java.lang.reflect.Field.java:类的属性对象;模块化

下面一张图说明了关系:函数

根据虚拟机的工做原理,通常状况下,类须要通过:加载->验证->准备->解析->初始化->使用->卸载这个过程,若是须要反射的类没有在内存中,那么首先会通过加载这个过程,并在在内存中生成一个class对象,有了这个class对象的引用,就能够发挥开发者的想象力,作本身想作的事情了。

反射的做用

前面只是说了反射是一种具备与Java类进行动态交互能力的一种机制,在Java和Android开发中,通常状况下下面几种场景会用到反射机制.

● 须要访问隐藏属性或者调用方法改变程序原来的逻辑,这个在开发中很常见的,因为一些缘由,系统并无开放一些接口出来,这个时候利用反射是一个有效的解决方法

● 自定义注解,注解就是在运行时利用反射机制来获取的。

●在开发中动态加载类,好比在Android中的动态加载解决65k问题等等,模块化和插件化都离不开反射,离开了反射步履维艰。

反射的工做原理

咱们知道,每一个java文件最终都会被编译成一个.class文件,这些Class对象承载了这个类的全部信息,包括父类、接口、构造函数、方法、属性等,这些class文件在程序运行时会被ClassLoader加载到虚拟机中。当一个类被加载之后,Java虚拟机就会在内存中自动产生一个Class对象,而咱们通常状况下用new来建立对象,实际上本质都是同样的,只是这些底层原理对咱们开发者透明罢了,咱们前面说了,有了class对象的引用,就至关于有了Method,Field,Constructor的一切信息,在Java中,有了对象的引用就有了一切,剩下怎么发挥是开发者本身的想象力所能决定的了。

反射的简单事例

前面说了这么多理论,下面简单实践一下

public class Student {
    private int age;//年龄
    private String name;//姓名
    private String address;//地址
     private static String sTest;
    public Student() {
         throw new IllegalAccessError("Access to default Constructor Error!");
    }

    private Student(int age, String name, String address) {
        this.age = age;
        this.name = name;
        this.address = address;
         sTest = "测试反射";
    }

    private int getAge() {
        return age;
    }
    
    private void setAge(int age) {
        this.age = age;
    }

    private String getName() {
        return name;
    }

    private void setName(String name) {
        this.name = name;
    }

    private String getAddress() {
        return address;
    }

    private void setAddress(String address) {
        this.address = address;
    }
    private static String getTest() {
        return sTest;
    }
}
复制代码

在这里为了练习,刻意用了private来修饰成员变量和方法 下面代码用构造器,方法和属性和静态方法分别来获取一下,

public class StudentClient {
    public static void main(String[] args) throws Exception{
        Class<?> clazz=Class.forName("ClassLoader.Student");
        Constructor constructors=clazz.getDeclaredConstructor(int.class,String.class,String.class);
        constructors.setAccessible(true);
        //利用构造器生成对象
        Object mStudent=constructors.newInstance(27,"小文","北京市海定区XX号");
        System.out.println(mStudent.toString());
        //获取隐藏的int属性
        Field mAgeField=clazz.getDeclaredField("age");
        mAgeField.setAccessible(true);
        int age= (int) mAgeField.get(mStudent);
        System.out.println("年龄为:"+age);
        //调用隐藏的方法
        Method getAddressMethod=clazz.getDeclaredMethod("getAge");
        getAddressMethod.setAccessible(true);
        int newage= (int) getAddressMethod.invoke(mStudent);
        System.out.println("年龄为:"+newage);
        //调用静态方法
        Method getTestMethod=clazz.getDeclaredMethod("getTest");
        getTestMethod.setAccessible(true);
        String result= (String) getTestMethod.invoke(null);
        System.out.println("调用静态方法:"+result);
    }
}
复制代码

结果以下:

你们都看得懂,应该能够理解,有同窗说不是有不少getDeclared***和get***的方法吗, 实际上都差很少的,只不过用的范围不同而已,getDeclared***获取的是仅限于本类的全部的不受访问限制的,而get***获取的是包括父类的但仅限于public修饰符的,Field和Method也是同样的道理,这个你们注意一下就好,最后一个须要注意的是调用静态方法和调用实例方法有点区别,调用实例方法必定须要一个类的实例,而调用静态方法不须要实例的引用,其实这是JVM的在执行方法上的有所区别,JVM在执行方法的时候会建立一个堆栈,堆栈里面保存了局部变量表以及其余的一些必要的信息,其中局部变量表里面也包含了局部参数,而局部参数里面保存了当前方法的形参,若是是调用实例方法的话,那么形参的第一个参数就是当前的类的引用了,而调用的是静态方法的话,那么第一个参数是为null的,这一点没法经过任何手段去绕过,换句话说调用实例方法必定须要一个类的引用,关于这一点,读者能够本身去查阅有关JVM的书籍。

固然了,反射的做用毫不止这些,在数组,泛型,设计模式等方面依然发挥了巨大的做用,但原理并无脱离上面说的,读者能够多查看相关源码学习,源码就是最好的学习资源。

反射在Android框架层的应用

这是本文须要重点说明的,众所周知,Android中的FrameWork是用Java语言编写的,天然离不开一些反射的影子,而利用反射更是能够达到咱们一些常规方法难于达到的目的,再者反射也是Java层中进行Hook的重要手段,目前的插件化更是大量利用反射。

首先提出需求:如何监控Activity的建立和启动过程? 有同窗说了,我在Activity里面重写生命周期方法不就能够了吗?实际上这个是达不到需求的,由于很简单,这些生命周期方法的调用是在建立和启动以后好久的事情了,里面的生命周期方法相对于整个Activity来讲是比较后面的事情,要想解决这个问题,必需要知道Activity是怎么来,中间通过了哪一个流程,最后去了哪里,只有明白了这些,才能知道在哪一个阶段作哪些事情,咱们知道,Activity的启动是一个IPC过程,也就是Binder机制,里面通过了本地进程->AMS进程-->再回到本地进程,下面是实例图:

图画的有些粗糙,你们将就看吧,从上面能够看到,Activity从本地到远程AMS之后,远程AMS只是作了权限以及属性的检查,而后再回到本地进程,这才开始真正的建立和检查,咱们才代码来分析一下,涉及到的类有Handler以及ActivityThread和Instrumentation类,首先从远端进程回到本地进程以后,系统的Handler类H会发送一个消息:LAUNCH_ACTIVITY,代码以下:省略了一些非必要代码,否则篇幅太长,下面的代码都是在ActivityThread.java里面的

public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } 
复制代码

随后调用了handleLaunchActivity方法,handleLaunchActivity方法里面又调用了 performLaunchActivity方法,代码以下:

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();
        mSomeActivitiesChanged = true;

        if (r.profilerInfo != null) {
            mProfiler.setProfiler(r.profilerInfo);
            mProfiler.startProfiling();
        }

        // Make sure we are running with the most recent config.
        handleConfigurationChanged(null, null);

        if (localLOGV) Slog.v(
            TAG, "Handling launch of " + r);

        // Initialize before creating the activity
        WindowManagerGlobal.initialize();

        Activity a = performLaunchActivity(r, customIntent);
        
        ....
        }
复制代码

在performLaunchActivity里面终于建立了Activity了,进入performLaunchActivity里面看看有一段很是核心的代码:

Activity activity = null;
        try {
        //
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            //经过mInstrumentation.newActivity()方法建立了Activity,mInstrumentation是Instrumentation类的实例
            ,对象的类为:Instrumentation.java
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }
复制代码

咱们如今已经知道了Activity的建立了,是由Instrumentation的newActivity()方法实现,咱们看一下方法:

public Activity newActivity(Class<?> clazz, Context context, 
            IBinder token, Application application, Intent intent, ActivityInfo info, 
            CharSequence title, Activity parent, String id,
            Object lastNonConfigurationInstance) throws InstantiationException, 
            IllegalAccessException {
        Activity activity = (Activity)clazz.newInstance();
        ActivityThread aThread = null;
        activity.attach(context, aThread, this, token, 0, application, intent,
                info, title, parent, id,
                (Activity.NonConfigurationInstances)lastNonConfigurationInstance,
                new Configuration(), null, null, null);
        return activity;
    }
复制代码

看到没,做为四大组件的Activity其实也是一个普通对象,也是由反射建立的,只不过因为加入了生命周期方法,才有组件这个活生生的对象存在, 因此说Android中反射无处不在,分析完了启动和建立的过程,回到刚才那个需求来讲,如何监控Activity的启动和建立呢?读者能够先本身想一下,首先启动是由Handler来发送消息,具体的在里面handlerMessage方法实现的, 这也是Handler里面的处理代码的顺序,以下代码:

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
复制代码

大不了咱们本身弄一个自定义的Handler.Callback接口,而后替换掉那个H类里面的处理接口,这样就能够监控Activity的启动了,好方法,咱们来写一下代码:

public static void hookHandler(Context context) throws Exception {
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
        currentActivityThreadMethod.setAccessible(true);
        //获取主线程对象
        Object activityThread = currentActivityThreadMethod.invoke(null);
        //获取mH字段
        Field mH = activityThreadClass.getDeclaredField("mH");
        mH.setAccessible(true);
        //获取Handler
        Handler handler = (Handler) mH.get(activityThread);
        //获取原始的mCallBack字段
        Field mCallBack = Handler.class.getDeclaredField("mCallback");
        mCallBack.setAccessible(true);
        //这里设置了咱们本身实现了接口的CallBack对象
        mCallBack.set(handler, new UserHandler(handler));
    }
复制代码
public class UserHandler  implements Callback {
    //这个100通常状况下最好也反射获取,固然了你也能够直接写死,跟系统的保持一致就行了
    public static final int LAUNCH_ACTIVITY = 100;
    private Handler origin;
    public UserHandler( Handler mHandler) {
        this.origin = mHandler;
    }

    @Override
    public boolean handleMessage(Message msg) {
        if (msg.what == LAUNCH_ACTIVITY) {
        //这样每次启动的时候能够作些额外的事情
            Log.d("[app]","作你想要的事情");
        }
        origin.handleMessage(msg);
        return false;
    }
}
复制代码

好了,Activity的启动监控就这样了,通常写在application里面的attachBaseContext()方法里面,由于这个方法时机最先。 好了,下面来讲说Activity的建立的监控,前面咱们知道了,Instrumentation的newActivity方法负责建立了Activity,那么突破口也就是在这里了,建立为咱们自定义的Instrumentation,而后反射替换掉就好,同时重写newActivity方法,能够作些事情,好比记录时间之类,下面是代码:

public static void hookInstrumentation() throws Exception{
        Class<?> activityThread=Class.forName("android.app.ActivityThread");
        Method currentActivityThread=activityThread.getDeclaredMethod("currentActivityThread");
        currentActivityThread.setAccessible(true);
        //获取主线程对象
        Object activityThreadObject=currentActivityThread.invoke(null);

        //获取Instrumentation字段
        Field mInstrumentation=activityThread.getDeclaredField("mInstrumentation");
        mInstrumentation.setAccessible(true);
        Instrumentation instrumentation= (Instrumentation) mInstrumentation.get(activityThreadObject);
        CustomInstrumentation customInstrumentation=new CustomInstrumentation(instrumentation);
        //替换掉原来的,就是把系统的instrumentation替换为本身的Instrumentation对象
        mInstrumentation.set(activityThreadObject,CustomInstrumentation);
        Log.d("[app]","Hook Instrumentation成功");

    }
复制代码
public class CustomInstrumentation  extends Instrumentation{
    private Instrumentation base;

    public CustomInstrumentation(Instrumentation base) {
        this.base = base;
    }

    //重写建立Activity的方法
    @Override
    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        Log.d("[app]","you are hook!,作本身想要的事情");
        Log.d("[app]","className="+className+" intent="+intent);
        return super.newActivity(cl, className, intent);
    }
}
复制代码

一样在application的attachBaseContext注入就好,固然了,Instrumentation还有其余方法能够重写,你们能够去试一试,下面是运行的结果:

看到没,监控启动和建立都实现了,其实这里面也有好多扩展的,好比启动Activity的时候,Instrumentation同样是能够监控的,你懂的,再次重写方法,而后实现本身的逻辑,另外,small插件化框架就是Hook了Instrumentation来动态加载Activity的,你们有兴趣能够去看看,除了以上方法,还有不少方法能够用相似的手段去实现,你们必定要多练习,好记性不如烂笔头就是这个道理。

使用反射须要注意的地方

从前面能够看出,使用反射很是方便,并且在一些特定的场合下能够实现特别的需求,可是使用反射也是须要注意一下几点的:

●反射最好是使用public修饰符的,其余修饰符有必定的兼容性风险,好比这个版本有,另外的版本可能没有

●你们都知道的Android开源代码引发的兼容性的问题,这是Android系统开源的最大的问题,特别是那些第三方的ROM,要慎用。

●若是大量使用反射,在代码上须要优化封装,否则很差管理,写代码不只仅是实现功能,还有维护性和可读性方法也须要增强,demo中能够直接这样粗糙些,在项目中仍是须要好好组织封装下的。

今天的文章就写到这里,感谢你们阅读。

相关文章
相关标签/搜索