Android PackageManager的妙用

android.content.pm.PackageManager 用于检索与设备上当前安装的应用程序包相关的各种信息的类。
要获取应用相关的信息,都用到它。

通过 Context#getPackageManager 实例化。

获取已安装应用列表

获取所有已安装的应用,

private void getAllInstallApp() {
    List<PackageInfo> list = getPackageManager().getInstalledPackages(0);
    if (null != list && list.size() > 0) {
        Log.d(TAG, " getAllInstallApp -- list.size() : " + list.size());
        for (PackageInfo p : list) {
            Log.d(TAG, "getAllInstallApp -- p.packageName : " + p.packageName);
        }
    }
}

这样写,AS 报黄

As of Android 11, this method no longer returns information about all apps; see https://g.co/dev/packagevisibility for details

Android R(11) 上获取到的应用数量也不全,adb shell pm list package 结果有 189 个,这样获取到的是 74 个(包括系统应用和自己,但不包括安装的第三方应用)。

在注册文件 AndroidManifest.xml 里添加 <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> 后,获取到 189 个了。涉及用户隐私,慎用!!
AS 也报黄

A <queries> declaration should generally be used instead of QUERY_ALL_PACKAGES; see https://g.co/dev/packagevisibility for details

最终写法

  • 删除 <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
  • 在注册文件 AndroidManifest.xml 里添加 queries 标签,填入需要查询的包名。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.test.luodemo">

    <queries>
        <package android:name="com.test.verticalgridviewdemo"/>
        <package android:name="com.test.androidopencvdemo"/>
        <package android:name="com.example.navigationdemo"/>
    </queries>

    <uses-permission android:name="..." />

    <application
       ...
    </application>

</manifest>

这样就获取到 77 个了(原来的74个 加上 queries里的 3 个)。

判断应用是否已安装

可以通过 获取已安装应用列表,然后过滤包名判断,

也就可以直接判断,

private boolean isPkgInstall(String pkgName){
    if (null == pkgName || TextUtils.isEmpty(pkgName)) {
        return false;
    }

    PackageInfo packageInfo = null;
    try {
        packageInfo = getPackageManager().getPackageInfo(pkgName, 0);
        if (null != packageInfo) {
            return true;
        }
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
    }

    return false;
}

判断是否是系统应用

	private boolean isSystemApp(String pkgName) {
        if (null == pkgName || TextUtils.isEmpty(pkgName)) {
            return false;
        }

        try {
            PackageInfo info = getPackageManager().getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);
            if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
                return true;
            }

        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        return false;
    }

判断是否是persist应用

	private boolean isPersistApp(String pkgName) {
        if (null == pkgName || TextUtils.isEmpty(pkgName)) {
            return false;
        }

        try {
            PackageInfo info = getPackageManager().getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);

            if ((info.applicationInfo.flags &
                    (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_PERSISTENT)) ==
                    (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_PERSISTENT)) {
                return true;
            }

        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        return false;
    }

获取应用信息

通过 PackageInfo 拿到,

代码 说明
packageInfo.applicationInfo.loadLabel(PackageManager pm) 应用名称 ,对应 AndroidManifest.xml 里的 android:label
packageInfo.applicationInfo.loadIcon(PackageManager pm) Drawable 类型的应用图标
packageInfo.packageName 包名,如 com.android.settings
packageInfo.versionName 版本,如 1.0
packageInfo.sharedUserId 对应 AndroidManifest.xml 里的 android:sharedUserId ,
Setings 的是 android.uid.system ,
Bluetooth 的是 com.android.bluetooth ,
自己写的 Demo 未添加 android:sharedUserId 标签获取到的是 null
packageInfo.sharedUserLabel 获取到的是 0
packageInfo.lastUpdateTime 上次更新时间,是 System.currentTimeMillis() 形式的

代码示例,

