Android应用安装

Android通过PackageManagerService(后面简称Pms)进行包管理,其主要功能包括:用户ID分配、包解析、包的安装卸载等。本文不对Pms进行分析,主要目的是探讨一下包安装。在本文中主要探讨包安装的相关操作,卸载作为安装的逆过程,实现类似,不再赘述。

Android中APK的安装方式

在Android中APK的安装有三种方式:

1、开机Pms初始化时,扫描包安装目录。

@/frameworks/base/services/java/com/android/server/SystemServer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void initAndLoop() {
    ......
     
    IPackageManager pm = null;
     
    ......
     
    try {
        ......
         
        pm = PackageManagerService.main(context, installer,
                factoryTest != SystemServer.FACTORY_TEST_OFF,
                onlyCore);
        ......
    catch (RuntimeException e) {
        Slog.e("System""******************************************");
        Slog.e("System""************ Failure starting core service", e);
    }
    ......
}

@/frameworks/base/services/java/com/android/server/pm/PackageManagerService.java

1
2
3
4
5
6
7
public static final IPackageManager main(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
    PackageManagerService m = new PackageManagerService(context, installer,
            factoryTest, onlyCore);
    ServiceManager.addService("package", m);
    return m;
}

下面是Pms构造函数的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public PackageManagerService(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
    ......
     
    synchronized (mInstallLock) {
    // writer
    synchronized (mPackages) {
        ......
         
        File dataDir = Environment.getDataDirectory();
        mAppDataDir = new File(dataDir, "data");
        mAppInstallDir = new File(dataDir, "app");
        mAppLibInstallDir = new File(dataDir, "app-lib");
        mAsecInternalPath = new File(dataDir, "app-asec").getPath();
        mUserAppDataDir = new File(dataDir, "user");
        mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
 
        ......
         
        // Find base frameworks (resource packages without code).
        mFrameworkInstallObserver = new AppDirObserver(
            frameworkDir.getPath(), OBSERVER_EVENTS, truefalse);
        mFrameworkInstallObserver.startWatching();
        scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM
                | PackageParser.PARSE_IS_SYSTEM_DIR,
                scanMode | SCAN_NO_DEX, 0);
 
        // Collected privileged system packages.
        File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
        mPrivilegedInstallObserver = new AppDirObserver(
                privilegedAppDir.getPath(), OBSERVER_EVENTS, truetrue);
        mPrivilegedInstallObserver.startWatching();
            scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR
                    | PackageParser.PARSE_IS_PRIVILEGED, scanMode, 0);
 
        // Collect ordinary system packages.
        File systemAppDir = new File(Environment.getRootDirectory(), "app");
        mSystemInstallObserver = new AppDirObserver(
            systemAppDir.getPath(), OBSERVER_EVENTS, truefalse);
        mSystemInstallObserver.startWatching();
        scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM
                | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);
 
        // Collect all vendor packages.
        File vendorAppDir = new File("/vendor/app");
        mVendorInstallObserver = new AppDirObserver(
            vendorAppDir.getPath(), OBSERVER_EVENTS, truefalse);
        mVendorInstallObserver.startWatching();
        scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM
                | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);
 
        ......
 
        if (!mOnlyCore) {
            EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                    SystemClock.uptimeMillis());
            mAppInstallObserver = new AppDirObserver(
                mAppInstallDir.getPath(), OBSERVER_EVENTS, falsefalse);
            mAppInstallObserver.startWatching();
            scanDirLI(mAppInstallDir, 0, scanMode, 0);
 
            mDrmAppInstallObserver = new AppDirObserver(
                mDrmAppPrivateInstallDir.getPath(), OBSERVER_EVENTS, falsefalse);
            mDrmAppInstallObserver.startWatching();
            scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,
                    scanMode, 0);
            ......
    // synchronized (mPackages)
    // synchronized (mInstallLock)
}

通过Pms的构造函数可以看出,Pms在初始化时会扫描/system/app、vender/app、/data/app、/data/app-private四个应用安装目录,然后调用sanDirLI方法进行安装。Pms通过AppDirObserver对这四个应用安装目录进行监控,一旦发现APK格式的文件则会调用scanPackageLI进行安装。

2、通过包安装器PackageInstaller安装

