注册

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

简介


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();
}


作者:悠二
链接:https://juejin.cn/post/7046353293240434724
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册