最近做游戏联运的sdk插件,里面涉及到了游戏的登录、应用内支付等功能,故而界面的布局是少不了的。首先,想到的是xml布局,但是,作为第三方的sdk插件,作为jar包需要加到第三方的libs中,xml界面布局是不被打包到jar包中的。虽然,xml布局文件可以通过反射获取到,但是,这对第三方的要求是所有文件必须放的位置正确,不得出现疏忽。为此,想到,如果把所有的资源文件全部放到一个apk中,然后去加载这个apk的资源,那问题岂不是都解决了?带着这个想法,一步一步来实现这个功能。
众所周知,apk中的资源文件基本上全部放到res目录下面,所以,怎么获取到资源apk的res实体是首要目标。以下代码就是获取资源apk中的Resource:
package com.tabolt.reflect;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import android.content.Context;
import android.content.res.Resources;
import android.util.DisplayMetrics;
public class GetOtherApkRes {
/**
*获取指定路径apk的resources
*@param context 上下文
*@param apkPath apk绝对路径
*/
public static Resources getResources(Context context, String apkPath) {
File file = new File(apkPath);
String PATH_PackageParser = "android.content.pm.PackageParser";
String PATH_AssetManager = "android.content.res.AssetManager";
try {
// 反射得到pkgParserCls对象并实例化,有参数
Class<?> pkgParserCls = Class.forName(PATH_PackageParser);
Class<?>[] typeArgs = { String.class };
Constructor<?> pkgParserCt = pkgParserCls.getConstructor(typeArgs);
Object[] valueArgs = { file.getAbsolutePath() };
Object pkgParser = pkgParserCt.newInstance(valueArgs);
// 从pkgParserCls类得到parsePackage方法
DisplayMetrics metrics = new DisplayMetrics();
metrics.setToDefaults();// 这个是与显示有关的, 这边使用默认
typeArgs = new Class<?>[] { File.class, String.class,DisplayMetrics.class, int.class };
Method pkgParser_parsePackageMtd = pkgParserCls.getDeclaredMethod("parsePackage", typeArgs);
valueArgs = new Object[] { file, file.getAbsolutePath(), metrics, 0 };
// 执行pkgParser_parsePackageMtd方法并返回
Object pkgParserPkg = pkgParser_parsePackageMtd.invoke(pkgParser,valueArgs);
// 从返回的对象得到名为"applicationInfo"的字段对象
if (pkgParserPkg == null) {
return null;
}
Field appInfoFld = pkgParserPkg.getClass().getDeclaredField("applicationInfo");
// 从对象"pkgParserPkg"得到字段"appInfoFld"的值
if (appInfoFld.get(pkgParserPkg) == null) {
return null;
}
// 反射得到assetMagCls对象并实例化,无参
Class<?> assetMagCls = Class.forName(PATH_AssetManager);
Object assetMag = assetMagCls.newInstance();
// 从assetMagCls类得到addAssetPath方法
typeArgs = new Class[1];
typeArgs[0] = String.class;
Method assetMag_addAssetPathMtd = assetMagCls.getDeclaredMethod("addAssetPath", typeArgs);
valueArgs = new Object[1];
valueArgs[0] = file.getAbsolutePath();
// 执行assetMag_addAssetPathMtd方法
assetMag_addAssetPathMtd.invoke(assetMag, valueArgs);
// 得到Resources对象并实例化,有参数
Resources res = context.getResources();
typeArgs = new Class[3];
typeArgs[0] = assetMag.getClass();
typeArgs[1] = res.getDisplayMetrics().getClass();
typeArgs[2] = res.getConfiguration().getClass();
Constructor<Resources> resCt = Resources.class.getConstructor(typeArgs);
valueArgs = new Object[3];
valueArgs[0] = assetMag;
valueArgs[1] = res.getDisplayMetrics();
valueArgs[2] = res.getConfiguration();
res = (Resources) resCt.newInstance(valueArgs);
return res;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
ok,上面的代码已经获取到了资源apk里面的Resource,然后接下来就是获取你要得到的资源了。
第一步:Resource中有个方法是getIdentifie,三个参数分别是 资源名,资源类型,资源apk包名,通过此方法可以获取到资源的id号。
第二步:上一步获取到资源id号之后,接下来就是要得到实质性的资源了。对于简单类型的资源,如:
(1)图片资源,可以获取到Drawable类型的图片
public Drawable getResApkDrawable(int id) {
return res.getDrawable(id);
}
(2)String类型资源,获取String文件夹中的字符串
public String getResApkString(int id) {
return res.getString(id)
}
对于color,dimens文件中的资源,获取方式与以上两种相同,再次不在赘述。
(3.1) layout布局资源的获取,要想获取layout资源,我采用的是根据id先获取XmlPullParser,然后再调用LayoutInflater的inflate方法获取layout资源,视图view。
public View getResApkLayoutView(Context context, int id) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return inflater.inflate(res.getLayout(id), null);
}
(3.2) 上一步获取到layout布局,然后就是要获取到layout中控件的实体view了,很简单,
public View getResApkWidgetView(View layout, int id) {
return layout.findViewById(id);
}
(4) anim动画资源的获取,res中没有方法可以直接获取到Animation,只有方法getAnimation获取到XmlPullParser,查询AnimationUtils的loadAnimation方法,发现加载Animation的最后一步是方法createAnimationFromXml,此方法的源码是:
private Animation createAnimationFromXml(Context c, XmlPullParser parser,AnimationSet parent,AttributeSet attrs) throws XmlPullParserException, IOException {
Animation anim = null;
int type;
int depth = parser.getDepth();
while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) &&
type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
String name = parser.getName();
if (name.equals("set")) {
anim = new AnimationSet(c, attrs);
createAnimationFromXml(c, parser, (AnimationSet) anim, attrs);
} else if (name.equals("alpha")) {
anim = new AlphaAnimation(c, attrs);
} else if (name.equals("scale")) {
anim = new ScaleAnimation(c, attrs);
} else if (name.equals("rotate")) {
anim = new RotateAnimation(c, attrs);
} else if (name.equals("translate")) {
anim = new TranslateAnimation(c, attrs);
} else {
throw new RuntimeException("Unknown animation name: "+ parser.getName());
}
if (parent != null) {
parent.addAnimation(anim);
}
}
return anim;
}
还有就是attrs的获取,AttributeSet attrs = Xml.asAttributeSet(parser);至此,动画Animation的获取算是完毕,最后的调用方法是:
public Animation getResApkAnim(Context context, int id) {
Animation animation = null;
XmlPullParser parser = res.getAnimation(id);
AttributeSet attrs = Xml.asAttributeSet(parser);
try {
animation = createAnimationFromXml(context, parser, null, attrs);
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return animation;
}
好了,到现在为止,资源apk中的要获取的资源现在都可以获取的到了,需要注意的是,获取layout布局视图view的时候,布局中用到的@资源的设置失效,不建议使用。