android自定义serviceloader接口隔离及获取自定义properties参数配置

android自定义serviceloader接口隔离及获取自定义properties参数配置,第1张

  之前看过大神的 美团组件化方案 ,其中提到了通过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 可以反射到另一个应用里面的类么等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9539021.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-29
下一篇 2023-04-29

发表评论

登录后才能评论

评论列表(0条)

保存