Android技能树点亮计划--Java反射与动态代理

1,003 阅读2分钟

前言

此处为语雀内容卡片,点击链接查看:www.yuque.com/youer-ycy0r…

简介

Java的反射是指程序在运行期可以拿到一个对象的所有信息

使用

反射主要分为以下几个步骤

1. 获取Class对象

JVM在加载类的时候,会为每个类生成一个独一无二的Class对象

获取方式有以下几种 
//name = Test.class.getDeclaredField("name");
//name = test.getClass().getDeclaredField("name");
name = Class.forName("com.example.app.MainActivity$Test").getDeclaredField("name");

2. 操作fileds

Test test = new Test("xxx");
Field name;
try {
  //name = Test.class.getDeclaredField("name");
  //name = test.getClass().getDeclaredField("name");
  name = Class.forName("com.example.app.MainActivity$Test").getDeclaredField("name");
  name.setAccessible(true);
  Log.d("test", (String)name.get(test));
} catch (NoSuchFieldException | IllegalAccessException | ClassNotFoundException e) {
  e.printStackTrace();
}

class Test {
  private String name;

  public Test(String name) {
    this.name = name;
  }
  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}
  • getDeclaredField :获取本类的任何field

  • getField:获取本类和基类的public field

  • 获取基类非public值,只能通过基类class的getDeclaredField

3. 调用method

// 无参数的方法
Method getName = Class.forName("com.example.app.MainActivity$Test").getMethod("getName");
Log.d("test", (String)getName.invoke(test));

// 有参数的方法
Method setName = Class.forName("com.example.app.MainActivity$Test").getMethod("setName", String.class);
setName.invoke(test, "sdaasda");
Log.d("test", test.getName());

动态代理

在程序运行期动态创建某个interface的实例,通过动态代理可以实现一个方法/类的hook

比如hook点击事件

public class HookOnClickListenerHelper {
    public static View.OnClickListener hook(Context context, final View v) {//
        return (OnClickListener)Proxy.newProxyInstance(v.getClass().getClassLoader(),
            new Class[] {OnClickListener.class},
            new ProxyHandler(new ProxyOnClickListener()));
    }

    static class ProxyHandler implements InvocationHandler {

        private View.OnClickListener listener;

        public ProxyHandler(OnClickListener listener) {
            this.listener = listener;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return method.invoke(listener, args);
        }
    }

    static class ProxyOnClickListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            Log.d("HookSetOnClickListener", "点击事件被hook到了");
        }
    }
}

findViewById(R.id.service).setOnClickListener(HookOnClickListenerHelper.hook(this, findViewById(R.id.service)))

实践

目标:动态代理应用版本号的返回

分析:

动态代理的实现相对来说是简单的,困难的部分在于通过读源码了解到功能是如何实现的,通过代理哪个类可以修改目标代码的返回

  1. Android是如何获取应用版本号的?

通过getPackageManager()的getPackageInfo()

PackageManager pm = getPackageManager();
PackageInfo pi = pm.getPackageInfo(getPackageName(), 0);
versionName = pi.versionName;
versioncode = pi.versionCode;
  1. getPackageManager()如何获取 ?

getPackageManager在Context中实现,Context是一个abstract Class,所有的实现都在 ContextImpl中,通过ContextImpl我们发现getPackageManager()是从 ActivityThread.getPackageManager()拿到的

// ContextImp.java
@Override
public PackageManager getPackageManager() {
    if (mPackageManager != null) {
        return mPackageManager;
    }

    final IPackageManager pm = ActivityThread.getPackageManager();
    if (pm != null) {
        // Doesn't matter if we make more than one instance.
        return (mPackageManager = new ApplicationPackageManager(this, pm));
    }

    return null;
}
  1. ActivityThread如何获取?

ActivityThread内部有静态方法currentActivityThread()来获取

// ActivityThread.java
public static ActivityThread currentActivityThread() {
     return sCurrentActivityThread;
 }
  1. ActivityThread中的packageManager怎么获取?

在ActivityThread中定义了sPackageManager,通过它我们就能拿到sPackageManager

 public static IPackageManager getPackageManager() {
     if (sPackageManager != null) {
         //Slog.v("PackageManager", "returning cur default = " + sPackageManager);
         return sPackageManager;
     }
     IBinder b = ServiceManager.getService("package");
     //Slog.v("PackageManager", "default service binder = " + b);
     sPackageManager = IPackageManager.Stub.asInterface(b);
     //Slog.v("PackageManager", "default service = " + sPackageManager);
     return sPackageManager;
 }

代理:

  1. 获取ActivityThread

    // 获取ActivityThread activityThreadClz = Class.forName("android.app.ActivityThread"); Method currentActivityThread = activityThreadClz.getDeclaredMethod("currentActivityThread"); currentActivityThread.setAccessible(true); Object activityThread = currentActivityThread.invoke(null);

  2. 获取packageManager

    // 获取packageManager Field packageManagerField = activityThreadClz.getDeclaredField("sPackageManager"); packageManagerField.setAccessible(true); final Object packageManager = packageManagerField.get(activityThread);

  3. 动态代理,处理getPackageInfo方法

    // 动态代理处理数据 Class<?> packageManagerClazz = Class.forName("android.content.pm.IPackageManager", false, getClassLoader()); Object proxy = Proxy.newProxyInstance(getClassLoader(), new Class[] {packageManagerClazz}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = method.invoke(packageManager, args); if ("getPackageInfo".equals(method.getName())) { PackageInfo packageInfo = (PackageInfo)result; packageInfo.versionName = "sdsds"; } return result; } });

  4. 给packManger设置hook的对象

    //hook sPackageManager packageManagerField.set(activityThread, proxy);

测试:

//越早 hook 越好,推荐在 attachBaseContext 调用
PackageManager pm = getPackageManager();
try {
    PackageInfo pi = pm.getPackageInfo(getPackageName(), 0);
    Log.d(TAG, pi.versionName);
} catch (NameNotFoundException e) {
    e.printStackTrace();
}

推荐封装库

JOOR

使用介绍 www.jianshu.com/p/1ba3680c1…

参考文档

《Android工程化最佳实践》

廖雪峰--反射

找到我

Android技能树点亮计划Git库

语雀

稀土掘金:悠二

Github:悠二