获取另一个apk中的资源文件

如何获取另一个apk的资源?在聊这个问题前,先来看看我们在程序中调用getResources()方法,为何获取的是自己apk中的资源文件。

追踪程序中getResources()方法,首先我们找到ContxtThemeWrapper.java中的如下代码:

@Override
public Resources getResources() {
    if (mResources != null) {
        return mResources;
    }
    if (mOverrideConfiguration == null) {
        mResources = super.getResources();
        return mResources;
    } else {
        Context resc = createConfigurationContext(mOverrideConfiguration);
        mResources = resc.getResources();
        return mResources;
    }
}

我们看到mResources是从resc对象中对象得到的,我们跟进createConfigurationContext方法,最终会发现这个方法是在ContextImpl.java这个类中实现的,ContextImpl.java继承自Context.java,当然我们从类名上就可以看出,下面是ContextImpl.java中对于createConfigurationContext方法的具体实现:

@Override
public Context createConfigurationContext(Configuration overrideConfiguration) {
    if (overrideConfiguration == null) {
        throw new IllegalArgumentException("overrideConfiguration must not be null");
    }

    ContextImpl c = new ContextImpl();
    c.init(mPackageInfo, null, mMainThread);
    c.mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(),
            getDisplayId(), overrideConfiguration, mResources.getCompatibilityInfo(),
            mActivityToken);
    return c;
}

我们关注第8行c.init方法,这个方法调用的是下面重载的init方法。

final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread,
            Resources container, String basePackageName, UserHandle user) {

        ...

        if (mResources != null &&
                ((compatInfo != null && compatInfo.applicationScale !=
                        mResources.getCompatibilityInfo().applicationScale)
                || activityToken != null)) {
            if (DEBUG) {
                Log.d(TAG, "loaded context has different scaling. Using container's" +
                        " compatiblity info:" + container.getDisplayMetrics());
            }
            if (compatInfo == null) {
                compatInfo = packageInfo.getCompatibilityInfo();
            }
            mDisplayAdjustments.setCompatibilityInfo(compatInfo);
            mDisplayAdjustments.setActivityToken(activityToken);
            mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(),
                    Display.DEFAULT_DISPLAY, null, compatInfo, activityToken);
        } else {
            mDisplayAdjustments.setCompatibilityInfo(packageInfo.getCompatibilityInfo());
            mDisplayAdjustments.setActivityToken(activityToken);
        }

        ...
    }

我们看到其中的mResource成员变量就是资源类的一个声明引用,它是由mResourcesManager的getTopLevelResources方法得到,并且我们回看createConfigurationContext方法c.init()方法后面一步仍是调用了getTopLevelResources, 继续跟进getTopLevelResources方法,注意,传个这个方法的第一个参数的值是mPackageInfo.getResDir(),这个值就是我们自己apk的的路径。

public Resources getTopLevelResources(String resDir, int displayId,
        Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {

    ...

    Resources r;
    synchronized (this) {
        // Resources is app scale dependent.
        if (false) {
            Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
        }
        WeakReference<Resources> wr = mActiveResources.get(key);
        r = wr != null ? wr.get() : null;
        //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
        if (r != null && r.getAssets().isUpToDate()) {
            if (false) {
                Slog.w(TAG, "Returning cached resources " + r + " " + resDir
                        + ": appScale=" + r.getCompatibilityInfo().applicationScale);
            }
            return r;
        }
    }

    //if (r != null) {
    //    Slog.w(TAG, "Throwing away out-of-date resources!!!! "
    //            + r + " " + resDir);
    //}

    AssetManager assets = new AssetManager();
    if (assets.addAssetPath(resDir) == 0) {
        return null;
    }

    ...

    r = new Resources(assets, dm, config, compatInfo, token);

    ...
}

这里使用了弱引用的技术,如果r在缓存中存在就从wr(WeakReference)中获取然后直接返回,不存在就调用new Resources(assets, dm, config, compatInfo, token);方法新建一个Resouces对象,在构造方法中传入了一个资源对象,我们主要关注一下获取资源的方法 很明显就是assets.addAssetPath(resDir) 这个了。

到这里就可以知道为什么用getResources()获取的是自己app的资源文件,因为这里传的是packageInfo.getResDir(),这是我们自己apk的的路径。 所以要获取别的apk的资源 ,我们可以在我们的app里面调用这个方法传入想要获取的apk路径,就可以获取别的apk资源。

但是想法虽好,现实是残酷的,我们发现addAssetPath方法是被加了@hide的,也就是我们不能直接在程序中使用。这时我们又想到了万能的反射技术,下面是具体代码:

String dexpath= "";
try {
    //所需的资源的apk的APK路径
    dexpath = getPackageManager().getApplicationInfo("com.chm.main", 0).sourceDir;
    Toast.makeText(this, dexpath, Toast.LENGTH_SHORT).show();
} catch (PackageManager.NameNotFoundException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
loadRes(dexpath);

loadRes方法:

private void loadRes(String path) {
    try {
        am = AssetManager.class.newInstance();
        Method addAssPath = AssetManager.class.getMethod("addAssetPath", String.class);
        addAssPath.invoke(am, path);
    } catch (Exception e) {
        e.printStackTrace();
    }
    rs = new Resources(am, super.getResources().getDisplayMetrics(), super.getResources().getConfiguration());
}

然后可以像下面这样给一个ImageView设置背景图标

iv.setImageDrawable(rs.getDrawable(R.drawable.message_pay));

这里有一个问题,如果你是想获取另一个apk中的message_pay图片,而这个方法中这张message_pay图片肯定是本apk中的,那么它们分别在R类中随机生成的对应整数很可能是不一致的。所以大多数情况下,这并不能获取我们真正想要的资源文件。甚至会抛出异常,如果找不到图片。

看到这里大家是不是很郁闷,那么我们再来看看另一种获取其他apk资源的方式。

也是用到了反射,我们直接看代码:

private void setImage(String dexpath){
    DexClassLoader loader = new DexClassLoader(dexpath, getApplicationInfo().dataDir, null, this.getClass().getClassLoader());
    try {
        Class<?> clazz = loader.loadClass("com.chm.main.HellogpsActivity");
        Method getImageId = clazz.getMethod("getImageId");
        //调用静态方法
//            int ic_launcher = (Integer) getImageId.invoke(clazz);
        int ic_launcher = (int) getImageId.invoke(clazz.newInstance());
        iv.setImageDrawable(getResourcs().getDrawable(ic_launcher));
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

这里我们通过DexClassLoader类加载器获取到了另一个apk的Activity类,然后调用了该Activity中的 getImageId方法。这种方式可以返回我们真正想要的另一个apk中的资源,但是那也必须让那个apk给我们提供相关的方法返回资源对应的随机数,我们通过这个随机数才能拿到这个资源。

最后,总的来说我们想完全准确的拿到与你毫无关系的apk中资源是做不到的,要不然岂不是天下大乱,但是如果我们做插件这样的东西到是可以用用。

欢迎关注公众号。
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值