PackageInstaller 5.0原始碼分析靜默安裝與靜默解除安裝

zhou_lee發表於2016-08-26

現在市面上各大手機的應用市場,都有靜默安裝和解除安裝的功能。當然,個人認為,這是一個流氓行為,可能你會莫名其妙的就安裝了一堆應用。像360手機助手、豌豆莢之類的,雖然很像靜默安裝,其實不是。他們一是通過獲取root許可權,用pm install命令來靜默安裝apk的;二是通過智慧安裝服務,也就是Android中的無障礙服務,模擬點選事件來安裝應用。

現在我們只分析Android中的靜默安裝和解除安裝,也就是PackageInstaller原始碼,原始碼版本5.0.2

PackageInstaller 原始碼目錄/packages/apps/PackageInstaller。

分析AndroidManifest檔案

首先我們看下AndroidManifest檔案:
apps/PackageInstaller/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.android.packageinstaller">

    <original-package android:name="com.android.packageinstaller" />

    <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
    <uses-permission android:name="android.permission.DELETE_PACKAGES" />
    <uses-permission android:name="android.permission.CLEAR_APP_CACHE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MANAGE_USERS" />
    <uses-permission android:name="android.permission.GRANT_REVOKE_PERMISSIONS" />
    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
    <uses-permission android:name="android.permission.READ_INSTALL_SESSIONS" />

    <application android:label="@string/app_name"
            android:allowBackup="false"
            android:theme="@style/Theme.DialogWhenLarge"
            android:supportsRtl="true">

        <activity android:name=".PackageInstallerActivity"
                android:configChanges="orientation|keyboardHidden|screenSize"
                android:excludeFromRecents="true">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <action android:name="android.intent.action.INSTALL_PACKAGE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="file" />
                <data android:mimeType="application/vnd.android.package-archive" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.INSTALL_PACKAGE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="file" />
                <data android:scheme="package" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.content.pm.action.CONFIRM_PERMISSIONS" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

        <activity android:name=".InstallAppProgress"
                android:configChanges="orientation|keyboardHidden|screenSize"
                android:exported="false" />

        <activity android:name=".UninstallerActivity"
                android:configChanges="orientation|keyboardHidden|screenSize"
                android:excludeFromRecents="true"
                android:theme="@style/Theme.AlertDialogActivity">
            <intent-filter>
                <action android:name="android.intent.action.DELETE" />
                <action android:name="android.intent.action.UNINSTALL_PACKAGE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="package" />
            </intent-filter>
        </activity>

        <activity android:name=".UninstallAppProgress"
                android:configChanges="orientation|keyboardHidden|screenSize"
                android:exported="false" />

        <activity android:name=".GrantActivity"
                android:configChanges="orientation|keyboardHidden|screenSize"
                android:excludeFromRecents="true"
                android:theme="@android:style/Theme.DeviceDefault.Dialog.NoActionBar">
            <intent-filter>
                <action android:name="android.content.pm.action.REQUEST_PERMISSION" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

    </application>
</manifest> 

請注意:android.permission.INSTALL_PACKAGES和android.permission.DELETE_PACKAGES許可權,一個是安裝許可權,一個是解除安裝許可權。

有五個Activity,PackageInstallerActivity(接受安裝)、InstallAppProgress(安裝進度條)、UninstallerActivity(接受解除安裝)、UninstallAppProgress(解除安裝進度條)、GrantActivity(詢問使用者許可權)。

由於所有的Activity都沒有指定android.intent.action.MAIN和android.intent.category.LAUNCHER標籤,所有我們在桌面上看不到任何應用程式的圖示。

PackageInstallerActivity指定了三個intent-filter,android.intent.action.INSTALL_PACKAGE是我們呼叫的Action,不過我們需要指定data,一種是scheme為file,必須要指定mimeType為application/vnd.android.package-archive;一種是scheme為package就ok了。

