Android APP中移除framework.jar包

1、背景

在之前的一篇文章里已经分享过了如何使用Android Studio + Gradle编译整机APK[https://blog.csdn.net/skysukai/article/details/86616309], 既然app已经可以脱离整机码源编译了,那是否可以走得更远些,将工程中的framework.jar给移除掉呢?答案是有可能的。
为什么会使用framework.jar的API而不直接使用Android提供的API呢?因为framework中还有许多接口未对外开放,在Google的官方叫法中,这些API叫non-sdk interface,这些非sdk的接口只在framework中被使用(也就是Android整机代码),未将他们开放出来当成普通的sdk interface。比如常见的SystemProperties就是一个非sdk接口。
APP是一款文件管理器,基本功能就是提供文件的快速访问。如果要将代码中依赖的framework.jar给移除掉,首先要解决的问题就是在什么地方使用了framework的API。

2、确定工程中使用framework API的地方

在build.gradle文件中注释掉编译framework.jar的地方,这样就能快速得到代码中哪些地方使用了framework中的API。

import android.os.UserHandle;
import android.os.SystemProperties;
import android.os.storage.VolumeInfo;
……

上面给出的import就是使用了framework中非sdk接口,这样我们就快速知道了使用非sdk接口的地方,接下来要做的就是确认这些非sdk接口的使用场景。确认完毕之后,就可以开始修改了。

3、详细步骤

3.1、使用sdk接口替换非sdk接口

在确认了每一个调用非sdk接口的地方之后,接下来需要确认是否有sdk的接口可以替换非sdk的接口。

import android.content.ServiceConnection;
import android.media.IMediaScannerListener;
import android.media.IMediaScannerService;

比如这几个非sdk接口,他们的作用就是绑定到MediaScannerService上,完成文件扫描的工作,这几个接口是否有sdk接口来完成相同工作呢?MediaScannerConnection就是这个类。MediaScannerConnection提供了onScanCompleted这个回调,可以用这个回调来通知界面刷新,完成的功能和bind到MediaScannerService完成的工作是一致的。接下来的工作就很简单了,即按照原有的流程,使用sdk接口来替代非sdk接口完成的工作。这部分代码修改起来比较零散且修改较大,这里就不贴代码了。

3.2、使用反射

并不是每一个非sdk接口都有这么好的运气,有对应的sdk接口完成相同的工作。这时候只能使用反射,代码运行的时候反射到framework中。下面给出APP中使用到的VolumeInfo反射调用,VolumeInfo的作用主要是封装了一些存储信息,在文件管理器这类APP中使用较多。

/**
 * This class is reflected to 'android.os.storage.VolumeInfo'.
 * We use some non-SDK interfaces.
 */
public class VolumeInfoReflection {
    public static final String ACTION_VOLUME_STATE_CHANGED =
            "android.os.storage.action.VOLUME_STATE_CHANGED";
    public static final String EXTRA_VOLUME_ID =
            "android.os.storage.extra.VOLUME_ID";
    public static final String EXTRA_VOLUME_STATE =
            "android.os.storage.extra.VOLUME_STATE";

    /** Stub volume representing internal private storage */
    public static final String ID_PRIVATE_INTERNAL = "private";
    /** Real volume representing internal emulated storage */
    public static final String ID_EMULATED_INTERNAL = "emulated";

    public static final int TYPE_PUBLIC = 0;
    public static final int TYPE_PRIVATE = 1;
    public static final int TYPE_EMULATED = 2;
    public static final int TYPE_ASEC = 3;
    public static final int TYPE_OBB = 4;

    public static final int STATE_UNMOUNTED = 0;
    public static final int STATE_CHECKING = 1;
    public static final int STATE_MOUNTED = 2;
    public static final int STATE_MOUNTED_READ_ONLY = 3;
    public static final int STATE_FORMATTING = 4;
    public static final int STATE_EJECTING = 5;
    public static final int STATE_UNMOUNTABLE = 6;
    public static final int STATE_REMOVED = 7;
    public static final int STATE_BAD_REMOVAL = 8;
	
	//反射一个方法
    public static List<?> getVolumeInfos(Context context) {
        StorageManager mStorageManager = context.getSystemService(StorageManager.class);
        List<?> volumeInfos = new ArrayList<>();

        try {
            Class storeManagerClazz = Class.forName("android.os.storage.StorageManager");
            Method getVolumesMethod = storeManagerClazz.getMethod("getVolumes");
            volumeInfos = (List<?>) getVolumesMethod.invoke(mStorageManager);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            return volumeInfos;
        }

    }

	//反射一个字段
    public static int getTypeField(Object volumeInfo) {
        int type = -1;

        try {
            Class volumeInfoClazz = Class.forName("android.os.storage.VolumeInfo");
            Field fsTypeField = volumeInfoClazz.getDeclaredField("type");
            type = (int)fsTypeField.get(volumeInfo);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            return type;
        }

    }

    public static File getPath(Object volumeInfo) {

        String path = getPathField(volumeInfo);

        return (path != null) ? new File(path) : null;
    }

	//反射一个字段
    public static String getPathField(Object volumeInfo) {
        String path = "";

        try {
            Class volumeInfoClazz = Class.forName("android.os.storage.VolumeInfo");
            Field pathField = volumeInfoClazz.getDeclaredField("path");
            path = (String) pathField.get(volumeInfo);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            return path;
        }
    }

	//反射一个方法
    public static Object getDiskMethod(Object volumeInfo) {
       Object diskInfo = null;

        try {
            Class volumeInfoClazz = Class.forName("android.os.storage.VolumeInfo");
            Method getDiskMethod = volumeInfoClazz.getMethod("getDisk");
            diskInfo = getDiskMethod.invoke(volumeInfo);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            return diskInfo;
        }
    }

    ……

4、收尾工作

将每一个使用到非sdk接口的地方修改完之后,需对APP进行全量遍历,防止由于修改导致功能回退及其他异常。

5、后记

Google对于非sdk接口的管控越来越严厉,据其官网上说,为什么不推荐使用这些非sdk接口,是因为这些非sdk接口可能会引发未知的异常。同时给出了非sdk接口的黑、白、灰名单。位于黑名单当中的非sdk接口即使使用反射也是没办法调用的,会抛出ClassNotFound Exception; 位于灰名单当中的非sdk接口至少在Android9.0还可以通过反射调用,但是不保证更高版本的Android还能调用;而位于白名单当中的非sdk接口可以一直调用。这个名单大家感兴趣可以自行上网找找。而我的这次修改,通过反射调用的非sdk接口都已经在名单上检查过了,大多位于灰名单当中,少数一两个位于白名单当中,没有位于黑名单当中的,这也从另一个方面保证了这次修改的可行性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值