Android-6.0之PMS安裝APK前奏

本文轉載於:http://www.iloveandroid.net/2016/06/20/Android_PackageManagerService-2/


前面介紹了PMS是如何啓動的,現在介紹Android系統是如何安裝一個APK的。

前面介紹PMS時,已經確定了PMS會註冊成爲一個service,而Android系統中需要使用一個service時,通常要找到其客戶端代理,然後通過其代理使用PMS提供的功能。

如上圖所示:

  1. IPackageManager使用了Android的AIDL語言定義了server要提供的業務函數,然後AIDL編譯器會自動生成IPackageManager接口代碼,其子類Stub繼承Binder且實現了IPackageManager接口

  2. PMS繼承Stub,所以可以作爲Server端

  3. Stub中的一個內部類Proxy中有一個IBinder的成員變量mRemote,利用mRemote可以和Server端通信

  4. client端在使用的時候是使用Context.getPackageManager函數返回的ApplicationPackageManager對象來處理,ApplicationPackageManager內部成員變量mPM指向Proxy類型的對象。也就意味着可以和PMS通信了。

這是一個典型的Binder服務模型,Android系統很多關鍵服務都是採用binder服務模型。最終上層獲取到的PMS的代理是PackageManager這個抽象類的實現類ApplicationPackageManager對象。

如何獲得PMS代理對象

使用下面的代碼,可以獲取到PMS的一個代理對象:

1
2
Context ct = getApplicationContext();
PackageManager pm =  ct.getPackageManager();

其中getPackageManager源碼位置:

1
Android-6/frameworks/base/core/java/android/app/ContextImpl.java

實現代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public PackageManager getPackageManager() {
      if (mPackageManager != null) {
          return mPackageManager;
      }

      IPackageManager pm = ActivityThread.getPackageManager();
      if (pm != null) {
          // Doesn't matter if we make more than one instance.
          return (mPackageManager = new ApplicationPackageManager(this, pm));
      }

      return null;
  }

ActivityThread.getPackageManager源碼位置:

1
Android-6/frameworks/base/core/java/android/app/ActivityThread.java

1
2
3
4
5
6
7
8
9
10
11
public static IPackageManager getPackageManager() {
    if (sPackageManager != null) {
        //Slog.v("PackageManager", "returning cur default = " + sPackageManager);
        return sPackageManager;
    }
    IBinder b = ServiceManager.getService("package");
    //Slog.v("PackageManager", "default service binder = " + b);
    sPackageManager = IPackageManager.Stub.asInterface(b);
    //Slog.v("PackageManager", "default service = " + sPackageManager);
    return sPackageManager;
}

只要瞭解Android binder框架的,就會馬上發現,這無非就是向SM查詢名爲”package”的服務,SM查詢成功的話,返回名爲”package”服務的一個引用對象,然後使用asInterface將其轉換一個代理對象。

PMS在向SM註冊時,名字確實是”package”。

1
Android-6/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
1
2
3
4
5
6
7
public static PackageManagerService main(Context context, Installer installer,
          boolean factoryTest, boolean onlyCore) {
      PackageManagerService m = new PackageManagerService(context, installer,
              factoryTest, onlyCore);
      ServiceManager.addService("package", m);
      return m;
  }

對PackageManager的調用,最終都轉換爲對PMS的調用。

安裝apk

可以使用如下代碼,調用去安裝一個apk文件已經在設備中的apk。

1
2
3
4
5
File apkfile  =  new File("/data/local/tmp/demo.apk");
Uri uri = Uri.fromFile(apkfile);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri,"application/vnd.android.package-archive");
startActivity(intent);

源碼路徑:

1
Android-6/packages/apps/PackageInstaller

系統應用PackageInstaller將會響應這個intent.

它的AndroidMainifest.xml中:

1
2
3
4
5
6
7
8
9
10
11
<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>
            .......

也就是說PackageInstallerActivity會真正響應這個intent.

PackageInstallerActivity中有兩個重要的成員

1
2
PackageManager mPm;
PackageInstaller mInstaller;

其中mPm是PMS的代理對象,mInstaller是PackageInstallerService的代理對象。PMS類之後內聚了PackageInstallerService,在PMS啓動的時候,初始化了改變量。

當PackageInstallerActivity這個Activity啓動的時候

1
2
3
4
5
6
7
protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        mPm = getPackageManager();
        mInstaller = mPm.getPackageInstaller();

        ..............

一開始便獲取到了這兩個對象。

繼續PackageInstallerActivity的onCreate:

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
if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
      ...............
      } else {
          //走的這個分支
          mSessionId = -1;
          mPackageURI = intent.getData();
          mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
          mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
      }
.......................
if ("package".equals(mPackageURI.getScheme())) {
        .................
      } 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);
      }

代碼邏輯還是比較好理解的,從intent獲取到要安裝的apk的路徑。調用PackageUtil.getPackageInfo解析。

其中PackageUtil.getPackageInfo:

1
2
3
4
5
6
7
8
9
10
public static PackageParser.Package getPackageInfo(File sourceFile) {
      final PackageParser parser = new PackageParser();
      try {
          PackageParser.Package pkg = parser.parseMonolithicPackage(sourceFile, 0);
          parser.collectManifestDigest(pkg);
          return pkg;
      } catch (PackageParserException e) {
          return null;
      }
  }

是不是有種熟悉的感覺,在PMS啓動的時候,掃描解析APK不就用到了PackageParser嗎。