// 下面是安裝的兩種呼叫方式
// 第一種
Intent intent = new Intent("android.intent.action.INSTALL_PACKAGE");
intent.setDataAndType(Uri.fromFile(new File("/weixin.apk")), "application/vnd.android.package-archive");
startActivity(intent);

// 第二種
Intent intent = new Intent("android.intent.action.INSTALL_PACKAGE");
intent.setData("package:com.tencent.weixin");
startActivity(intent);

至於UninstallerActivity,只指定一個intent-filter,scheme也是為file,不過卻有兩個Action,android.intent.action.DELETE和android.intent.action.UNINSTALL_PACKAGE。所以,我們也有兩種方法可以調起來它。

// 下面是解除安裝的兩種呼叫方式
// 第一種
Intent intent = new Intent("android.intent.action.DELETE");
intent.setData("package:com.tencent.weixin");
startActivity(intent);

// 第二種
Intent intent = new Intent("android.intent.action.UNINSTALL_PACKAGE");
intent.setData("package:com.tencent.weixin");
startActivity(intent);

分析靜默安裝

PackageInstallerActivity類

我們來看下PackageInstallerActivity類的程式碼,程式碼也不是特別多,700多行,從onCreate()方法開始,onCreate()的程式碼是比較多的,我們就挑些重點的來看。

    ....

    final PackageUtil.AppSnippet as;
        if ("package".equals(mPackageURI.getScheme())) {
            mInstallFlowAnalytics.setFileUri(false);
            try {
                mPkgInfo = mPm.getPackageInfo(mPackageURI.getSchemeSpecificPart(),
                        PackageManager.GET_PERMISSIONS | PackageManager.GET_UNINSTALLED_PACKAGES);
            } catch (NameNotFoundException e) {
            }
            if (mPkgInfo == null) {
                Log.w(TAG, "Requested package " + mPackageURI.getScheme()
                        + " not available. Discontinuing installation");
                showDialogInner(DLG_PACKAGE_ERROR);
                setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
                mInstallFlowAnalytics.setPackageInfoObtained();
                mInstallFlowAnalytics.setFlowFinished(
                        InstallFlowAnalytics.RESULT_FAILED_PACKAGE_MISSING);
                return;
            }
            as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
                    mPm.getApplicationIcon(mPkgInfo.applicationInfo));
        } else {
            mInstallFlowAnalytics.setFileUri(true);
            final File sourceFile = new File(mPackageURI.getPath());
            PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);

            // Check for parse errors
            if (parsed == null) {
                Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
                showDialogInner(DLG_PACKAGE_ERROR);
                setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
                mInstallFlowAnalytics.setPackageInfoObtained();
                mInstallFlowAnalytics.setFlowFinished(
                        InstallFlowAnalytics.RESULT_FAILED_TO_GET_PACKAGE_INFO);
                return;
            }
            mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
                    PackageManager.GET_PERMISSIONS, 0, 0, null,
                    new PackageUserState());
            mPkgDigest = parsed.manifestDigest;
            as = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
        }

    ...

mPackageURI = intent.getData();獲取待安裝Android應用的路徑或package。接下來需要驗證scheme是否合法,也就是是否為file或者package。主要要獲取PackageInfo物件的例項,因為這個物件可以獲取到待安裝應用程式的包名、應用名稱、圖示…一系列的相關資訊。

PackageUtil.AppSnippet是一個靜態的內部類,裡面只有兩個欄位,存放的是label和icon,也就是我們安裝的時候看見的應用名稱和圖示。

