这是一篇迟到了许久的文章,因工作原因一直没时间写(好吧,其实就是懒)。。
导语
清明节当天,各大公司都将自己的应用设成了全局灰色调,以哀悼逝去的同胞,作为程序猿的我们,当然要研究一下此技术的实习方案。
思路
首先,核心代码如下:
Paint paint = new Paint();
ColorMatrix cm = new ColorMatrix();
cm.setSaturation(0);
mPaint.setColorFilter(new ColorMatrixColorFilter(cm));
view.setLayerType(View.LAYER_TYPE_HARDWARE, paint);
上述代码原理是:
创建一个饱和度为0的颜色过滤器(饱和度为0时,view就变成了黑白色),将这个颜色过滤器通过setLayerType方法设置到当前View上。(此方法会开启此View的硬件加速功能)
确定了原理,我们现在要确定代码执行的时机:
因为我们要实现的功能是全局灰度化,所以首先view的选型最好是一个根view,这样我们就不用在每个子view上重复操作了,其次我们还要适配Dialog和PopWindow等Window类型的组件,所以我们不能从Activity上动心思了,因为Activity本质也是一个Window,所以综上所述,我们将代码的执行时机选在了WindowManager的addView方法上面,因为不论是Activity的创建还是Dialog等组件的创建最终都会走到这个方法里面。
我们只要在WindowManager执行addView方法时进行hook,加入我们上面的代码逻辑即可。
实现
上面确定了大体思路,我们现在来进一步分析实现方案:
首先,既然是要进行hook操作,那么我们就要先确定hook点:
WindowManager是一个接口类,其实现类为WindowManagerImpl,而WindowManagerImpl中真正实现窗口操作的类为WindowManagerGlobal,是一个单例类,我们看一下其addView的实现:
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
....
mViews.add(view);
....
}
可以发现方法内部将view保存到了一个list里面,那我们可以hook这个list,在其添加view的时候,我们就调用上面的代码将其置灰,那如何才能感知到view的添加操作呢?
实现具有数据感知能力的list:
我们可以自定义一个数组,让其继承于 ArrayList,在其执行添加删除操作时,通过回调进行通知,具体实现如下:
public class ObservableArrayList<T> extends ArrayList<T> {
private List<OnListChangeListener> mListeners = new ArrayList<>();
public void addOnListChangedListener(OnListChangeListener listener) {
if (mListeners != null) {
mListeners.add(listener);
}
}
public void removeOnListChangedListener(OnListChangeListener listener) {
if (mListeners != null) {
mListeners.remove(listener);
}
}
@Override
public boolean add(T object) {
super.add(object);
notifyAdd(size() - 1, 1);
return true;
}
@Override
public void add(int index, T object) {
super.add(index, object);
notifyAdd(index, 1);
}
@Override
public boolean addAll(Collection<? extends T> collection) {
int oldSize = size();
boolean added = super.addAll(collection);
if (added) {
notifyAdd(oldSize, size() - oldSize);
}
return added;
}
@Override
public void clear() {
int oldSize = size();
super.clear();
if (oldSize != 0) {
notifyRemove(0, oldSize);
}
}
@Override
public T remove(int index) {
T val = super.remove(index);
notifyRemove(index, 1);
return val;
}
@Override
public boolean remove(Object object) {
int index = indexOf(object);
if (index >= 0) {
remove(index);
return true;
} else {
return false;
}
}
@Override
public T set(int index, T object) {
T val = super.set(index, object);
if (mListeners != null) {
for (OnListChangeListener listener : mListeners) {
listener.onChange(this, index, 1);
}
}
return val;
}
@Override
protected void removeRange(int fromIndex, int toIndex) {
super.removeRange(fromIndex, toIndex);
notifyRemove(fromIndex, toIndex - fromIndex);
}
private void notifyAdd(int start, int count) {
if (mListeners != null) {
for (OnListChangeListener listener : mListeners) {
listener.onAdd(this, start, count);
}
}
}
private void notifyRemove(int start, int count) {
if (mListeners != null) {
for (OnListChangeListener listener : mListeners) {
listener.onRemove(this, start, count);
}
}
}
public interface OnListChangeListener {
void onChange(ArrayList list, int index, int count);
void onAdd(ArrayList list, int start, int count);
void onRemove(ArrayList list, int start, int count);
}
}
接下来就是执行hook操作:
/**
* @param enable 是否开启全局灰色调
*/
public static void enable(boolean enable) {
if (!enable) {
return;
}
try {
//灰色调Paint
final Paint mPaint = new Paint();
ColorMatrix mColorMatrix = new ColorMatrix();
mColorMatrix.setSaturation(0);
mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));
//反射获取windowManagerGlobal
@SuppressLint("PrivateApi")
Class<?> windowManagerGlobal = Class.forName("android.view.WindowManagerGlobal");
@SuppressLint("DiscouragedPrivateApi")
java.lang.reflect.Method getInstanceMethod = windowManagerGlobal.getDeclaredMethod("getInstance");
getInstanceMethod.setAccessible(true);
Object windowManagerGlobalInstance = getInstanceMethod.invoke(windowManagerGlobal);
//反射获取mViews
Field mViewsField = windowManagerGlobal.getDeclaredField("mViews");
mViewsField.setAccessible(true);
Object mViewsObject = mViewsField.get(windowManagerGlobalInstance);
//创建具有数据感知能力的ObservableArrayList
ObservableArrayList<View> observerArrayList = new ObservableArrayList<>();
observerArrayList.addOnListChangedListener(new ObservableArrayList.OnListChangeListener() {
@Override
public void onChange(ArrayList list, int index, int count) {
}
@Override
public void onAdd(ArrayList list, int start, int count) {
View view = (View) list.get(start);
if (view != null) {
view.setLayerType(View.LAYER_TYPE_HARDWARE, mPaint);
}
}
@Override
public void onRemove(ArrayList list, int start, int count) {
}
});
//将原有的数据添加到新创建的list
observerArrayList.addAll((ArrayList<View>) mViewsObject);
//替换掉原有的mViews
mViewsField.set(windowManagerGlobalInstance, observerArrayList);
} catch (Exception e) {
e.printStackTrace();
}
}
最后,只需在Application的onCreate中调用上述代码进行hook即可。
已知存在的问题
- 开启了View的硬件加速,可能会存在兼容性问题
- 对于SurfaceView和Textureview以及其子类无效
对于问题1笔者在项目中暂未遇到问题,如果读者朋友们在项目中遇到兼容性问题,欢迎留言;
对于问题2其原因是因为这两个组件内部有独立的绘图表面(Surface),笔者目前未想到好的解决方案,如果有思路欢迎一块交流。
组件封装
为了方便大家使用,我将上述代码封装成了组件并上传到了jitpack,使用方法如下:
首先在你项目的build.gradle文件中添加jitpack的仓库
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
添加依赖:
implementation 'com.github.U2tzJTNE:GlobalGray:1.0'
Application的onCreate中调用:
GlobalGray.enable(true);