Android8.0 PackageManagerService相關 -- APK安裝和install 的變更和原始碼淺析
1.1 APK安裝和install
installd服務是用來執行程式包的安裝與解除安裝的。
1.1.1 參考資料
//各版本支援的命令
//installer overview
//Android中installd程序存在的意義
http://blog.csdn.net/liqingxu2005/article/details/43447941
//install原始碼分析4.4
http://blog.csdn.net/yangwen123/article/details/11104397
////install 原始碼分析7.0
http://blog.csdn.net/gaugamela/article/details/52769139
//8.0 install原始碼有很大變化,概述見本文,詳細的需要自行分析程式碼
//apk安裝過程PMS的原始碼分析
//apk安裝過程overview
//4種安裝方法的程式碼分析,程式碼過時,可參考分析問題的方法
http://blog.csdn.net/wh_19910525/article/details/7909686
1.1.2 初始過程的變化
在android 7.0,installd的啟動設定不再放在init.rc裡面了,放在frameworks\native\cmds\installd\installd.rc,並frameworks\native\cmds\installd\Android.mk 裡設定這個rc檔案進行編譯,
LOCAL_STATIC_LIBRARIES := libdiskusage
LOCAL_ADDITIONAL_DEPENDENCIES += $(LOCAL_PATH)/Android.mk
LOCAL_INIT_RC :=installd.rc
LOCAL_CLANG := true
include $(BUILD_EXECUTABLE)
在android8.0,又發生了一些變化,install.rc裡建立了很多目錄,並將rc檔案放在frameworks/native/cmds/installd/Android.bp進行編譯,
cc_binary {
name:"installd",
defaults:["installd_defaults"],
srcs:["installd.cpp"],
static_libs:["libdiskusage"],
init_rc: ["installd.rc"],
}
1.1.3 IPC變化
在android7.0及之前,IPC通過socket完成;
在android8.0,IPC通過binder實現。
在下面檔案可看到變化
frameworks\base\core\java\com\android\internal\os\Installer.java
frameworks\native\cmds\installd\
1.1.4 Apk安裝
Apk安裝有幾種常見的場景:系統內建apk的安裝,adb install安裝,應用市場下載後點擊安裝或本地檔案點選安裝,靜默安裝等。
//開機PKMS掃描、解析 4.4
http://blog.csdn.net/luoshengyang/article/details/6766010
//安裝原始碼分析,開機安裝的java層 6.0
在7.0開始,安裝apk的函式createDataDirsLI()就沒有了,使用了新的邏輯,可從PackageManagerService.java的scanPackageDirtyLI繼續向下分析程式碼。
順便提下,6.0的scanPackageDirtyLI向下呼叫到installd的介面,操作檔案時使用了selinux_android_setfilecon,這裡和selinux的檔案訪問策略有關,有興趣的可以分析下。
//安裝原始碼分析,點選安裝的java層 6.0
在應用層,8.0的程式碼發生了一些變化,下面這個控制安裝介面的檔案沒有了,
/packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallAppProgress.java
,用InstallInstalling取代了它,
主要的檔案差異如下,針對不同裝置進行了相應處理,處理邏輯的變化就暫不分析了,
1.1.5 8.0 apk安裝
安裝應用的時候會發一個intent,
PMS解析intent,呼叫PackageInstallerActivity來啟動安裝介面,
會呼叫到PackageInstallerActivity的onCreate—startInstallConfirm,在之後的onClick,執行startInstall,啟動android O新增加的安裝activity InstallInstalling,
private void startInstall() {
// Startsubactivity to actually install the application
Intent newIntent =new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
newIntent.setClass(this, InstallInstalling.class);
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();
}
在InstallInstalling的onCreate方法裡,有兩個主要步驟,一個是PackageParser.parsePackageLite,他解析apk裡面的部分資訊出來;另一個是getPackageInstaller().createSession,它負責建立一個會話,用於後續的安裝,會話是通過binder呼叫在PackageInstallerServices裡面建立的。
PackageParser.PackageLitepkg = PackageParser.parsePackageLite(file, 0);
mSessionId =getPackageManager().getPackageInstaller().createSession(params);
在InstallInstalling.java的onResume啟動一個非同步task InstallingAsyncTask,
protected void onResume() {
super.onResume();
// This is thefirst onResume in a single life of the activity
if(mInstallingTask == null) {
PackageInstaller installer = getPackageManager().getPackageInstaller();
PackageInstaller.SessionInfo sessionInfo =installer.getSessionInfo(mSessionId);
if(sessionInfo != null && !sessionInfo.isActive()) {
mInstallingTask = new InstallingAsyncTask();
mInstallingTask.execute();
} else {
// we willreceive a broadcast when the install is finished
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
}
}
}
InstallingAsyncTask主要是操作session,session也通過binder方式來操作,主要完成的工作是通過之前建立的session,建立資料夾,將檔案拷貝到安裝目錄下面,
protected PackageInstaller.SessiondoInBackground(Void... params) {
PackageInstaller.Session session;
try {
session =getPackageManager().getPackageInstaller().openSession(mSessionId);
} catch (IOException e) {
returnnull;
}
session.setStagingProgress(0);
try {
File file= new File(mPackageURI.getPath());
try(InputStream in = new FileInputStream(file)) {
longsizeBytes = file.length();
try(OutputStream out = session
.openWrite("PackageInstaller", 0, sizeBytes)) {
byte[] buffer = new byte[4096];
while (true) {
int numRead = in.read(buffer);
if (numRead == -1) {
session.fsync(out);
break;
}
if (isCancelled()) {
session.close();
break;
}
out.write(buffer, 0, numRead);
public IPackageInstallerSessionopenSession(int sessionId) {
try {
returnopenSessionInternal(sessionId);
} catch(IOException e) {
throwExceptionUtils.wrap(e);
}
}
通過prepareStageDir建立目錄,
static void prepareStageDir(File stageDir)throws IOException {
if(stageDir.exists()) {
throw newIOException("Session dir already exists: " + stageDir);
}
try {
Os.mkdir(stageDir.getAbsolutePath(), 0755);
Os.chmod(stageDir.getAbsolutePath(), 0755);
} catch(ErrnoException e) {
// Thispurposefully throws if directory already exists
throw newIOException("Failed to prepare session dir: " + stageDir, e);
}
if(!SELinux.restorecon(stageDir)) {
throw newIOException("Failed to restorecon session dir: " + stageDir);
}
}
目前為止apk的安裝都只是在PackageInstaller apk和framework的PackageInstaller框架之間交換,還沒有涉及到PMS,這一點是怎麼做到的呢,如果沒有PMS參與,就沒有參考文件裡面我們瞭解的那些對manifest的解析、許可權處理、檔案拷貝和dex優化等相關流程,這裡就產生了割裂感。我看到的很多文件對這點都沒講透,所以這裡多寫一點。
PMS在上層的介面類是APM,通過PM類在PackageInstaller和PackageInstallerSession的引用就能找到相關的關聯關係,然後我們就會發現一個很重要的方法,就是session的commit方法,詳細說來,
在7.0系統,InstallAppProgress的doPackageStage直接呼叫了session.commit(前面還有session的建立和open等),
private void doPackageStage(PackageManagerpm, PackageInstaller.SessionParams params) {
finalPackageInstaller packageInstaller = pm.getPackageInstaller();
PackageInstaller.Session session = null;
try {
final StringpackageLocation = mPackageURI.getPath();
final File file = newFile(packageLocation);
final intsessionId = packageInstaller.createSession(params);
final byte[]buffer = new byte[65536];
session =packageInstaller.openSession(sessionId);
finalInputStream in = new FileInputStream(file);
final longsizeBytes = file.length();
finalOutputStream out = session.openWrite("PackageInstaller", 0,sizeBytes);
try {
int c;
while ((c = in.read(buffer)) != -1) {
out.write(buffer, 0, c);
if(sizeBytes > 0) {
final float fraction = ((float) c / (float) sizeBytes);
session.addProgress(fraction);
}
}
session.fsync(out);
} finally {
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(out);
}
// Create aPendingIntent and use it to generate the IntentSender
IntentbroadcastIntent = new Intent(BROADCAST_ACTION);
PendingIntentpendingIntent = PendingIntent.getBroadcast(
InstallAppProgress.this /*context*/,
sessionId,
broadcastIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
session.commit(pendingIntent.getIntentSender());
} catch(IOException e) {
onPackageInstalled(PackageInstaller.STATUS_FAILURE);
} finally {
IoUtils.closeQuietly(session);
}
}
然後,通過binder,在PackageInstallerSession.java裡,呼叫commit,給handler發一個訊息MSG_COMMIT,
final PackageInstallObserverAdapteradapter = new PackageInstallObserverAdapter(mContext,
statusReceiver, sessionId, mIsInstallerDeviceOwner, userId);
mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
然後Handler.CallbackmHandlerCallback再呼叫commitLocked(),這裡就開始呼叫到PMS的安裝介面了,然後就是PMS的流程,相關參考文件都有很詳盡的說明,我就不贅述了。
private void commitLocked() throwsPackageManagerException {
…
// Unpack nativelibraries
extractNativeLibraries(mResolvedStageDir, params.abiOverride);
。。。
mRelinquished =true;
mPm.installStage(mPackageName,stageDir, stageCid, localObserver, params,
installerPackageName, installerUid, user, mCertificates);
}
在8.0上呢,沒有了InstallAppProgress檔案,改用InstallInstalling,但思路差不多,在非同步taskInstallingAsyncTask的方法中呼叫commit,之後的邏輯差不多。
protected voidonPostExecute(PackageInstaller.Session session) {
if (session !=null) {
IntentbroadcastIntent = new Intent(BROADCAST_ACTION);
broadcastIntent.setPackage(
getPackageManager().getPermissionControllerPackageName());
broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
InstallInstalling.this,
mInstallId,
broadcastIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
session.commit(pendingIntent.getIntentSender());
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
} else {
getPackageManager().getPackageInstaller().abandonSession(mSessionId);
if(!isCancelled()) {
launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null);
}
}
}