apk的安裝過程同樣離不開Binder機制和PMS。當我們選中安裝Apk,一般分爲兩種安裝方式:
- 有界面安裝
- 無界面安裝
一、有界面安裝方式
比如有些版本的手機會彈出是否確定安裝的界面,點擊確定以後纔會繼續安裝。對於這種安裝方式會首先通過跨進程的方式調用系統的啓動安裝界面。流程圖如下:
1、不同android版本手動安裝的apk代碼不一樣
在7.0版本之前我們可能使用以下代碼方式進行apk安裝:
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.parse("file://" + path),"application/vnd.android.package-archive");
context.startActivity(intent);
但是如果在之後版本使用這種方式,就會拋出異常:FileUriExposeException,產生異常的原因是因爲StrictMode API政策禁止應用程序將file:// Uri暴露給另一個應用程序。所以7.x之後我們使用如下方式進行apk安裝:
Uri apkUri = FileProvider.getUriForFile(context,"包名.fileprovider", apkFile);
Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
install.setDataAndType(apkUri, "application/vnd.android.package-archive");
context.startActivity(install)
在此之前,你需要在res/xml/file_paths.xml配置如下內容:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<external-path
path=""
name="Download"/>
</paths>
</resources>
在AndroidManifest.xml配置如下內容:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="包名.provider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
2、不同android版本啓動方式不一樣
爲什麼會出現上面的問題呢?這是因爲7.x版本之後,系統的啓動界面入口發生了變化。從之前的PackageInstallerActivity.java變成了InstallStart.java.不過最後你會發現,還是會走到PackageInstallerActivity這個類。
(1) InstallStart.java啓動界面
進入onCreate()方法,代碼如下:
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
//--------1、判斷是否有安裝權限 ,沒有的話直接結束界面
if (mAbortInstall) {
setResult(RESULT_CANCELED);
finish();
return;
}
//---------2、跳轉參數設置
Intent nextActivity = new Intent(intent);
nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);
nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);
nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);
//---------3、當前action是否爲PackageInstaller.ACTION_CONFIRM_PERMISSIONS 顯示這裏不是,我們是Intent.ACTION_VIEW
if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Uri packageUri = intent.getData();
//-------4、判斷packageUri是否爲空不成立,判斷Uri的Scheme協議是否是content,對於7.x系列條件是成立的
if (packageUri != null && (packageUri.getScheme().equals(ContentResolver.SCHEME_FILE)
|| packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) {
// Copy file to prevent it from being changed underneath this process
nextActivity.setClass(this, InstallStaging.class);
} else if (packageUri != null && packageUri.getScheme().equals(
PackageInstallerActivity.SCHEME_PACKAGE)) {
//---------5、不是content協議跳轉到 PackageInstallerActivity界面
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
//安裝失敗
Intent result = new Intent();
result.putExtra(Intent.EXTRA_INSTALL_RESULT,
PackageManager.INSTALL_FAILED_INVALID_URI);
setResult(RESULT_FIRST_USER, result);
nextActivity = null;
}
}
//----------6、跳轉並關閉當前界面
if (nextActivity != null) {
startActivity(nextActivity);
}
finish();
}
(2) InstallStaging.java界面
它的作用就是將傳入的content協議轉爲file協議。關鍵代碼如下:
private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> {
@Override
protected Boolean doInBackground(Uri... params) {
...
return true;
}
@Override
protected void onPostExecute(Boolean success) {
if (success) {
Intent installIntent = new Intent(getIntent());
//設置跳轉界面 8.0代碼
//9.0會跳轉到DeleteStagedFileOnResult 並在進入PackageInstallerActivity之後通知它將file協議刪除
installIntent.setClass(InstallStaging.this, PackageInstallerActivity.class);
//將content協議轉換成file協議
installIntent.setData(Uri.fromFile(mStagedFile));
installIntent.setFlags(installIntent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);
installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
//跳轉到PackageInstallerActivity界面
startActivityForResult(installIntent, 0);
} else {
showError();
}
}
}
(3) PackageInstallerActivity.java界面
該界面纔是真正安裝的界面,在onCreate()方法會初始化安裝需要的對象信息、通過Uri創建一個apk文件、並解析apk文件得到包信息,主要代碼如下:
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
//------------1、初始化安裝需要的對象信息
mPm = getPackageManager();
mIpm = AppGlobals.getPackageManager();
mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
mInstaller = mPm.getPackageInstaller();
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
//------------2、根據Uri的Scheme進行預處理
boolean wasSetUp = processPackageUri(packageUri);
if (!wasSetUp) {
return;
}
//-------------3、判斷是否是未知來源的應用
checkIfAllowedAndInitiateInstall();
}
進入註釋2處方法,主要代碼如下:
private boolean processPackageUri(final Uri packageUri) {
mPackageURI = packageUri;
final String scheme = packageUri.getScheme();
switch (scheme) {
...
case SCHEME_FILE: {
//-------1、根據apk路徑創建apk文件
File sourceFile = new File(packageUri.getPath());
//-------2、解析得到apk包信息
PackageParser.Package parsed = PackageUtil.getPackageInfo(this, sourceFile);
...
//-------3、通過PackageParser類重新生成一個對象PackageInfo,保存包信息
mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
PackageManager.GET_PERMISSIONS, 0, 0, null,
new PackageUserState());
mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
} break;
...
}
return true;
}
進入註釋3處方法,主要代碼如下:
private void checkIfAllowedAndInitiateInstall() {
//-----------1、允許未知應用安裝或者根據Intent判斷得出該APK不是未知來源,進入初始化安裝方法
if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
initiateInstall();
return;
}
// ----------2、是否跳轉到設置界面手動開啓權限
if (isUnknownSourcesDisallowed()) {
if ((mUserManager.getUserRestrictionSource(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
Process.myUserHandle()) & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
return;
} else {
startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
finish();
}
} else {
handleUnknownSources();
}
}
initiateInstall()方法,最終會彈出安裝確定的dialog,當用戶點擊確定後進入正在安裝界面InstallInstalling.java
(4) InstallInstalling.java界面
主要處理安裝的代碼在onResume()方法中會開啓異步任務,代碼如下:
private final class InstallingAsyncTask extends AsyncTask<Void, Void,
PackageInstaller.Session> {
volatile boolean isDone;
@Override
protected PackageInstaller.Session doInBackground(Void... params) {
PackageInstaller.Session session;
...
//------------1、將APK的信息通過IO流的形式寫入到PackageInstaller.Session中
...
return session;
}
@Override
protected void onPostExecute(PackageInstaller.Session session) {
...
//-----------2、調用PackageInstaller.Session的commit方法,將APK的信息交由PMS處理
session.commit(pendingIntent.getIntentSender());
...
}
}
安裝成功後會進入回調,最終進入launchSuccess()方法,啓動安裝成功界面
(4) InstallSuccess.class界面
這樣就完成了有界面的apk安裝
二、無界面安裝方式
當我們輸入"adb -s 設備地址 install apk全路徑"執行命令以後,同樣可以進行apk的安裝。它的安裝流程流程圖如下:
c層代碼不做太多分析,調動的文件源碼路徑爲"system/core/adb/commandline.cpp",c層最終通過shell腳本指向pm.java類。
1、pm.java
pm.java的入口方法main中會調用run方法,主要代碼如下:
(1)run()方法
public int run(String[] args) throws IOException, RemoteException {
...
//--------1、拿到PMS的代理對象
mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
if (mPm == null) {
System.err.println(PM_NOT_RUNNING_ERR);
return 1;
}
mInstaller = mPm.getPackageInstaller();
....
//--------2、安裝方法
if ("install".equals(op)) {
return runInstall();
}
//--------3、卸載方法
if ("uninstall".equals(op)) {
return runUninstall();
}
...
}
該方法會拿到PMS的代理對象,然後執行runInstall()方法,主要代碼如下:
(2)runInstall()方法
private int runInstall() {
...
try {
VerificationParams verificationParams = new VerificationParams(verificationURI,
originatingURI, referrerURI, VerificationParams.NO_UID, null);
mPm.installPackageAsUser(apkFilePath, obs.getBinder(), installFlags,
installerPackageName, verificationParams, abi, userId);
...
}
} catch (RemoteException e) {
...
}
}
最終調用PMS遠程服務的installPackageAsUser()方法。
2、PackageManagerService.java
(1)installPackageAsUser()方法
該方法最後會發送一條Message消息,交給內部的handler處理。handler處理消息
(1)doHandleMessage()方法
void doHandleMessage(Message msg) {
switch (msg.what) {
case INIT_COPY: {
//----------1、取出安裝信息InstallParams
HandlerParams params = (HandlerParams) msg.obj;
//----------2、將新的安裝請求放入到mPendingIntalls中,等待處理 idx爲當前安裝apk個數
PendingInstalls.add(idx, params);
//---------3、發送消息
mHandler.sendEmptyMessage(MCS_BOUND);
...
break;
}
case MCS_BOUND: {
...
//---------4、調用startCopy()方法執行安裝
if (params.startCopy()) {
...
}
...
break;
}
}
}
最後第4步調用startCopy方法。
(2)startCopy()方法
內部很簡單,會做另外一個方法handleStartCopy()調用,代碼不貼出來了
(3)handleStartCopy()方法
public void handleStartCopy() throws RemoteException {
...
//---------1、判斷是否安裝在D卡
final boolean onSd = (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;
//判斷是否安裝內部空間
final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;
...
//---------2、創建安裝參數對象
final InstallArgs args = createInstallArgs(this);
mArgs = args;
...
//---------3、校驗安裝包是否需要校驗
if(false){
//---------4、如果不需要校驗,調用InstallArgs的copyApk函數
ret = args.copyApk(mContainerService, true);
...
}
判斷安裝路徑,校驗安裝包,最後執行copyApk()方法。
(4)copyApk()方法
內部會調用doCopyApk()方法。
(5)doCopyApk()方法
private int doCopyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
...
//-----------1、創建安裝目錄
final File tempDir =mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);
codeFile = tempDir;
resourceFile = tempDir;
...
//------------2、真正實現apk文件拷貝
ret = imcs.copyPackage(origin.file.getAbsolutePath(), target);
if (ret != PackageManager.INSTALL_SUCCEEDED) {
Slog.e(TAG, "Failed to copy package");
return ret;
}
//-----------3、 獲取庫的跟目錄,進行Native代碼,即so的拷貝
final File libraryRoot = new File(codeFile, LIB_DIR_NAME);
NativeLibraryHelper.Handle handle = null;
...
handle = NativeLibraryHelper.Handle.create(codeFile);
ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,abiOverride);
}
以上拷貝完成以後就完成了apk的安裝。
總結: