本文正在参加「金石计划 . 分割6万现金大奖」
前言
Github Demo:合作 Demo 一起看,效果杠杠的
很快乐遇见你~
在App 黑白化技能实践上篇这篇文章,咱们介绍了:
1、App 黑白化完结原理:将 Paint 的饱和度设置为 0,然后进行 View 的绘制
2、App 黑白化两种计划实践:
1、对页面的 DecorView 进行黑白化设置
2、替换页面的内容栏 FramLaout 为黑白化 FrameLayout
3、分析了 App 黑白化两种计划存在的一些问题
1、计划一:Dialog,PopupWindow 黑白化不生效
2、计划二:Dialog 黑白化生效,PopupWindow 黑白化不生效
4、给出了 App 黑白化两种计划出现问题的原因以及新的思路
还没有看过的朋友,建议先去阅览一下。
回忆一下咱们说的新思路:
App 中 Window 的增加最终都会走到 WindowManagerGlobal 的 addView 方法:
//WindowManagerGlobal#addView
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
//...
synchronized (mLock) {
// 将 view 增加到 mViews,mViews 是一个 ArrayList 调集
mViews.add(view);
// 最终经过 viewRootImpl 来增加 window
try {
root.setView(view, wparams, panelParentView);
}
}
}
WindowManagerGlobal 是一个大局单例,其间 mViews 是一个调集,App 中所有的 Window 在增加的时候都会被它给存起来。
那咱们就能够经过 Hook 拿到 mViews 中所有的 View 然后进行黑白化设置,这样不管是 Activity,Dialog,PopupWindow 还是其他一些 Window 组件,都会变成黑白化。具体一点便是:
1、Hook WindowManagerGlobal 中的 mViews ,将其改成可感知数据的 ArrayList 调集
2、监听 mViews 的 add 操作,然后对 View 进行黑白化设置
在此之前,你需求了解:
1、什么是 Hook?
2、怎样进行 Hook?
才能完结上述操作
一、Hook 介绍
1.1、什么是 Hook?
简单了解:Hook 便是运用署理方针对原始方针进行劫持,刺进一段咱们自己的逻辑,完结偷梁换柱
正常调用:
Hook 调用:
1.2、怎样进行 Hook?
Hook 通常是有固定套路的:
1、承认 Hook 点(被劫持的原始方针咱们称之为 Hook 点)
2、界说署理类
3、运用署理方针替换 Hook 点
说起来有点抽象,下面我就手把手带领大家进行 Hook 实践。
1.3、Hook 实践
如下例子:
MainActivity 中,咱们给 btnHook 设置了点击事情并进行了 Log 打印,现在要求在不改动这个点击事情的情况下,弹出 Toast 并显示:erdai666。
如何做到?
咱们是不是就要对btnHook.setOnClickListener
的点击事情(OnClickListener)进行 Hook
class MainActivity: BaseActivity() {
private val btnHook by lazy {
findViewById<Button>(R.id.btnHook)
}
override fun getLayoutId(): Int {
return R.layout.activity_main
}
override fun initView() {
btnHook.setOnClickListener{
Log.d("MainActivity", "Hello")
}
}
}
按照上面的固定套路:
1.3.1、承认 Hook 点
跟一下btnHook.setOnClickListener
的源码:
//1、btnHook.setOnClickListener => View#setOnClickListener
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
//2、getListenerInfo().mOnClickListener => ListenerInfo#mOnClickListener
public class View implements Drawable.Callback,... {
//...
static class ListenerInfo {
//...
public OnClickListener mOnClickListener;
}
}
上述代码咱们能够知道:
1、当咱们进入btnHook.setOnClickListener
,发现 OnClickListener 方针被赋值给了getListenerInfo().mOnClickListener
2、getListenerInfo() 是一个 ListenerInfo 方针,ListenerInfo 是 View 中的一个静态内部类,它持有了 OnClickListener 方针
那么现在咱们就能够承认 Hook 点:View 中 ListenerInfo 中的 OnClickListener 方针
1.3.2、界说署理类
class ProxyClickListener(var context: Context,var clickListener: View.OnClickListener)
: View.OnClickListener {
override fun onClick(v: View?) {
Toast.makeText(context,"erdai666",Toast.LENGTH_SHORT).show()
clickListener.onClick(v)
}
}
署理类逻辑很简单:便是在 OnClickListener 的基础上增加了咱们自己的 Toast 提示
Tips: 假如 Hook 点是接口,咱们能够运用 JDK 的动态署理,假如动态署理不清楚的能够看我这篇文章,详细介绍了 Java 六大规划原则和 24 种规划形式:”一篇就够”系列:Java 六大规划原则和常用规划形式
1.3.3、运用署理方针替换 Hook 点
替换进程咱们需求运用到反射,假如反射不清楚,能够看我这篇文章:Android APT 系列 (一):APT 筑基之反射
理一理替换的思路:
咱们的方针是对 View 中 ListenerInfo 中的 OnClickListener 方针进行替换,因而要经过反射拿到这个方针,细节拆分:
1、拿到当时 View 方针的 ListenerInfo 方针
2、经过 ListenerInfo 方针在拿到 OnClickListener 方针
拿到 OnClickListener 方针后,创立署理方针,然后对当时 OnClickListener 方针进行替换即可
新建一个 HookSetOnClickListenerHelper 类对上面的思路进行实践:
object HookSetOnClickListenerHelper {
/**
* context:上下文
* view:当时 view 方针
*/
fun hook(context: Context,view: View){
//一、拿到当时 View 方针的 ListenerInfo 方针
val getListenerInfoMethod = View::class.java.getDeclaredMethod("getListenerInfo")
//破坏封装,让咱们能访问 private 润饰的成员
getListenerInfoMethod.isAccessible = true
val listenerInfo = getListenerInfoMethod.invoke(view)
//二、经过 ListenerInfo 方针在拿到 OnClickListener 方针
//android.view.View$ListenerInfo 这种是内部类的写法
val mOnClickListenerFiled = Class.forName("android.view.View\$ListenerInfo").getDeclaredField("mOnClickListener")
val mOnClickListener = mOnClickListenerFiled.get(listenerInfo) as View.OnClickListener
//三、创立署理方针
//1、法一
val proxyClickListener = ProxyClickListener(context,mOnClickListener)
//2、法二:由于 OnClickListener 是一个接口,咱们能够运用 JDK 动态署理
// val proxyClickListener = Proxy.newProxyInstance(
// context.classLoader,
// arrayOf(View.OnClickListener::class.java)
// ) { proxy, method, args ->
// Toast.makeText(context, "erdai666", Toast.LENGTH_SHORT).show()
// method?.invoke(mOnClickListener, *args)
// }
//四、运用署理方针替换原始方针
mOnClickListenerFiled.set(listenerInfo,proxyClickListener)
}
}
ok,接下来修改 MainActivity:
class MainActivity: BaseActivity() {
private val btnHook by lazy {
findViewById<Button>(R.id.btnHook)
}
override fun getLayoutId(): Int {
return R.layout.activity_main
}
override fun initView() {
btnHook.setOnClickListener{
Log.d("MainActivity", "Hello")
}
//Hook
HookSetOnClickListenerHelper.hook(this,btnHook)
}
}
运转 App,效果如下:
了解了 Hook,接下来咱们就 Hook WindowManagerGlobal 中的 mViews 完结 App 黑白化
二、Hook WindowManagerGlobal 中 mViews 完结 App 黑白化
咱们的思路很明确:
1、Hook WindowManagerGlobal 中的 mViews ,将其改成可感知数据的 ArrayList 调集
2、监听 mViews 的 add 操作,然后对 View 进行黑白化设置
代码完结,写了详细的注释:
//1、新建 mViews 方针的署理类:可感知数据的 ArrayList 调集
class ObservableArrayList<T>(private val onListAddListener: OnListAddListener<T>?) :
ArrayList<T>() {
override fun add(element: T): Boolean {
val isAdd = super.add(element)
onListAddListener?.add(this, size - 1)
return isAdd
}
//监听器:监听 ArrayList add 操作
interface OnListAddListener<T> {
fun add(list: ArrayList<T>, index: Int)
}
}
//2、新建一个 GlobalGray 编写 Hook 逻辑
object GlobalGray {
fun hook(){
//一、获取 WindowManagerGlobal 方针
val windowManagerGlobalClass = Class.forName("android.view.WindowManagerGlobal")
val getInstanceStaticMethod = windowManagerGlobalClass.getDeclaredMethod("getInstance")
val windowManagerGlobal = getInstanceStaticMethod.invoke(windowManagerGlobalClass)
//二、获取 WindowManagerGlobal 中的 mViews
val mViewsField = windowManagerGlobalClass.getDeclaredField("mViews")
mViewsField.isAccessible = true
val mViews = mViewsField.get(windowManagerGlobal) as ArrayList<View>
//三、创立署理类方针
//创立饱和度为 0 的画笔
val paint = Paint()
val cm = ColorMatrix()
cm.setSaturation(0f)
paint.colorFilter = ColorMatrixColorFilter(cm)
val proxyArrayList = ObservableArrayList(object : ObservableArrayList.OnListAddListener<Any>{
override fun add(list: ArrayList<Any>, index: Int) {
val view = list[index] as View
view.setLayerType(View.LAYER_TYPE_HARDWARE,paint)
}
})
//将原有的数据增加到署理 ArrayList
proxyArrayList.addAll(mViews)
//四、运用署理方针替换原始方针
mViewsField.set(windowManagerGlobal,proxyArrayList)
}
}
Hook 逻辑写好了,咱们测试一下,在 Applicaton 里边增加 Hook 的逻辑:
class MyApp: Application() {
override fun onCreate() {
super.onCreate()
//Hook 大局 App 黑白化
GlobalGray.hook()
}
}
运转 App ,效果验证:
2.1、关于视频
咱们这种计划只是针对 WindowManagerGlobal addView 进程中的所有 View 完结黑白化,假如不是这个范畴的则完结不了,例如:SurfaceView
1、关于普通的 View,Android 中的窗口界面包含多个 View 组成的 View Hierachy 的树形结构,只有最顶层的 DecorView才对 WMS 可见,这个 DecorView 在 WMS 中有一个对应的 WindowState,此刻 APP 恳求创立 Surface 时,会在SurfaceFlinger 内部树立对应的 Layer。
2、而关于 SurfaceView 它自带一个Surface,这个 Surface 在 WMS 有自己对应的 WindowState,在 SurfaceFlinger 中有自己对应的 Layer。
3、SurfaceView 从 App 端看它仍然在 View hierachy 结构中,但在 WMS 和 SurfaceFlinger 中它与宿主窗口是别离的。因而 SurfaceView 的 Surface 的渲染能够放到独自线程去做,不会影响主线程对事情的呼应。
因而假如你视频播映运用的是 SurfaceView ,则这种计划完结不了黑白化,你需求针对 SurfaceView 独自去处理.
三、总结
本篇文章咱们介绍了:
1、Android 高档必备 Hook ,并带领大家手把手对 Hook 进行了实践
2、Hook 是有固定套路的:
1、承认 Hook 点
2、界说署理类
3、运用署理方针替换 Hook 点
掌握这个套路,咱们就能很轻松的去进行 Hook
3、经过 Hook WindowManagerGlobal 的 mViews 完结 App 大局黑白化
4、假如你视频播映运用的是 SurfaceView ,则这种计划完结不了黑白化,你需求针对 SurfaceView 独自去处理
好了,本篇文章到这里就完毕了,希望能给你带来帮助
感谢你阅览这篇文章
你的点赞,谈论,是对我巨大的鼓舞!
欢迎关注我的大众号: sweetying ,文章更新可第一时间收到
假如有问题,大众号内有加我微信的进口,在技能学习、个人成长的道路上,咱们一起行进!