接下來有一步很重要,就是檢查”未知來源”是否開啟了,如果開啟,就進入initiateInstall()方法,否則,就彈窗提示。

       if ((requestFromUnknownSource) && (!isInstallingUnknownAppsAllowed())) {
           showDialogInner(DLG_UNKNOWN_APPS);
           mInstallFlowAnalytics.setFlowFinished(
                   InstallFlowAnalytics.RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING);
           return;
       }
    // 判斷未知來源是否被允許
    private boolean isInstallingUnknownAppsAllowed() {
            UserManager um = (UserManager) getSystemService(USER_SERVICE);

            boolean disallowedByUserManager = um.getUserRestrictions()
                    .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, false);
            boolean allowedBySecureSettings = Settings.Secure.getInt(getContentResolver(),
                    Settings.Secure.INSTALL_NON_MARKET_APPS, 0) > 0;

            return (allowedBySecureSettings && (!disallowedByUserManager));
        }

這裡面是有兩個變數來控制的,一個requestFromUnknownSource和isInstallingUnknownAppsAllowed(),在5.0以前,是隻有isInstallingUnknownAppsAllowed()來控制的。它是通過讀取Content Provider,來獲取設定中的”未知來源”是否被選中來判斷的。

小技巧:我們有時需要往系統裡面設定一些標誌位,清除應用快取不希望被清除,可以嘗試用下Settings.Secure這個類,它提供了一套讀寫方法,是相當於鍵值對形式的。只有重置機器它才會被清除的,不過需要響應的許可權。

接下來我們要分析initiateInstall()方法,你會發現,除了獲取ApplicationInfo物件例項,然後就呼叫了startInstallConfirm()方法。這個方法顯示了安裝視窗的應用資訊、許可權列表、安裝、取消按鈕等,我們安裝時看見的許可權資訊,就在AppSecurityPermissions類中,AppSecurityPermissions是一個元件,封裝了一些處理許可權的功能,AppSecurityPermissions.getPermissionsView()方法獲取顯示不同的許可權View物件,ScrollView通過addView()直接把它新增到其中,然後顯示出來。

startInstallConfirm()方法獲取了三種許可權:

// 獲取與隱私相關許可權的數量
final int NP = perms.getPermissionCount(AppSecurityPermissions.WHICH_PERSONAL);
// 獲取與裝置相關許可權的數量
final int ND = perms.getPermissionCount(AppSecurityPermissions.WHICH_DEVICE);
// 新加入的許可權
final int new = perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW);

點選安裝,其實是開啟了InstallAppProgress Activity,由它來執行安裝操作,當然會傳遞一些資訊過去,由Intent攜帶過去。

InstallAppProgress類

InstallAppProgress類程式碼很少,就是安裝和安裝過後的反饋資訊。其實,靜默安裝也就是在這其中實現,前面的都是一些攔截、校驗的操作,要實現自己的靜默安裝,只要呼叫跟InstallAppProgress類中一樣的API即可。

public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        Intent intent = getIntent();
        // 獲取ApplicationInfo物件
        mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
        mInstallFlowAnalytics = intent.getParcelableExtra(EXTRA_INSTALL_FLOW_ANALYTICS);
        mInstallFlowAnalytics.setContext(this);
        // 獲取要安裝的Uri
        mPackageURI = intent.getData();

        final String scheme = mPackageURI.getScheme();
        if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) {
            mInstallFlowAnalytics.setFlowFinished(
                    InstallFlowAnalytics.RESULT_FAILED_UNSUPPORTED_SCHEME);
            throw new IllegalArgumentException("unexpected scheme " + scheme);
        }

        // 進行一些初始化工作,以及安裝Andrroid應用
        initView();
    }

onCreate()方法主要獲取上個介面傳遞過來的一些資訊,獲取mAppInfo物件和mPackageURI安裝的uri,驗證下scheme,呼叫initView()方法開始安裝。

initView()方法的程式碼就很多了,提取精煉的來分析。

try {
            // 如果待安裝程式已經安裝,則返回PackageInfo物件,否則返回null
            PackageInfo pi = pm.getPackageInfo(mAppInfo.packageName,
                    PackageManager.GET_UNINSTALLED_PACKAGES);
            if (pi != null) {
                // 如果Android程式已經安裝,設定安裝模式為更新
                installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
            }
        } catch (NameNotFoundException e) {
        }