Android提供了一个默认的包安装器,位于/package/app/PackageInstaller目录。通过其Manifest文件可以看出,PackageInstaller会对我们安装应用发出的Intent进行处理,这里PackageInstaller提供了两种处理方式,分别是:file方式和package方式。

@/package/app/PackageInstaller/AndroidManifest.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<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>
</activity>

@/package/app/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java

1
2
3
4
5
6
7
8
@Override
protected void onCreate(Bundle icicle) {
    super.onCreate(icicle);
 
    ......
     
    initiateInstall();
}
1
2
3
4
5
private void initiateInstall() {
    ......
 
    startInstallConfirm();
}

startInstallConfirm方法中点击“确认”后,会发出一个Intent,接收者为InstallAppProgress。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void onClick(View v) {
    if(v == mOk) {
        if (mOkCanInstall || mScrollView == null) {
            // Start subactivity to actually install the application
            mInstallFlowAnalytics.setInstallButtonClicked();
            Intent newIntent = new Intent();
            newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
                    mPkgInfo.applicationInfo);
            newIntent.setData(mPackageURI);
            newIntent.setClass(this, InstallAppProgress.class);
            ......
             
            startActivity(newIntent);
            finish();
        else {
            mScrollView.pageScroll(View.FOCUS_DOWN);
        }
    else if(v == mCancel) {
        ......
    }
}

@/package/app/PackageInstaller/src/com/android/packageinstaller/InstallAppProgress.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public void initView() {
    setContentView(R.layout.op_progress);
    int installFlags = 0;
    PackageManager pm = getPackageManager();
     
    ......
     
    String installerPackageName = getIntent().getStringExtra(
            Intent.EXTRA_INSTALLER_PACKAGE_NAME);
    Uri originatingURI = getIntent().getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
    Uri referrer = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
    int originatingUid = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
            VerificationParams.NO_UID);
    ManifestDigest manifestDigest = getIntent().getParcelableExtra(EXTRA_MANIFEST_DIGEST);
    VerificationParams verificationParams = new VerificationParams(null, originatingURI,
            referrer, originatingUid, manifestDigest);
    PackageInstallObserver observer = new PackageInstallObserver();
 
    if ("package".equals(mPackageURI.getScheme())) {
        try {
            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);
    }
}

InstallAppProgress即应用安装过程中的进度条界面。通过上面的代码可以看到在initView方法的最后会调用Pms的installPackageWithVerificationAndEncryption方法进行安装。

3、通过adb命令安装

adb命令pm是Pms的Shell客户端,通过pm可以进行包相关的一些操作,包括安装和卸载。pm命令的用法如下:

Android应用安装Android应用安装

pm的代码实现在Pm.java中,如下:

@/frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) {
    new Pm().run(args);
}
 
public void run(String[] args) {
    ......
     
    mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
     
    ......
 
    if ("install".equals(op)) {
        runInstall();
        return;
    }
 
    if ("uninstall".equals(op)) {
        runUninstall();
        return;
    }
    ......
}