private void getPkgInfo(String pkgName){
    if (null == pkgName || TextUtils.isEmpty(pkgName)) {
        return;
    }

    PackageInfo packageInfo = null;
    PackageManager packageManager = getPackageManager();
    try {
        packageInfo = packageManager.getPackageInfo(pkgName, 0);
        if (null != packageInfo) {
            String label = (String) packageInfo.applicationInfo.loadLabel(packageManager);
            Drawable icon = (Drawable) packageInfo.applicationInfo.loadIcon(packageManager);
            String strPName =  packageInfo.packageName;
            String strVersionName =  packageInfo.versionName;
            String sharedUserId =  packageInfo.sharedUserId;
            int sharedUserLabel =  packageInfo.sharedUserLabel;
            long uTime =  packageInfo.lastUpdateTime;
        }
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
    }
}

获取应用申请的权限

通过 PackageInfo 获取。

注意,实例化 PackageInfo 时传入的参数是 PackageManager.GET_PERMISSIONS

private void getPkgPermissionInfo(String pkgName){
    if (null == pkgName || TextUtils.isEmpty(pkgName)) {
        return;
    }

    PackageInfo pInfo = null;
    PackageManager packageManager = getPackageManager();
    try {
        pInfo = packageManager.getPackageInfo(pkgName, PackageManager.GET_PERMISSIONS);
        if (null != pInfo) {
            String[] permissions = pInfo.requestedPermissions;
        }
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
    }
}

打印出的结果就是注册文件 AndroidManifest.xml 里声明的,如
android.permission.WAKE_LOCK 、android.permission.READ_EXTERNAL_STORAGE 、android.permission.WRITE_EXTERNAL_STORAGE 等,

模拟器测试,这三个权限我没申请也有 ,
android.permission.ACCESS_NETWORK_STATE
android.permission.RECEIVE_BOOT_COMPLETED
android.permission.FOREGROUND_SERVICE

获取签名信息

通过 PackageInfo 获取。

注意,实例化 PackageInfo 时传入的参数是 PackageManager.GET_SIGNATURES

	try {
            PackageInfo info = getPackageManager().getPackageInfo("com.android.settings", PackageManager.GET_SIGNATURES);
            Signature[] signatures = info.signatures;
            String str = signatures[0].toString();
        } catch (Exception e){
            e.printStackTrace();
        }

str 输出为 :android.content.pm.Signature@b2d95fc0

获取 MD5、 SHA1、 SHA256

通过 PackageInfo 获取。

注意,实例化 PackageInfo 时传入的参数是 PackageManager.GET_SIGNATURES

获取方法是一样的,只是初始化 MessageDigest 时传的值不同,

	/**
     * 获取 MD5 、SHA1 、SHA256
     * @param algorithm : 只能是 MD5 、SHA1 、SHA256
     * 返回值是这种形式:22:13:F1:91:ED:BC:92:E7:44:1B:34:9E:45:90:AB:76
     * */
    private String getAppSignatureAlgorithm(String pkgName, String algorithm){
        if (null == pkgName || TextUtils.isEmpty(pkgName)) {
            return null;
        }
        try {
            PackageInfo info = getPackageManager().getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);

            Signature[] signatures = info.signatures;
            byte[] signBytes = signatures[0].toByteArray();

            MessageDigest mDigest = MessageDigest.getInstance(algorithm);
            byte[] bytesMd5 = mDigest.digest(signBytes);
            StringBuilder builder = new StringBuilder();
            for (byte b : bytesMd5) {
                builder.append(String.format("%02X", b)).append(":");// byte 转 16 进制
            }

            builder.replace(builder.length()-1, builder.length(),"");// 删除最后的 :
            return builder.toString();

        }catch (Exception e){
            e.printStackTrace();
        }

        return null;
    }

使用示例,

String PKG_SETTINGS = "com.android.settings";
String sMd5 = getAppSignatureAlgorithm(PKG_SETTINGS, "MD5");
String sSha1 = getAppSignatureAlgorithm(PKG_SETTINGS, "SHA1");
String sSha256 = getAppSignatureAlgorithm(PKG_SETTINGS, "SHA256");

结果可以参考 AndroidStudio查看apk签名信息_android studio查看apk签名版本-CSDN博客 验证