首先,我們需要獲取PackageInfo物件,判斷程式是安裝還是為更新。應用程式是安裝還是更新主要是通過installFlags變數來控制的(很重要)。

VerificationParams verificationParams = new VerificationParams(null, originatingURI,
                referrer, originatingUid, manifestDigest);
        PackageInstallObserver observer = new PackageInstallObserver();
        if ("package".equals(mPackageURI.getScheme())) {
            try {
                // 更新Android應用
                pm.installExistingPackage(mAppInfo.packageName);
                observer.packageInstalled(mAppInfo.packageName,
                        PackageManager.INSTALL_SUCCEEDED);
            } catch (PackageManager.NameNotFoundException e) {
                observer.packageInstalled(mAppInfo.packageName,
                        PackageManager.INSTALL_FAILED_INVALID_APK);
            }
        } else {
            // 安裝或更新應用
            pm.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags,
                    installerPackageName, verificationParams, null);
        }

這段程式碼就是靜默安裝了。如該scheme為package的話,就是更新Android應用, pm.installExistingPackage(mAppInfo.packageName);否則就是安裝或更新應用,pm.installPackageWithVerificationAndEncryption(mPackageURI,observer, installFlags, installerPackageName, verificationParams, null)。

其中installExistingPackage()和installPackageWithVerificationAndEncryption()都是PackageManager類的方法,都可以實現靜默安裝,也就是在安裝過程中不會出現任何提示。不過這兩個都是為hide的,普通應用不能呼叫,只有系統應用才能呼叫。

關於安裝資訊的反饋,是通過PackageInstallObserver類,就是一個監聽器,回撥一個Message,通過Handler發出來。

class PackageInstallObserver extends IPackageInstallObserver.Stub {
        public void packageInstalled(String packageName, int returnCode) {
            Message msg = mHandler.obtainMessage(INSTALL_COMPLETE);
            msg.arg1 = returnCode;
            mHandler.sendMessage(msg);
        }
    }
private Handler mHandler = new Handler() {

        public void handleMessage(Message msg) {

            switch (msg.what) {

            case INSTALL_COMPLETE:

                if (msg.arg1 == PackageManager.INSTALL_SUCCEEDED) {
                    // 成功安裝應用程式
                    Toast.makeText(getApplicationContext(), "成功安裝應用程式", Toast.LENGTH_SHORT).show();

                } else if (msg.arg1 == PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE) {
                    // 由於空間不足而導致安裝失敗
                    Toast.makeText(getApplicationContext(), "由於空間不足而導致安裝失敗", Toast.LENGTH_SHORT).show();

                } else {
                    // 由於其他原因導致安裝失敗
                    Toast.makeText(getApplicationContext(), "由於其他原因導致安裝失敗", Toast.LENGTH_SHORT).show();
                    getExplanationFromErrorCode(msg.arg1);
                }
                break;

            default:
                break;
            }

        }

    };

 private int getExplanationFromErrorCode(int errCode) {
        Log.d(TAG, "Installation error code: " + errCode);
        switch (errCode) {
            case PackageManager.INSTALL_FAILED_INVALID_APK:
                return R.string.install_failed_invalid_apk;
            case PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES:
                return R.string.install_failed_inconsistent_certificates;
            case PackageManager.INSTALL_FAILED_OLDER_SDK:
                return R.string.install_failed_older_sdk;
            case PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE:
                return R.string.install_failed_cpu_abi_incompatible;
            default:
                return -1;
        }
    }

反饋資訊分為三種,成功安裝應用程式、由於空間不足而導致安裝失敗、由於其他原因導致安裝失敗,其中由於其他原因導致安裝失敗,可以根據getExplanationFromErrorCode()方法,得到具體的失敗原因,進行相對應的處理。

