Android侦听应用(Package)变化的方法(2)实现PackageMonitor

时间:2022-06-24 00:04:49

先看看PackageMonitor的基本定义:

package com.android.internal.content;


/**
 * Helper class for monitoring the state of packages: adding, removing,
 * updating, and disappearing and reappearing on the SD card.
 */
public abstract class PackageMonitor extends android.content.BroadcastReceiver

static {
    sPackageFilt.addAction(Intent.ACTION_PACKAGE_ADDED);
    sPackageFilt.addAction(Intent.ACTION_PACKAGE_REMOVED);
    sPackageFilt.addAction(Intent.ACTION_PACKAGE_CHANGED);
    sPackageFilt.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
    sPackageFilt.addAction(Intent.ACTION_PACKAGE_RESTARTED);
    sPackageFilt.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
    sPackageFilt.addDataScheme("package");
    sNonDataFilt.addAction(Intent.ACTION_UID_REMOVED);
    sNonDataFilt.addAction(Intent.ACTION_USER_STOPPED);
    sNonDataFilt.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
    sNonDataFilt.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
    sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
    sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
}

可以看到PackageMonitor是一个内部API,并且它实际上是一个BroadcastReceiver,在框架内部注册了接收上述Action的Intent广播。虽然目前没有对第三方App开放,但其设计思想可以借鉴,毕竟注册各种Package相关的Intent比较琐碎,在一个工程中封装一个类似的Monitor作为基本框架模块供复用很有必要。PackageMonitor的主要工作是将注册/解析Intent广播封装,对外暴露成需要实现的有语义的回调。看一下他的注册和有代表性的回调。

(1)注册
PackageMonitor有三个注册方法:

public void register(Context context, Looper thread, boolean externalStorage) 
public void register(Context context, Looper thread, UserHandle user, boolean externalStorage)
public void register(Context context, UserHandle user, boolean externalStorage, Handler handler)

可以看到提供了注册回调执行的线程、注册侦听的用户(支持安卓多用户体系)、选择是否侦听外置存储空间(譬如SD卡)中的package变化等配置项。

(2)有代表性的回调
/**
 * Called when a package is really added (and not replaced).
 */
public void onPackageAdded(String packageName, int uid) {
}

/**
 * Called when a package is really removed (and not replaced).
 */
public void onPackageRemoved(String packageName, int uid) {
}

/**
 * Direct reflection of {@link Intent#ACTION_PACKAGE_CHANGED
 * Intent.ACTION_PACKAGE_CHANGED} being received, informing you of
 * changes to the enabled/disabled state of components in a package
 * and/or of the overall package.
 *
 * @param packageName The name of the package that is changing.
 * @param uid The user ID the package runs under.
 * @param components Any components in the package that are changing.  If
 * the overall package is changing, this will contain an entry of the
 * package name itself.
 * @return Return true to indicate you care about this change, which will
 * result in {@link #onSomePackagesChanged()} being called later.  If you
 * return false, no further callbacks will happen about this change.  The
 * default implementation returns true if this is a change to the entire
 * package.
 */
public boolean onPackageChanged(String packageName, int uid, String[] components) {
    if (components != null) {
        for (String name : components) {
            if (packageName.equals(name)) {
                return true;
            }
        }
    }
    return false;
}

public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
    return false;
}

public void onHandleUserStop(Intent intent, int userHandle) {
}

public void onUidRemoved(int uid) {
}

public void onPackagesAvailable(String[] packages) {
}

public void onPackagesUnavailable(String[] packages) {
}

public void onPackagesSuspended(String[] packages) {
}

public void onPackagesUnsuspended(String[] packages) {
}
可以根据自己的需求进行重写方法。针对其中几个研究一下:
-onUidRemoved(int uid):
自从android引入对多用户的支持,uid和user就成了容易混淆的概念。参见我的另2篇:
Android下uid与多用户释疑(一) 
Android下uid与多用户释疑(二) 
这里的uid是指应用程序。uid removed即指应用程序被删除(卸载)。如果某些业务逻辑中对于应用程序是通过uid标识管理的,可以使用这个回调。 
对应的Intent Action是Intent.ACTION_UID_REMOVED。
-onPackagesSuspended()/onPackagesUnsuspended():
对应Intent.ACTION_PACKAGES_SUSPENDED/ACTION_PACKAGES_UNSUSPENDED。查看PMS、PackageManager的源代码看到是在调用这个api时触发的Intent广播:

/**
 * Puts the package in a suspended state, where attempts at starting activities are denied.
 *
 * <p>It doesn't remove the data or the actual package file. The application notifications
 * will be hidden, the application will not show up in recents, will not be able to show
 * toasts or dialogs or ring the device.
 *
 * <p>The package must already be installed. If the package is uninstalled while suspended
 * the package will no longer be suspended.
 *
 * @param packageNames The names of the packages to set the suspended status.
 * @param suspended If set to {@code true} than the packages will be suspended, if set to
 * {@code false} the packages will be unsuspended.
 * @param userId The user id.
 *
 * @return an array of package names for which the suspended status attemptis not set as requested in
 * this method.
 *
 * @hide
 */
public abstract String[] setPackagesSuspendedAsUser(
        String[] packageNames, boolean suspended, @UserIdInt int userId);

关于应用挂起,后续会有文章跟进。
以一个系统服务SearchManagerService为例看下对于PackageMonitor的使用:
/**
 * Refreshes the "searchables" list when packages are added/removed.
 */
class MyPackageMonitor extends PackageMonitor {

    @Override
    public void onSomePackagesChanged() {
        updateSearchables();
    }

    @Override
    public void onPackageModified(String pkg) {
        updateSearchables();
    }

    private void updateSearchables() {
        final int changingUserId = getChangingUserId();
        synchronized (mSearchables) {
            // Update list of searchable activities
            for (int i = 0; i < mSearchables.size(); i++) {
                if (changingUserId == mSearchables.keyAt(i)) {
                    mSearchables.valueAt(i).updateSearchableList();
                    break;
                }
            }
        }
        // Inform all listeners that the list of searchables has been updated.
        Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
                | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
        mContext.sendBroadcastAsUser(intent, new UserHandle(changingUserId));
    }
}

上述参看代码版本为android7.1。