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)))
实践
目标:动态代理应用版本号的返回
分析:
动态代理的实现相对来说是简单的,困难的部分在于通过读源码了解到功能是如何实现的,通过代理哪个类可以修改目标代码的返回
- Android是如何获取应用版本号的?
通过getPackageManager()的getPackageInfo()
PackageManager pm = getPackageManager();
PackageInfo pi = pm.getPackageInfo(getPackageName(), 0);
versionName = pi.versionName;
versioncode = pi.versionCode;
- 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;
}
- ActivityThread如何获取?
ActivityThread内部有静态方法currentActivityThread()来获取
// ActivityThread.java
public static ActivityThread currentActivityThread() {
return sCurrentActivityThread;
}
- 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;
}
代理:
获取ActivityThread
// 获取ActivityThread
activityThreadClz = Class.forName("android.app.ActivityThread");
Method currentActivityThread = activityThreadClz.getDeclaredMethod("currentActivityThread");
currentActivityThread.setAccessible(true);
Object activityThread = currentActivityThread.invoke(null);获取packageManager
// 获取packageManager
Field packageManagerField = activityThreadClz.getDeclaredField("sPackageManager");
packageManagerField.setAccessible(true);
final Object packageManager = packageManagerField.get(activityThread);动态代理,处理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;
}
});给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
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。