判断两个应用签名是否一致

使用 checkSignatures 方法,源码注释,

/**
* Compare the signatures of two packages to determine if the same
* signature appears in both of them. If they do contain the same
* signature, then they are allowed special privileges when working
* with each other: they can share the same user-id, run instrumentation
* against each other, etc.
*
* @param packageName1 First package name whose signature will be compared.
* @param packageName2 Second package name whose signature will be compared.
*
* @return Returns an integer indicating whether all signatures on the
* two packages match. The value is >= 0 ({@link #SIGNATURE_MATCH}) if
* all signatures match or < 0 if there is not a match ({@link
* #SIGNATURE_NO_MATCH} or {@link #SIGNATURE_UNKNOWN_PACKAGE}).
*
* @see #checkSignatures(int, int)
*/
@CheckResult
@SignatureResult
public abstract int checkSignatures(@NonNull String packageName1,
@NonNull String packageName2);

示例,

int ret = getPackageManager().checkSignatures("com.android.settings","com.test.luodemo");// -3

int ret1 = getPackageManager().checkSignatures("com.android.settings", "com.android.bluetooth");//  0

ret = -3 ,对应 PackageManager.SIGNATURE_NO_MATCH 。

ret1 = 0 ,对应 PackageManager.SIGNATURE_MATCH 。

获取四大组件信息

PackageInfo 源码,获取四大组件需要传对应的 flag ,

    /**
     * Array of all {@link android.R.styleable#AndroidManifestActivity
     * &lt;activity&gt;} tags included under &lt;application&gt;,
     * or null if there were none.  This is only filled in if the flag
     * {@link PackageManager#GET_ACTIVITIES} was set.
     */
    public ActivityInfo[] activities;

    /**
     * Array of all {@link android.R.styleable#AndroidManifestReceiver
     * &lt;receiver&gt;} tags included under &lt;application&gt;,
     * or null if there were none.  This is only filled in if the flag
     * {@link PackageManager#GET_RECEIVERS} was set.
     */
    public ActivityInfo[] receivers;

    /**
     * Array of all {@link android.R.styleable#AndroidManifestService
     * &lt;service&gt;} tags included under &lt;application&gt;,
     * or null if there were none.  This is only filled in if the flag
     * {@link PackageManager#GET_SERVICES} was set.
     */
    public ServiceInfo[] services;

    /**
     * Array of all {@link android.R.styleable#AndroidManifestProvider
     * &lt;provider&gt;} tags included under &lt;application&gt;,
     * or null if there were none.  This is only filled in if the flag
     * {@link PackageManager#GET_PROVIDERS} was set.
     */
    public ProviderInfo[] providers;

