获取未安装资源apk种的资源文件

最近做游戏联运的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的时候,布局中用到的@资源的设置失效,不建议使用。