現在,我們已經理解啦靜默安裝的原理了,在安裝程式之前的彈窗,是PackageInstaller故意搞出來了,我們只要去掉它,呼叫PackageManager的API就能實現自己的靜默安裝。

分析靜默解除安裝

UninstallerActivity類

靜默安裝分析完了,現在就來分析下靜默解除安裝吧。從UninstallerActivity類看起,相對於安裝,解除安裝的這個類只有200多行程式碼,少很多。

 @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        ...

        final Intent intent = getIntent();
        final Uri packageUri = intent.getData();

        // 獲取要解除安裝程式的包名
        final String packageName = packageUri.getEncodedSchemeSpecificPart();

        final IPackageManager pm = IPackageManager.Stub.asInterface(
                ServiceManager.getService("package"));

        mDialogInfo = new DialogInfo();

        mDialogInfo.user = intent.getParcelableExtra(Intent.EXTRA_USER);
        if (mDialogInfo.user == null) {
            mDialogInfo.user = android.os.Process.myUserHandle();
        }

        mDialogInfo.allUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false);
        mDialogInfo.callback = intent.getIBinderExtra(PackageInstaller.EXTRA_CALLBACK);

        // 獲取ApplicationInfo物件
        try {
            mDialogInfo.appInfo = pm.getApplicationInfo(packageName,
                    PackageManager.GET_UNINSTALLED_PACKAGES, mDialogInfo.user.getIdentifier());
        } catch (RemoteException e) {
        }
    ...
        showConfirmationDialog();
    }

onCreate()方法,抽取啦一些主要程式碼出來,主要獲取傳遞過來的包名,初始化Dialog,然後通過showConfirmationDialog()彈出Dialog。

UninstallAlertDialogFragment是一個DialogFragment,為UninstallerActivity的內部類,也就是顯示的解除安裝對話方塊。點選”確定”,開始解除安裝,呼叫startUninstallProgress()方法。

void startUninstallProgress() {
        Intent newIntent = new Intent(Intent.ACTION_VIEW);
        newIntent.putExtra(Intent.EXTRA_USER, mDialogInfo.user);
        // 要求解除安裝該Android應用對於所有使用者的程式和資料(Android 4.2開始支援多使用者)
        newIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, mDialogInfo.allUsers);
        newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback);
        // appInfo 封裝了要解除安裝程式的資訊
        newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mDialogInfo.appInfo);
        // 允許解除安裝視窗返回是否解除安裝成功的標誌
        if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
            newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
            newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
        }
        // 指定負責解除安裝Android應用的視窗類
        newIntent.setClass(this, UninstallAppProgress.class);
        startActivity(newIntent);
    }

主要的解除安裝操作在UninstallAppProgress類中,而UninstallerActivity只是做解除安裝前的攔截作用,現在解除安裝視窗而已。

UninstallAppProgress類

這個類主要作用就是解除安裝APK,在onCreate()方法中有個東西很重要。

// 獲取是否刪除所有使用者資料的標誌位
mAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false);

就是在解除安裝的時候,是否刪除使用者資料。主要的解除安裝工作有initView()方法去完成,提煉主要程式碼分析。

        IPackageManager packageManager = IPackageManager.Stub
                .asInterface(ServiceManager.getService("package"));
        // 建立解除安裝監聽器
        PackageDeleteObserver observer = new PackageDeleteObserver();
        // 靜默解除安裝Android應用
        try {
            packageManager.deletePackageAsUser(mAppInfo.packageName, observer,
                    mUser.getIdentifier(),
                    mAllUsers ? PackageManager.DELETE_ALL_USERS : 0);
        } catch (RemoteException e) {
            // Shouldn't happen.
            Log.e(TAG, "Failed to talk to package manager", e);
        }

解除安裝應用程式主要通過deletePackageAsUser()方法,第一個引數是包名,第二個是解除安裝反饋的監聽器,第三個是使用者id,第四個是是否刪除使用者資料。PackageDeleteObserver監聽器是用來接收解除安裝的反饋資訊。