在run方法中初始化了一个Pms的客户端代理对象mPm,后续的相关操作将有mPm完成。下面看一下Pm中负责安装的方法runInstall的代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
private void runInstall() {
    int installFlags = PackageManager.INSTALL_ALL_USERS;
     
    ......
     
    while ((opt=nextOption()) != null) {
        if (opt.equals("-l")) {
            installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
        else if (opt.equals("-r")) {
            installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
        else if (opt.equals("-i")) {
            installerPackageName = nextOptionData();
            if (installerPackageName == null) {
                System.err.println("Error: no value specified for -i");
                return;
            }
        else if (opt.equals("-t")) {
            installFlags |= PackageManager.INSTALL_ALLOW_TEST;
        else if (opt.equals("-s")) {
            // Override if -s option is specified.
            installFlags |= PackageManager.INSTALL_EXTERNAL;
        else if (opt.equals("-f")) {
            // Override if -s option is specified.
            installFlags |= PackageManager.INSTALL_INTERNAL;
        else if (opt.equals("-d")) {
            installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
 
        ......   
             
    PackageInstallObserver obs = new PackageInstallObserver();
    try {
        VerificationParams verificationParams = new VerificationParams(verificationURI,
                originatingURI, referrerURI, VerificationParams.NO_UID, null);
 
        mPm.installPackageWithVerificationAndEncryption(apkURI, obs, installFlags,
                installerPackageName, verificationParams, encryptionParams);
 
        synchronized (obs) {
            while (!obs.finished) {
                try {
                    obs.wait();
                catch (InterruptedException e) {
                }
            }
            if (obs.result == PackageManager.INSTALL_SUCCEEDED) {
                System.out.println("Success");
            else {
                System.err.println("Failure ["
                        + installFailureToString(obs.result)
                        "]");
            }
        }
    catch (RemoteException e) {
        System.err.println(e.toString());
        System.err.println(PM_NOT_RUNNING_ERR);
    }
}

可以看出runInstall最终会调用Pms的installPackageWithVerificationAndEncryption方法进行安装。通过pm安装时,安装成功的返回信息为“Success”,安装失败的返回信息为”Failure[失败信息]"。

静默安装实现

在了解了Android中包安装的方式后,接下来探讨一些如何实现”静默安装“。所谓静默安装即跳过安装界面和进度条,在不被用户察觉的情况下载后台安装。下面针对上面的三种安装方式分别来分析如何实现静默安装。

1、push安装包到应用安装目录的方式

在Pms初始化时安装包的流程中,我们知道Pms会监控/system/app、vender/app、/data/app、/data/app-private这四个应用安装目录。因此如果能够将APK文件push进应用安装目录不就可以触发AppDirObserver中的包安装逻辑了了吗?所以这种思路理论上是行得通的,但有两个局限:

  • 局限一:如下图所示,/system/app的访问权限为root,这就要求在push到/system/app目录时必须具有root权限。

    而/data/app的访问权限为system。要获得system权限就要求使用这种方式的应用程序必须签名为platform并且sharedUserId制定为“android.uid.system”。

  • 局限二:系统应用(/system/app)与普通应用(/data/app)的安装方式是不同的,对于系统应用,所有资源都包含在apk这个zip包中,而且其在/system/app不必以包名命名(理论上可以随便起名)。

    而对于普通应用安装后,它的dex、lib、资源文件(安装包)分别存放在不同的目录,并且安装后以packagename-x.apk的形式保存在/data/app目录下。?

Android应用安装

那这种安装方式是不是就没有用了呢?非也。

网上有些电子市场或管家类软件实现的”秒装“功能应该就是安装这个思路实现的,当然这里只是猜测,需要进一步研究。

2、调用Pm隐藏API

Android实现了一个应用安装器的APK负责包的安装工作,在上面的分析中我们知道,PackageInstaller的工作实际上只是安装界面、权限确认、进度显示等,真正的安装工作依然是调用Pms实现的。到这里我们就有了第二种思路,能不能绕过安装界面,直接调用Pms里面的相应方法呢?当然可以,PackageManager类中就提供了这样的方法:

@/frameworks/base/core/java/android/content/pm/PackageManager.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * @hide
 *
 * Install a package. Since this may take a little while, the result will
 * be posted back to the given observer.  An installation will fail if the calling context
 * lacks the {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if the
 * package named in the package file‘s manifest is already installed, or if there‘s no space
 * available on the device.
 *
 * @param packageURI The location of the package file to install.  This can be a ‘file:‘ or a
 * ‘content:‘ URI.
 * @param observer An observer callback to get notified when the package installation is
 * complete. {@link IPackageInstallObserver#packageInstalled(String, int)} will be
 * called when that happens.  observer may be null to indicate that no callback is desired.
 * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
 * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
 * @param installerPackageName Optional package name of the application that is performing the
 * installation. This identifies which market the package came from.
 */
public abstract void installPackage(
        Uri packageURI, IPackageInstallObserver observer, int flags,
        String installerPackageName);

可以看出,这个方法是hide的,因此在应用开发时如果要使用,必须通过反射。

这里的IPackageInstallObserver是installPackage方法的一个回调接口通知,其实现在IPackageInstallObserver.aidl中,如下:

@/frameworks/base/core/java/com/android/content/pm/IPackageInstallObserver.aidl

1
2
3
4
5
6
7
8
9
package android.content.pm;
 
/**
 * API for installation callbacks from the Package Manager.
 * @hide
 */
oneway interface IPackageInstallObserver {
    void packageInstalled(in String packageName, int returnCode);
}

使用Android内置未公开API有两种方法:一种是通过反射的方式实现;另一种是在工程目录下建立与所引用系统类相同的类和方法,这里只要求类和方法名相同,不需要实现,只保证编译时不报错就可以了,根据Java的类加载机制,在运行时,会去加载系统类。下面是采用第二种方法时的两段示例代码:

实现接口回调的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class MyPakcageInstallObserver extends IPackageInstallObserver.Stub {
        Context cxt;
        String appName;
        String filename;
        String pkname;
 
        public MyPakcageInstallObserver(Context c, String appName,
                 String filename,String packagename) {
            this.cxt = c;
            this.appName = appName;
            this.filename = filename;
            this.pkname = packagename;
        }
 
        @Override
        public void packageInstalled(String packageName, int returnCode) {
            Log.i(TAG, "returnCode = " + returnCode);// 返回1代表安装成功
                        if (returnCode == 1) {
                            //TODO
                        }
            Intent it = new Intent();
            it.setAction(CustomAction.INSTALL_ACTION);
            it.putExtra("install_returnCode", returnCode);
            it.putExtra("install_packageName", packageName);
            it.putExtra("install_appName", appName); cxt.sendBroadcast(it);
        }
    }

调用PackageManager.java隐藏方法,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
     * 静默安装
     * */
    public static void autoInstallApk(Context context, String fileName,
            String packageName, String APPName) {
        Log.d(TAG, "jing mo an zhuang:" + packageName + ",fileName:" + fileName);
        File file = new File(fileName);
        int installFlags = 0;
        if (!file.exists())
            return;
        installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
        if (hasSdcard()) {
            installFlags |= PackageManager.INSTALL_EXTERNAL;
        }
        PackageManager pm = context.getPackageManager();
        try {
            IPackageInstallObserver observer = new MyPakcageInstallObserver(
                    context, APPName, appId, fileName,packageName,type_name);
            Log.i(TAG, "########installFlags:" + installFlags+"packagename:"+packageName);
            pm.installPackage(Uri.fromFile(file), observer, installFlags,
                    packageName);
        catch (Exception e) {
             
        }
 
    }

这种方法也有一定的限制:

首先,要在AndroidManifest.xml中声明”android.permission.INSTALL_PACKAGES”权限;

其次,应用需要system权限。


3、调用pm命令进行安装

在adb窗口通过pm install安装包本来就是没有安装界面的,这不正是我们想要的吗?通过pm的安装方式需要取得root或system权限。

pm的安装方式有两种,一种需要root权限,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
new Thread() {
    public void run() {
    Process process = null;
    OutputStream out = null;
    InputStream in = null;
    try {
    // 请求root
    process = Runtime.getRuntime().exec("su");
    out = process.getOutputStream();
    // 调用安装
    out.write(("pm install -r " + currentTempFilePath + "\n").getBytes());
    in = process.getInputStream();
    int len = 0;
    byte[] bs = new byte[256];
    while (-1 != (len = in.read(bs))) {
    String state = new String(bs, 0, len);
    if (state.equals("Success\n")) {
       //安装成功后的操作
         }
       }
    catch (IOException e) {
        e.printStackTrace();
    catch (Exception e) {
        e.printStackTrace();
    finally {
        try {
            if (out != null) {
                out.flush();
                out.close();
            }
            if (in != null) {
                in.close();
            }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
  }
}.start();

另一钟需要system权限,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
new Thread() {
    public void run() {
    Process process = null;
    InputStream in = null;
    try {
    // 请求root
    process = Runtime.getRuntime().exec("pm install -r " + currentTempFilePath + "\n"); 
    in = process.getInputStream();
    int len = 0;
    byte[] bs = new byte[256];
    while (-1 != (len = in.read(bs))) {
    String state = new String(bs, 0, len);
    if (state.equals("Success\n")) {
       //安装成功后的操作
         }
       }
    catch (IOException e) {
        e.printStackTrace();
    catch (Exception e) {
        e.printStackTrace();
    finally {
        try {
            if (in != null) {
                in.close();
            }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
  }
}.start();

关于system权限的获取在介绍push方式的安装时已做介绍。上面的代码只给出了比较核心的部分,在实际实现中,对返回结果的处理同样重要。

Android应用安装,布布扣,bubuko.com

Android应用安装

上一篇:hutool实战(带你掌握里面的各种工具)目录


下一篇:PHPWAMP站点管理的“域名模式”和“端口模式”详解、均支持自定义