示例代码,
本例只是拿到 name ,所以用 ComponentInfo
要拿到启动模式(launchMode)等信息,还是用 ActivityInfoServiceInfoProviderInfo

	/**
     * @param type : 0 activity , 1 service , 2 provider , 3 receiver
     * */
    private void getPkgComponent(String pkgName, int type){
        if (null == pkgName || TextUtils.isEmpty(pkgName)) {
            return;
        }

        PackageInfo pInfo = null;
        PackageManager packageManager = getPackageManager();
        try {
            switch (type){
                case 1:
                    pInfo = packageManager.getPackageInfo(pkgName, PackageManager.GET_SERVICES);
                    break;
                case 2:
                    pInfo = packageManager.getPackageInfo(pkgName, PackageManager.GET_PROVIDERS);
                    break;
                case 3:
                    pInfo = packageManager.getPackageInfo(pkgName, PackageManager.GET_RECEIVERS);
                    break;
                case 0:
                default:
                    pInfo = packageManager.getPackageInfo(pkgName, PackageManager.GET_ACTIVITIES);break;
            }

            if (null != pInfo) {
                ComponentInfo[] componentInfos = null;
                switch (type){
                    case 1:
                        componentInfos = pInfo.services;
                        break;
                    case 2:
                        componentInfos = pInfo.providers;
                        break;
                    case 3:
                        componentInfos = pInfo.receivers;
                        break;
                    case 0:
                    default:
                        componentInfos = pInfo.activities;
                        break;
                }
                if (null != componentInfos && componentInfos.length > 0) {
                    Log.d(TAG, "getPkgComponent -- componentInfos.length : " + componentInfos.length);
                    for (ComponentInfo info : componentInfos) {
                        Log.d(TAG , "getPkgComponent -- info : " + info.name);
                    }
                }
            }
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }

输出结果是这样的 :

com.test.luodemo.packagemanager.PackageManagerTestActivity
com.test.luodemo.alarmtest.MyIntentService
com.test.luodemo.alarmtest.MyWakefulReceiver
com.test.luodemo.provider.LContentProvider

清除应用缓存

PackageManager.deleteApplicationCacheFiles(String packageName, IPackageDataObserver observer) 源码说明,

    /**
     * Attempts to delete the cache files associated with an application.
     * Since this may take a little while, the result will
     * be posted back to the given observer.  A deletion will fail if the calling context
     * lacks the {@link android.Manifest.permission#DELETE_CACHE_FILES} permission, if the
     * named package cannot be found, or if the named package is a "system package".
     *
     * @param packageName The name of the package to delete
     * @param observer An observer callback to get notified when the cache file deletion
     * is complete.
     * {@link android.content.pm.IPackageDataObserver#onRemoveCompleted(String, boolean)}
     * will be called when that happens.  observer may be null to indicate that
     * no callback is desired.
     *
     * @hide
     */
    @SuppressWarnings("HiddenAbstractMethod")
    @UnsupportedAppUsage
    public abstract void deleteApplicationCacheFiles(@NonNull String packageName,
            @Nullable IPackageDataObserver observer);

这个方法使用由限制:

  • 需要 android.permission.DELETE_CACHE_FILES 权限;
  • 只有系统应用才能申请 android.permission.DELETE_CACHE_FILES 权限,需要是系统应用;
  • 标注为 @hide ,无法直接调用。

使用方法:

  • 1.系统应用直接调用。
    参考安卓原生 Settings 应用的代码,
    public void clearCache() {
        mMetricsFeatureProvider.action(getContext(), MetricsEvent.ACTION_SETTINGS_CLEAR_APP_CACHE);
        mClearCachePreference.setClearingCache(true);
        mPackageManager.deleteApplicationCacheFiles(mEntry.info.packageName,
                new IPackageDataObserver.Stub() {
                    public void onRemoveCompleted(final String packageName,
                            final boolean succeeded) {
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                mClearCachePreference.setClearingCache(false);
                                cacheCleared(succeeded);
                            }
                        });
                    }
                });
        mClearCachePreference.refresh();
    }
  • 2.反射调用。
    AS 编译,导入 framework.jar ,添加依赖,
compileOnly files('libs/framework.jar')

反射调用,mark 下反射的写法

private final IPackageDataObserver iPackageDataObserver = new IPackageDataObserver.Stub() {
    @Override
    public void onRemoveCompleted(String packageName, boolean isSucceed) throws RemoteException {
	
    }
};
private void clearPkgCacheData(String pkgName){
    if (null == pkgName || TextUtils.isEmpty(pkgName)) {
        return;
    }

    PackageManager packageManager = getPackageManager();

    Class<?> classPm = null;
    try {
        classPm = Class.forName("android.content.pm.PackageManager");
        Method method = classPm.getDeclaredMethod("deleteApplicationCacheFiles", String.class, IPackageDataObserver.class);
        method.setAccessible(true);
        method.invoke(packageManager, pkgName, iPackageDataObserver);
    } catch (Exception e) {
        e.printStackTrace();
    }

}

不是系统应用,反射可以调用到,但是会报错,

Caused by: java.lang.SecurityException: Neither user 10156 nor current process has
	 android.permission.INTERNAL_DELETE_CACHE_FILES.

设置为系统应用就可以了。

猜你喜欢

转载自blog.csdn.net/weixin_44021334/article/details/133710929