class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
        public void packageDeleted(String packageName, int returnCode) {
            Message msg = mHandler.obtainMessage(UNINSTALL_COMPLETE);
            msg.arg1 = returnCode;
            msg.obj = packageName;
            mHandler.sendMessage(msg);
        }
    }
    private Handler mHandler = new Handler() {

        public void handleMessage(Message msg) {

            switch (msg.what) {

            case UNINSTALL_COMPLETE:
                ...

                // 解除安裝完成
                int resultCode = msg.arg1;
                final String packageName = (String) msg.obj;
                // 處理不需要返回解除安裝狀態的情況
                switch (msg.arg1) {
                case PackageManager.DELETE_SUCCEEDED:
                    // 成功解除安裝Android應用
                    return;
                case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER:
                    // 由於某些策略的原因導致了解除安裝失敗
                    break;
                case PackageManager.DELETE_FAILED_OWNER_BLOCKED:
                    break;
                default:
                    // 由於其他原因導致了解除安裝失敗
                    break;
                }
                break;
                ...

            default:
                break;
            }

        }

    };

這是解除安裝結果的一些處理,我只列出了解除安裝的結果型別,其中涉及到一些UI操作,並沒有展開。主要分為需要處理返回狀態和不需要處理返回狀態這兩種情況,它們都會在處理完畢過後關閉這個視窗。

因為靜默解除安裝和安裝都是非同步處理的,所有使用者感覺不到,仍然可以做自己的事情,但是其實這個時候,系統是在高速運作,程式的安裝和解除安裝是需要耗費cpu資源的,所以如果手機效能差的話,可能會感覺到卡噸…唉,流氓軟體就是這樣來的。

實現自己的靜默安裝與解除安裝

分析了這麼多,我們應該來實現自己的靜默安裝和解除安裝了。由於靜默安裝和解除安裝的API是系統級別的,所以我們必須要用Android原始碼來編譯的。在Eclipse下建Project,寫個.mk檔案,可以直接把PackageInstaller的copy過來,修改一下就好。
Android.mk檔案

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := optional

LOCAL_SRC_FILES :=$(call all-java-files-under, src)

LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4

LOCAL_PACKAGE_NAME := ZhouSilenceInstaller
LOCAL_CERTIFICATE := platform

include $(BUILD_PACKAGE)

AndroidManifest.xml檔案,也copy一下PackageInstaller的,修改一些就好。主要,由於我們不需要安裝前的提示框,所有我們只需要安裝的就可以了,所以Activity的主題可以設定為

android:theme="@android:style/Theme.NoDisplay"

這個主題是不顯示的,比透明主題更好。

反過來想一下,是不是可以用Service。這樣會不會更好,實驗證明,我感覺IntentService實現靜默安裝和解除安裝會更好。程式碼在後面會貼上的。

許可權問題一定要注意

<!-- 安裝許可權 -->
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
<!-- 解除安裝許可權 -->
<uses-permission android:name="android.permission.DELETE_PACKAGES" />

這兩個是最重要的,當然,其中還有一些讀取sdcard的,關於獲取許可權列表的、使用者資訊的…

先看下清單檔案吧~區域性,具體的請下載檢視吧。

        <!-- 靜默安裝服務 -->
        <service
            android:name="com.zhou.silence.installer.InstallService"
            android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.VIEW.HIDE" />
                <action android:name="android.intent.action.INSTALL_PACKAGE_HIDE" />

                <category android:name="android.intent.category.DEFAULT" />

                <data android:scheme="file" />
                <data android:mimeType="application/vnd.android.package-archive" />
            </intent-filter>
        </service>

        <!-- 靜默解除安裝服務 -->
        <service
            android:name="com.zhou.silence.installer.UninstallService"
            android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.DELETE" />
                <action android:name="android.intent.action.UNINSTALL_PACKAGE" />

                <category android:name="android.intent.category.DEFAULT" />

                <data android:scheme="package" />
            </intent-filter>
        </service>

當然,我也寫了Activity安裝、解除安裝的實現方式,我只貼上了Service的程式碼,因為我認為它比Activity更好。

靜默安裝 InstallService.java

package com.zhou.silence.installer;

import java.io.File;

import com.zhou.silence.installer.InstallerAct.PackageInstallObserver;

import android.app.IntentService;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.widget.Toast;
import android.util.DisplayMetrics;
import android.content.pm.PackageParser;
import android.content.pm.PackageUserState;
import android.content.pm.ResolveInfo;
import android.content.pm.VerificationParams;
import android.content.pm.IPackageInstallObserver;

public class InstallService extends IntentService {

    private PackageInfo mPkgInfo;

    private Uri mPackageURI;

    public InstallService() {
        super("InstallService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        synchronized (this) {
            handleIntent(intent);
        }
    }

    private void handleIntent(Intent intent) {
        try {
            Toast.makeText(getApplicationContext(), "開始安裝...", Toast.LENGTH_SHORT).show();
            PackageManager pm = getPackageManager();
            int installFlags = 0;

            // 獲取要安裝的Uri
            mPackageURI = intent.getData();

            // 用File物件封裝apk檔案的路徑
            final File sourceFile = new File(mPackageURI.getPath());

            // 建立封裝包資訊的Package物件
            PackageParser packageParser = new PackageParser();

            PackageParser.Package parsed = packageParser.parsePackage(sourceFile, 0);

            PackageUserState state = new PackageUserState();

            mPkgInfo = PackageParser.generatePackageInfo(parsed, null, 
                    PackageManager.GET_UNINSTALLED_PACKAGES, 0, 0, null, state);

            if (mPkgInfo == null) {
                // 如果Android程式已經安裝,設定安裝模式為更新
                installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
            }

            VerificationParams verificationParams = new VerificationParams(null, null, 
                    null, VerificationParams.NO_UID, null);

            PackageInstallObserver observer = new PackageInstallObserver();

            // 安裝或更新應用
            pm.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags,
                    mPkgInfo.applicationInfo.packageName, verificationParams, null);

        } catch (Exception e) {
            Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }
    }

    private final int INSTALL_COMPLETE = 1;

    private Handler mHandler = new Handler() {

        public void handleMessage(Message msg) {

            switch (msg.what) {

            case INSTALL_COMPLETE:

                if (msg.arg1 == PackageManager.INSTALL_SUCCEEDED) {
                    // 成功安裝應用程式
                    Toast.makeText(getApplicationContext(), "成功安裝應用程式", Toast.LENGTH_SHORT).show();

                } else if (msg.arg1 == PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE) {
                    // 由於空間不足而導致安裝失敗
                    Toast.makeText(getApplicationContext(), "由於空間不足而導致安裝失敗", Toast.LENGTH_SHORT).show();

                } else {
                    // 由於其他原因導致安裝失敗
                    Toast.makeText(getApplicationContext(), "由於其他原因導致安裝失敗", Toast.LENGTH_SHORT).show();
                }
                break;

            default:
                break;
            }

        }

    };

    // 非同步安裝事件監聽器類
    class PackageInstallObserver extends IPackageInstallObserver.Stub {

        public void packageInstalled(String packageName, int returnCode) {
            Message msg = mHandler.obtainMessage(INSTALL_COMPLETE);
            msg.arg1 = returnCode;
            mHandler.sendMessage(msg);
        }

    }


}

靜默解除安裝 UninstallService.java

package com.zhou.silence.installer;

import com.zhou.silence.installer.UninstallAct.PackageDeleteObserver;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.IntentService;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.widget.Toast;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageManager;
import android.content.pm.UserInfo;
import android.os.ServiceManager;
import android.os.UserManager;
import android.os.UserHandle;

