之前看过大神的 美团组件化方案 ,其中提到了通过servicelaoder进行解耦的思路,主要是通过配置接口及其实现类的方式坐到接口隔离作用,本文主要是实现此思路并延伸出通过加载自定义properties文件获取参数配置信息
通过查看ServiceLoader源码可知,ServiceLoader是通过加载META-INF/services/路径下的接口实现类,加载方式是通过读取配置文件并通过反射的方式获取类的实例
1配置文件读取,获取文件流
其中PREFIX = "META-INF/services/";
由此可见加载路径是META-INF文件夹下面的文件
2通过流获取实现类全路径
其中parseLine方法里面是做了类全路径名校验
思路:
1读取配置文件
2获取配置的类全名
3通过反射获取类的实例
我们的配置文件将写在assets文件夹下
通过查看apk包结构可以发现assets文件夹位置是与META-INF平级的,由此我们可以将系统的ServiceLoader加载文件路径改为assets路径
1配置文件读取,获取文件流
2通过流获取接口类与实现类的对应集合
由于接口类与实现类是一对一关系,所以通过Map以键值对的方式存储接口类与实现类,在系统ServiceLoader做简单修改:
3获取实现类
在上一步已经获取了所有接口类和实现类的集合,在此通过接口类全名来获取实现类全名,并通过反射的方式获取实现类实例:
到此我们自定义ServiceLoader已经初步实现,在实际开发中,我们一般只需要一个实例及单利,在此我们可以用Map将类的实例与接口类名绑定起来即可。
使用
加载properties配置参数的思路与ServiceLoader基本一致,只是获取配置参数可以通过java类Properties获取
1获取流
和自定义ServiceLoader获取流一致
2获取Properties实例
3获取value
4使用
1在查看Iterable 接口时无意中发现了default关键字,经查看资料显示为java8新加的,用于在接口中写默认的方法函数体
有兴趣的可以去 >
一个app不可以直接访问另一个app的类。如果非要实现进程间通信,可以通过aidl做。可以考虑采用jar包的方式,将app B 打成jar文件 导入到 app A 中 就可以实现了。在应用中可以反射到类似framework里面的 diaolog activity这些类,是因为import了这些类,所以在这些包以jar的形式编译到应用。相反,是因为以jar的形式编译到了应用,才有了import。如果没有jar,就import不了。
项目开发中,为了用户信息的安全,会有禁止页面被截屏、录屏的需求。
这类资料,在网上有很多,一般都是通过设置Activity的Flag解决,如:
//禁止页面被截屏、录屏getWindow()addFlags(WindowManagerLayoutParamsFLAG_SECURE);
这种设置可解决一般的防截屏、录屏的需求。
如果页面中有d出Popupwindow,在录屏视频中的效果是:
非Popupwindow区域为黑色
但Popupwindow区域仍然是可以看到的
如下面两张Gif图所示:
未设置FLAG_SECURE,录屏的效果,如下图(git中间的水印忽略):
设置了FLAG_SECURE之后,录屏的效果,如下图(git中间的水印忽略):
原因分析
看到了上面的效果,我们可能会有疑问PopupWindow不像Dialog有自己的window对象,而是使用WindowManageraddView方法将View显示在Activity窗体上的。那么,Activity已经设置了FLAG_SECURE,为什么录屏时还能看到PopupWindow?
我们先通过getWindow()addFlags(WindowManagerLayoutParamsFLAG_SECURE);来分析下源码:
1、Windowjava
//window布局参数private final WindowManagerLayoutParams mWindowAttributes = new WindowManagerLayoutParams();//添加标识public void addFlags(int flags) {
setFlags(flags, flags);
}//通过mWindowAttributes设置标识public void setFlags(int flags, int mask) { final WindowManagerLayoutParams attrs = getAttributes();
attrsflags = (attrsflags&~mask) | (flags&mask);
mForcedWindowFlags |= mask;
dispatchWindowAttributesChanged(attrs);
}//获得布局参数对象,即mWindowAttributespublic final WindowManagerLayoutParams getAttributes() { return mWindowAttributes;
}
通过源码可以看到,设置window属性的源码非常简单,即:通过window里的布局参数对象mWindowAttributes设置标识即可。
2、PopupWindowjava
//显示PopupWindowpublic void showAtLocation(View parent, int gravity, int x, int y) {
mParentRootView = new WeakReference<>(parentgetRootView());
showAtLocation(parentgetWindowToken(), gravity, x, y);
}//显示PopupWindowpublic void showAtLocation(IBinder token, int gravity, int x, int y) { if (isShowing() || mContentView == null) { return;
}
TransitionManagerendTransitions(mDecorView);
detachFromAnchor();
mIsShowing = true;
mIsDropdown = false;
mGravity = gravity;
//创建Window布局参数对象
final WindowManagerLayoutParams p =createPopupLayoutParams(token);
preparePopup(p);
px = x;
py = y;
invokePopup(p);
}//创建Window布局参数对象protected final WindowManagerLayoutParams createPopupLayoutParams(IBinder token) { final WindowManagerLayoutParams p = new WindowManagerLayoutParams();
pgravity = computeGravity();
pflags = computeFlags(pflags);
ptype = mWindowLayoutType;
ptoken = token;
psoftInputMode = mSoftInputMode;
pwindowAnimations = computeAnimationResource(); if (mBackground != null) {
pformat = mBackgroundgetOpacity();
} else {
pformat = PixelFormatTRANSLUCENT;
} if (mHeightMode < 0) {
pheight = mLastHeight = mHeightMode;
} else {
pheight = mLastHeight = mHeight;
} if (mWidthMode < 0) {
pwidth = mLastWidth = mWidthMode;
} else {
pwidth = mLastWidth = mWidth;
}
pprivateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH
| PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
psetTitle("PopupWindow:" + IntegertoHexString(hashCode())); return p;
}//将PopupWindow添加到Window上private void invokePopup(WindowManagerLayoutParams p) { if (mContext != null) {
ppackageName = mContextgetPackageName();
} final PopupDecorView decorView = mDecorView;
decorViewsetFitsSystemWindows(mLayoutInsetDecor);
setLayoutDirectionFromAnchor();
mWindowManageraddView(decorView, p); if (mEnterTransition != null) {
decorViewrequestEnterTransition(mEnterTransition);
}
}
通过PopupWindow的源码分析,我们不难看出,在调用showAtLocation时,会单独创建一个WindowManagerLayoutParams布局参数对象,用于显示PopupWindow,而该布局参数对象上并未设置任何防止截屏Flag。
如何解决
原因既然找到了,那么如何处理呢?
再回头分析下Window的关键代码:
//通过mWindowAttributes设置标识public void setFlags(int flags, int mask) { final WindowManagerLayoutParams attrs = getAttributes();
attrsflags = (attrsflags&~mask) | (flags&mask);
mForcedWindowFlags |= mask;
dispatchWindowAttributesChanged(attrs);
}
其实只需要获得WindowManagerLayoutParams对象,再设置上flag即可。
但是PopupWindow并没有像Activity一样有直接获得window的方法,更别说设置Flag了。我们再分析下PopupWindow的源码:
//将PopupWindow添加到Window上private void invokePopup(WindowManagerLayoutParams p) { if (mContext != null) {
ppackageName = mContextgetPackageName();
}
final PopupDecorView decorView = mDecorView;
decorViewsetFitsSystemWindows(mLayoutInsetDecor);
setLayoutDirectionFromAnchor(); //添加View
mWindowManageraddView(decorView, p); if (mEnterTransition != null) {
decorViewrequestEnterTransition(mEnterTransition);
}
}
我们调用showAtLocation,最终都会执行mWindowManageraddView(decorView, p);
那么是否可以在addView之前获取到WindowManagerLayoutParams呢?
答案很明显,默认是不可以的。因为PopupWindow并没有公开获取WindowManagerLayoutParams的方法,而且mWindowManager也是私有的。
如何才能解决呢?
我们可以通过hook的方式解决这个问题。我们先使用动态代理拦截PopupWindow类的addView方法,拿到WindowManagerLayoutParams对象,设置对应Flag,再反射获得mWindowManager对象去执行addView方法。
风险分析:
不过,通过hook的方式也有一定的风险,因为mWindowManager是私有对象,不像Public的API,谷歌后续升级Android版本不会考虑其兼容性,所以有可能后续Android版本中改了其名称,那么我们通过反射获得mWindowManager对象不就有问题了。不过从历代版本的Android源码去看,mWindowManager被改的几率不大,所以hook也是可以用的,我们尽量写代码时考虑上这种风险,避免以后出问题。
public class PopupWindow {
private WindowManager mWindowManager;
}
而addView方法是ViewManger接口的公共方法,我们可以放心使用。
public interface ViewManager{ public void addView(View view, ViewGroupLayoutParams params); public void updateViewLayout(View view, ViewGroupLayoutParams params); public void removeView(View view);
}
功能实现
考虑到hook的可维护性和扩展性,我们将相关代码封装成一个独立的工具类吧。
package comcccdddtestpopupwindowutils;
import androidosHandler;
import androidviewWindowManager;
import androidwidgetPopupWindow;
import javalangreflectField;
import javalangreflectInvocationHandler;
import javalangreflectMethod;
import javalangreflectProxy;public class PopNoRecordProxy implements InvocationHandler { private Object mWindowManager;//PopupWindow类的mWindowManager对象
public static PopNoRecordProxy instance() { return new PopNoRecordProxy();
} public void noScreenRecord(PopupWindow popupWindow) { if (popupWindow == null) { return;
} try { //通过反射获得PopupWindow类的私有对象:mWindowManager
Field windowManagerField = PopupWindowclassgetDeclaredField("mWindowManager");
windowManagerFieldsetAccessible(true);
mWindowManager = windowManagerFieldget(popupWindow); if(mWindowManager == null){ return;
} //创建WindowManager的动态代理对象proxy
Object proxy = ProxynewProxyInstance(HandlerclassgetClassLoader(), new Class[]{WindowManagerclass}, this); //注入动态代理对象proxy(即:mWindowManager对象由proxy对象来代理)
windowManagerFieldset(popupWindow, proxy);
} catch (IllegalAccessException e) {
eprintStackTrace();
} catch (NoSuchFieldException e) {
eprintStackTrace();
}
}
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //拦截方法mWindowManageraddView(View view, ViewGroupLayoutParams params);
if (method != null && methodgetName() != null && methodgetName()equals("addView")
&& args != null && argslength == 2) { //获取WindowManagerLayoutParams,即:ViewGroupLayoutParams
WindowManagerLayoutParams params = (WindowManagerLayoutParams) args[1]; //禁止录屏
setNoScreenRecord(params);
}
} catch (Exception ex) {
exprintStackTrace();
} return methodinvoke(mWindowManager, args);
} /
禁止录屏
/
private void setNoScreenRecord(WindowManagerLayoutParams params) {
setFlags(params, WindowManagerLayoutParamsFLAG_SECURE, WindowManagerLayoutParamsFLAG_SECURE);
} /
允许录屏
/
private void setAllowScreenRecord(WindowManagerLayoutParams params) {
setFlags(params, 0, WindowManagerLayoutParamsFLAG_SECURE);
} /
设置WindowManagerLayoutParams flag属性(参考系统类WindowsetFlags(int flags, int mask))
@param params WindowManagerLayoutParams
@param flags The new window flags (see WindowManagerLayoutParams)
@param mask Which of the window flag bits to modify
/
private void setFlags(WindowManagerLayoutParams params, int flags, int mask) { try { if (params == null) { return;
} paramsflags = (paramsflags & ~mask) | (flags & mask);
} catch (Exception ex) {
exprintStackTrace();
}
}
}
Popwindow禁止录屏工具类的使用,代码示例:
//创建PopupWindow
//正常项目中,该方法可改成工厂类
//正常项目中,也可自定义PopupWindow,在其类中设置禁止录屏
private PopupWindow createPopupWindow(View view, int width, int height) {
PopupWindow popupWindow = new PopupWindow(view, width, height); //PopupWindow禁止录屏
PopNoRecordProxyinstance()noScreenRecord(popupWindow); return popupWindow;
} //显示Popupwindow
private void showPm() {
View view = LayoutInflaterfrom(this)inflate(Rlayoutpm1, null);
PopupWindow pw = createPopupWindow(view,ViewGroupLayoutParamsWRAP_CONTENT, ViewGroupLayoutParamsWRAP_CONTENT);
pw1setFocusable(false);
pw1showAtLocation(thisgetWindow()getDecorView(), GravityBOTTOM | GravityRIGHT, PopConstPopOffsetX, PopConstPopOffsetY);
}
录屏效果图:
双亲委托机制
类在进行类加载的时候,把加载任务托管给父类加载器,如能加载成功,则返回,否则依次向子类加载器递归尝试类加载。
意义:
①避免类的重复加载,父类加载已加载该类时,子ClassLoader就没有必要加载一次了。
②安全性,防止核心API被随意篡改。
ClassLoader
ClassLoader本身是一个抽象方法。它的主要实现类有BootClassLoader、PathClassLoader、DexClassLoader
BootClassLoader:用于加载Android Framwork层(SDK)的class文件
PathClassLoader:用于Android应用程序加载器,可以加载指定的dex和jar、zip、apk中的classesdex(系统使用)
DexClassLoader:用于加载指定的dex和jar、zip、apk中的classesdex。(供开发者使用)
拓展:
在API26之前。
optimizedDirectory 参数就是dexopt的产出目录(odex)。那 PathClassLoader 创建时,这个目录为null,就
意味着不进行dexopt?并不是, optimizedDirectory 为null时的默认路径为:/data/dalvik-cache。
在API26之后DexClassLoader也取消了optimizedDirectory
热修复相关
LoadClass:
findClass:PathClassLoader和DexClassLoader的父类BaseDexClassLoader中实现findClass。
BaseDexClassLoader中
PathClassLoader加载过后,pathlist 中存在一个Element数组,Element类中存在一个dexFile成员表示dex文件,即:APK中有X个dex,则Element数组就有X个元素。
总结:
可能看到这里我们比较乱了,理一下。一个类的加载经历了哪些。我们以PathClassLoader为例。
①加载一个类的时候,首先通过Class缓存寻找是否已经加载过该类。参考抽象类的loadClass方法。
②若在缓存中未找到该类,则交由父加载器加载该类。参考抽象类的loadClass方法。
③调用父加载器PathClassLoader的父类BaseDexClassLoader实现的findClass方法加载该类。
④PathClassLoader在初始化的时候调用父构造方法实例化DexPathList属性,DexPathList属性初始化时构造方法内通过makePathElements(或makeDexElements 不同API可能不同)加载APK内的dex文件生成Element数组。
⑤BaseDexClassLoader实现的findClass方法中顺序循环已存在的Element数组,通过Element中的DexFile加载类。。
⑥未找到,抛出类未找到异常。
热修复(multide 形式(thinker、qfix))
热修复的原理。我们只需在应用启动的时候,一般是在application方法中(因为class加载首先从缓存中加载),在应用启动后,经过PathClassLoader加载过后所有的类都在 pathList的Element 数组,把生成的Elment数组插入到PathList的Element数组的最前方。在加载类的时候就只会加载到我们需要更新的类了,因为是顺序寻找,找到就返回。(先从我们补丁的dex文件生成的element寻找,找不到再从APK的dex生成的element种寻找)。
热修复基本思路总结:
①获取到当前引用的PathClassLoader
②反射获取其中DexPathList属性:DexPathList pathList
③获取到补丁包pathdex文件的Element[]数组 pElements。参考PathClassLoader怎么把dex文件转换为Element数组的。于是我们反射执行DexPathList 中的makePathElements方法(视API而定)传入dex路径得到补丁包的element数组。
④获取pathList的dexElements数组。
⑤把补丁包的pElements数组合并到pathList的dexElements数组的前方,即newElements=pElements+dexElements
⑥反射赋值把newElements替换掉pathList的dexElements
热修复没这么简单,还需考虑混淆,API版本不同导致的使用makePathElements方法或makeDexElements方法等因素。
热修复(InstantRun 形式(Robust))待了解。
以上就是关于android自定义serviceloader接口隔离及获取自定义properties参数配置全部的内容,包括:android自定义serviceloader接口隔离及获取自定义properties参数配置、怎么反射android的隐藏API,例如我要反射出“IBatteryStats”类、android 可以反射到另一个应用里面的类么等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)