1
Android-6/frameworks/base/core/java/android/content/pm/PackageParser.java
1
2
3
4
5
6
7
8
9
10
11
12
13
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
       if (mOnlyCoreApps) {
        ....................
       }
       final AssetManager assets = new AssetManager();
       try {
           final Package pkg = parseBaseApk(apkFile, assets, flags);
           pkg.codePath = apkFile.getAbsolutePath();
           return pkg;
       } finally {
           IoUtils.closeQuietly(assets);
       }
   }

就是調用parseBaseApk來解析傳入的apk文件,這個過程實際上就是在解析apk中AndroidMainfest.xml文件,將其中的信息村粗,並把解析結果存入PackageParser.Package對象中返回。

然後將解析得到的Package對象,通過generatePackageInfo()方法轉換爲一個PackageInfo對象。

繼續PackageInstallerActivity的onCreate:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Block the install attempt on the Unknown Sources setting if necessary.
        if (!requestFromUnknownSource) {
            initiateInstall();
            return;
        }

        // If the admin prohibits it, or we're running in a managed profile, just show error
        // and exit. Otherwise show an option to take the user to Settings to change the setting.
        final boolean isManagedProfile = mUserManager.isManagedProfile();
        if (!unknownSourcesAllowedByAdmin
                || (!unknownSourcesAllowedByUser && isManagedProfile)) {
            showDialogInner(DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES);
            mInstallFlowAnalytics.setFlowFinished(
                    InstallFlowAnalytics.RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING);
        } else if (!unknownSourcesAllowedByUser) {
            // Ask user to enable setting first
            showDialogInner(DLG_UNKNOWN_SOURCES);
            mInstallFlowAnalytics.setFlowFinished(
                    InstallFlowAnalytics.RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING);
        } else {
            initiateInstall();
        }

requestFromUnknownSource爲true,因爲我們確實是從未知來源安裝的,沒有通過內置的應用商店安裝。

這裏做一些檢查,例如如果沒有在設置中打開允許安裝位置來源,這樣就要彈出一個提示框,然後用戶去設置中打開允許位置來源安裝。

最終都是調用initiateInstall這個方法的。

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
private void initiateInstall() {
        String pkgName = mPkgInfo.packageName;
        // Check if there is already a package on the device with this name
        // but it has been renamed to something else.
        String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });
        if (oldName != null && oldName.length > 0 && oldName[0] != null) {
            pkgName = oldName[0];
            mPkgInfo.packageName = pkgName;
            mPkgInfo.applicationInfo.packageName = pkgName;
        }
        // Check if package is already installed. display confirmation dialog if replacing pkg
        try {
            // This is a little convoluted because we want to get all uninstalled
            // apps, but this may include apps with just data, and if it is just
            // data we still want to count it as "installed".
            mAppInfo = mPm.getApplicationInfo(pkgName,
                    PackageManager.GET_UNINSTALLED_PACKAGES);
            if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
                mAppInfo = null;
            }
        } catch (NameNotFoundException e) {
            mAppInfo = null;
        }

        mInstallFlowAnalytics.setReplace(mAppInfo != null);
        mInstallFlowAnalytics.setSystemApp(
                (mAppInfo != null) && ((mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0));

        startInstallConfirm();
    }

代碼邏輯也很簡單,主要是判斷當前系統中是否已經安裝這個app,安裝過的話,設置替換flag等信息,然後調用startInstallConfirm,會彈出一個對話框,詢問是否需要安裝此應用嗎,以及這個app將獲得哪些權限等。

因爲前面已經解析過該apk了,所以顯示其有哪些權限就很簡單。

當點擊確定按鈕的時候,執行下面的代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void onClick(View v) {
    if (v == mOk) {
        if (mOkCanInstall || mScrollView == null) {
            mInstallFlowAnalytics.setInstallButtonClicked();
            if (mSessionId != -1) {
                .................
            } else {
              // 走這個分支
                startInstall();
            }
        } else {
            mScrollView.pageScroll(View.FOCUS_DOWN);
        }
    } else if(v == mCancel) {
      ........................
    }
}

主要工作由startInstall()完成:

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
private void startInstall() {
      // Start subactivity to actually install the application
      Intent newIntent = new Intent();
      newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
              mPkgInfo.applicationInfo);
      newIntent.setData(mPackageURI);
      newIntent.setClass(this, InstallAppProgress.class);
      newIntent.putExtra(InstallAppProgress.EXTRA_MANIFEST_DIGEST, mPkgDigest);
      newIntent.putExtra(
              InstallAppProgress.EXTRA_INSTALL_FLOW_ANALYTICS, mInstallFlowAnalytics);
      String installerPackageName = getIntent().getStringExtra(
              Intent.EXTRA_INSTALLER_PACKAGE_NAME);
      if (mOriginatingURI != null) {
          newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
      }
      if (mReferrerURI != null) {
          newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
      }
      if (mOriginatingUid != VerificationParams.NO_UID) {
          newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
      }
      if (installerPackageName != null) {
          newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
                  installerPackageName);
      }
      if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
          newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
          newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
      }
      if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
      startActivity(newIntent);
      finish();
  }
}

這裏創建一個Intent,設置數據和哪個發送給哪個class,調用 startActivity(newIntent),啓動InstallAppProgress.

發佈了14 篇原創文章 · 獲贊 26 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章