public class UninstallService extends IntentService {

    private static final int UNINSTALL_COMPLETE = 1;

    private UserHandle mUser;
    private boolean mAllUsers;

    private Intent intent;

    private Handler mHandler = new Handler() {

        public void handleMessage(Message msg) {

            switch (msg.what) {

            case UNINSTALL_COMPLETE:
                // 解除安裝完成
                int resultCode = msg.arg1;
                final String packageName = (String) msg.obj;

                // 處理不需要返回解除安裝狀態的情況
                switch (msg.arg1) {
                case PackageManager.DELETE_SUCCEEDED:
                    // 成功解除安裝Android應用
                    Toast.makeText(getBaseContext(), "解除安裝成功", Toast.LENGTH_LONG).show();
                    return;

                case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER:
                    // 由於某些策略的原因導致了解除安裝失敗
                    Toast.makeText(getBaseContext(), "Uninstall failed because " + packageName + " is a device admin",
                            Toast.LENGTH_LONG).show();
                    break;

                case PackageManager.DELETE_FAILED_OWNER_BLOCKED:
                    Toast.makeText(getBaseContext(), "Uninstall failed because owner blocked", Toast.LENGTH_LONG)
                            .show();
                    break;

                default:
                    // 由於其他原因導致了解除安裝失敗
                    Toast.makeText(getBaseContext(), "Uninstall failed for " + packageName + " with code " + msg.arg1,
                            Toast.LENGTH_LONG).show();
                    break;
                }
                break;

            default:
                break;
            }

        }

    };

    public UninstallService() {
        super("UninstallService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        synchronized (this) {
            this.intent = intent;
            handleIntent(intent);
        }
    }

    private void handleIntent(Intent intent) {
        Toast.makeText(getApplicationContext(), "正在解除安裝...", Toast.LENGTH_SHORT).show();

        final Uri packageUri = intent.getData();
        // 獲取要解除安裝程式的包名
        final String packageName = packageUri.getEncodedSchemeSpecificPart();

        mUser = intent.getParcelableExtra(Intent.EXTRA_USER);
        if (mUser == null) {
            mUser = android.os.Process.myUserHandle();
        }
        // 獲取是否刪除所有使用者資料的標誌位
        mAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false);
        if (mAllUsers && UserHandle.myUserId() != UserHandle.USER_OWNER) {
            throw new SecurityException("Only owner user can request uninstall for all users");
        }

        IPackageManager packageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
        PackageDeleteObserver observer = new PackageDeleteObserver();
        try {
            packageManager.deletePackageAsUser(packageName, observer, mUser.getIdentifier(),
                    mAllUsers ? PackageManager.DELETE_ALL_USERS : 0);
        } catch (Exception e) {
            Toast.makeText(getBaseContext(), e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }

    class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
        public void packageDeleted(String packageName, int returnCode) {
            Message msg = mHandler.obtainMessage(UNINSTALL_COMPLETE);
            msg.arg1 = returnCode;
            msg.obj = packageName;
            mHandler.sendMessage(msg);
        }
    }

}

然後,我們可以用模組編譯

mmm packages/apps/ZhouSilenceInstaller/

adb install out/target/product/grouper/system/app/ZhouSilenceInstaller/ZhouSilenceInstaller.apk

就可以看見效果了,SUCCESS!

好了,至此,PackageInstaller的原始碼就分析完畢啦,也實現了自己的靜默安裝和解除安裝。其中發現4.0和5.0的原始碼區別還是很大的,也許你用的是6.0,可能和本文的程式碼有所出入,甚至可能連API都有些不同,請耐心分析,肯定會有所收穫。

靜默安裝的demo:http://download.csdn.net/detail/u012301841/9613549
測試靜默安裝的demo:http://download.csdn.net/detail/u012301841/9613923

相關文章