qq游戏大厅中解析不安装apk的研究

首先我是根据 这篇文章开始的我的研究。

一,加载dex文件:

DexClassLoader loader = new DexClassLoader(APK_PATH, getApplication().getCacheDir().getPath(), null, this.getClassLoader());

第一个参数是apk路径,第二个参数用调用者的缓存地址作为加载路径,第三个不用管,第四个是调用者的ClassLoader,其实也就是SystemClassLoader(可以换成默认的)

看了QQ游戏大厅的代码,基本上就做到了这一步,把代码加载出来,但是被加载的apk代码里是不能引用任何资源的(例如R.drawable.XX),它的做法是把所有的资源都放在Assets文件夹中,然后用AssetsManager去解析。

这样做好处是绕过了下面我的步骤

坏处很多: 1. .不能利用android提供的为不同dpi设置不同的图片decode比例,要自己写

2. 不能用xml文件,未安装的apk里所有界面需要在代码里写(我去,这多大的工作量啊!)

3. 基本不能国际化了,他无法用R.string.XXX也就说明显示的字符串都写在代码里了(我去,悲剧啊)

怎么证明我以上说的?反编译下qq游戏大厅下的Apk文件,会发现它res下只有一个icon文件,什么都没有。所有图片都在assets文件夹下,且任何地方都没有布局文件和string.xml

二,加载res文件:

哈哈,qq游戏大厅的人看到这是不是要哭了?其实res文件是可以加载出来的,为什么这么说,我们来看一下Resources类是怎么加载出一个drawable的,然后在试图找到一个方法骗过系统。

public Drawable getDrawable(int id) throws NotFoundException {synchronized (mTmpValue) {TypedValue value = mTmpValue;getValue(id, value, true);//取得id的存储信息到value中return loadDrawable(value, id);//真正拿drawable的方法,我们继续看}} InputStream is = mAssets.openNonAsset(value.assetCookie, file, AssetManager.ACCESS_STREAMING);//loadDrawable最核心就是这句,发现没?他是从Assetmanager里面读到的(其实loadDrawable还有别的分支,但是最后都是从AssetManager去的资源,有兴趣看源码吧)

这里我们需要知道两个函数,,一个是assetManager中有个方法叫addAssetPath

(可能需要反射,文章中提到的方法在sdk中不能直接找到的都需要反射,或者引用framework.jar,不再重复说明),这里我们就可以把刚才用到的apk路径传进去,让某个AssetManager可以引用到未安装的apk资源。

Resources有一个构造方法:

public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {this(assets, metrics, config, null);}这里我们把刚才构造好的AssetManager传进去就得到了一个特别的Resources对吧?离成功又近了一步。这里我要说明一下我们下一步的目标是解析出一个含有自建类的,引用了R资源的xml布局文件(基本可以满足qq游戏大厅的现有加载需求)。

我们都知道,想要加载其它已安装的apk中xml,可以通过Context中这个方法实现:(不知道的去补姿势!^~^)

public abstract Context createPackageContext(String packageName, int flags) throws PackageManager.NameNotFoundException;想调用这个方法,我们需要一个context,当然不能用调用者自己的,我们moke一个吧:

class MyContext extends ContextWrapper然后它持有我们刚才辛苦搞出来的Resources和classLoader,并且在重载方法getClassLoader、getResources中返回他们,都搞定了?没,我们发现在load xml时候出错了,问题在于我们未安装的apk中解析xml方式使用的方法是调用者的逻辑,而不是我们moke的context的,怎么办?重载getSystemService,在传入LAYOUT_INFLATER_SERVICE的时候用PolicyManager.makeNewLayoutInflater方法造一个假的LayoutInflater,这样解析xml时候会在我们moke的上下文中进行,也就是我们刚造出来的假Resources中。

都搞定了?哈哈,还没有。这时我们发现代码中引用的R.drawable.XXX可以运行了,但是在xml中的引用是不行的,问题出在哪?我们看源码吧:

View构造的时候会调用TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View,\defStyle, 0);实现解析xml里面的资源引用等,这个方法实现很简单getTheme().obtainStyledAttributes(set, attrs, defStyleAttr, defStyleRes);刚才出现的问题在于我们没有重载getTheme方法,走了错的路径。可是这东西怎么重载?没关系,我们看看Android自己怎么实现的,我们在ContextThemeWrapper发现了这样的代码(2.3是在ComtextImp类中,而且实现略有不同)@Override public Resources.Theme getTheme() {if (mTheme != null) {return mTheme;}mThemeResource = Resources.selectDefaultTheme(mThemeResource,getApplicationInfo().targetSdkVersion);initializeTheme();return mTheme;}看懂了吧,我们就按照他的做法,new重来一个新的Theme就可以了。这里我们的context还需要重载getPackageName,原因类似,返回未安装的apk报名就可以了。

到这里,我们终于可以从一个未安装的apk中得到了他的一个layout,其实也就是可以显示我们想要的所有东西(像qq游戏这种只有在一个界面上画东西的应用,其实已经可以满足所有需求了)。特别说明下,未安装apk的preference是写在调用者的data\data目录下。

最后,我们还有哪些做不了?启动activity、service、provider不行,因为没注册在系统里,解决办法:可以在moke的context定义一些特殊回调方法,未安装apk中反射调用它,让调用者启动一个新activity,并且加载自己另外一个layout。service和provider暂时无解。

却坐在不足一平米的椅子上。

qq游戏大厅中解析不安装apk的研究

相关文章:

你感兴趣的文章:

标签云: