使用hook修改全局Toast
需求背景
对android原生系统中的Settings模块所有的Toast进行统一替换
技术背景
-
反射基础(用于获取所需方法及字段,并替换使用我们的代理类)
-
Hook(动态代理)
实现细节
流程简述
- 自定义一个代理类的调度类,它要实现InvocationHandler的invoke方法;
- 通过反射,调用Toast的getService方法获得INotificationManager对象(Hook对象实例);
- 用Hook对象实例为参数,生成一个代理类的实例;
- 用代理类的实例,替换第一步拿到的INotificationManager对象。
细节
0.查看android原生Toast代码,内部的sService可以作为hook点
Toast代码代码如下:
public class Toast {
...
//HOOK替换的对象
private static INotificationManager sService;
static private INotificationManager getService() {
if (sService != null) {
return sService;
}
sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
return sService;
}
...
/**
* Show the view for the specified duration.
*/
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
}
-
自定义代理类
生成代理类,并且拦截enqueueToast方法,对入参进行修改后再传入目标的原生的方法。
public class ToastProxy implements InvocationHandler { private static final String TAG = "ToastProxy"; private Object mService; private Context mContext; public Object newProxyInstance(Context context, Object sService) { this.mService = sService; this.mContext = context; return Proxy.newProxyInstance(sService.getClass().getClassLoader(), sService.getClass().getInterfaces(), this); } @Override public Object invoke(Object arg0, Method method, Object[] args) throws Throwable { Log.i(TAG, "invoke: method == " + method.getName()); if ("enqueueToast".equals(method.getName())) { if (args != null && args.length > 0) { Field mNextView = args[1].getClass().getDeclaredField("mNextView"); mNextView.setAccessible(true); View mNextView = (View) mNextViewClazz.get(args[1]); if (mNextView != null && mNextView instanceof LinearLayout){ //获取原Toast的文字 String nextString = ((TextView) ((LinearLayout) mNextView).getChildAt(0)).getText().toString(); //构造所需变量 TextView value = new TextView(mContext); value.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); value.setBackgroundColor(Color.RED); value.setText(nextString); mNextViewClazz.set(args[1], value); } } } return method.invoke(mService, args); } }
-
全局替换吐司。
反射调用getService生成sService静态对象,用Hook对象实例为参数,生成一个代理类的实例,用代理类的实例,替换第一步拿到的sService对象。
private void setToast() { try { // 获得HOOK点 Toast toast = new Toast(this); Method getService = toast.getClass().getDeclaredMethod("getService"); getService.setAccessible(true); final Object sService = getService.invoke(toast); // 生成代理对象 ToastProxy toastProxy = new ToastProxy(); Object proxyNotiMng = toastProxy.newProxyInstance(this,sService); // 替换 sService Field sServiceField = Toast.class.getDeclaredField("sService"); sServiceField.setAccessible(true); sServiceField.set(sService, proxyNotiMng); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } }