Android4.4
問題:安裝“全民飛機大戰”、“天天炫鬥”等一些apk到SD卡時,安裝失敗。
log:
01-12 15:49:28.829 W/zipro ( 2710): write failed in inflate (28672 vs 32768)
01-12 15:49:28.829 E/DefContainer( 2710): Could not copy native libraries to /mnt/asec/smdl2tmp1/lib
01-12 15:49:34.309 D/InstallAppProgress( 5068): Installation error code: -18
問題:安裝“全民飛機大戰”、“天天炫鬥”等一些apk到SD卡時,安裝失敗。
log:
01-12 15:49:28.829 W/zipro ( 2710): write failed in inflate (28672 vs 32768)
01-12 15:49:28.829 E/DefContainer( 2710): Could not copy native libraries to /mnt/asec/smdl2tmp1/lib
01-12 15:49:34.309 D/InstallAppProgress( 5068): Installation error code: -18
相關代碼:
ZipFileRO.cpp
ZipFileRO::inflateBuffer()
long writeSize = zstream.next_out - writeBuf;
int cc = TEMP_FAILURE_RETRY(write(fd, writeBuf, writeSize));
if (cc < 0) {
ALOGW("write failed in inflate: %s", strerror(errno));
goto z_bail;
} else if (cc != (int) writeSize) {
ALOGW("write failed in inflate (%d vs %ld)", cc, writeSize);
goto z_bail;
}
PackageManager.java
// ------ Errors related to sdcard
/**
* Installation return code: this is passed to the {@link IPackageInstallObserver} by
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
* a secure container mount point couldn't be accessed on external media.
* @hide
*/
public static final int INSTALL_FAILED_CONTAINER_ERROR = -18;
分析:copy native libraries 的過程中出現了問題。
下面簡單分析一下安裝apk到SD卡的流程。
(本文介紹從文件管理器安裝apk到SD卡的流程,忽略了一些異常處理,如空間不足的判斷等)
1、從文件管理器點擊apk進行安裝。
FolderFragment.openFile(File f)
startActivity(intent);
D/wzf ( 8501): @@@@ fileUri = file:///storage/sdcard1/Downloads/Apps/quanminfeijidazhan.apk
D/wzf ( 8501): @@@@ intent = Intent { act=android.intent.action.VIEW (has extras) }
D/wzf ( 8501): @@@@ type = application/vnd.android.package-archive
2、啓動的是PackageInstallerActivityPackageInstallerActivity.java
protected void onCreate(Bundle icicle) {
//...
if ("package".equals(mPackageURI.getScheme())) {
//...
} else {
mInstallFlowAnalytics.setFileUri(true);
final File sourceFile = new File(mPackageURI.getPath());
PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);//主要是解析AndroidManifest.xml
mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
PackageManager.GET_PERMISSIONS, 0, 0, null,
new PackageUserState());
}
//...
initiateInstall();
}
3、選擇安裝到SD卡,點擊安裝
PackageInstallerActivity.java
public void onClick(View v) {
if(v == mOk) {
if (mOkCanInstall || mScrollView == null) {//安裝
Intent newIntent = new Intent();
newIntent.setClass(this, InstallAppProgress.class);
//...newIntent.putExtra(...);添加各種信息
startActivity(newIntent);
finish();
} else {//下一步
mScrollView.pageScroll(View.FOCUS_DOWN);
}
} else if(v == mCancel) {
setResult(RESULT_CANCELED);
mInstallFlowAnalytics.setFlowFinished(InstallFlowAnalytics.RESULT_CANCELLED_BY_USER);
finish();
}
}
InstallAppProgress.java
public void initView() {
//初始化界面
PackageInstallObserver observer = new PackageInstallObserver();//監聽安裝結果
if ("package".equals(mPackageURI.getScheme())) {
//...
} else {
//通過PackageManager調用PackageManagerService服務進行安裝
pm.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags,
installerPackageName, verificationParams, null);
}
}
4、安裝
PackageManagerService.java
public void installPackageWithVerificationAndEncryption(Uri packageURI,
IPackageInstallObserver observer, int flags, String installerPackageName,
VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
//...判斷是不是adb install,是否允許未知來源,安裝位置判斷
final Message msg = mHandler.obtainMessage(INIT_COPY);
msg.obj = new InstallParams(packageURI, observer, userFilteredFlags, installerPackageName,
verificationParams, encryptionParams, user);
mHandler.sendMessage(msg);
}
INIT_COPY 做的事情是如果已經連接了media container service,就直接mPendingInstalls.add(idx, params),併發送消息mHandler.sendEmptyMessage(MCS_BOUND)。
如果沒有連接服務,就調用connectToService(),建立連接後,也會發送MCS_BOUND消息。
這裏的media container service是"com.android.defcontainer.DefaultContainerService"。
PackageManagerService.java
private boolean connectToService() {
Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
if (mContext.bindServiceAsUser(service, mDefContainerConn,
Context.BIND_AUTO_CREATE, UserHandle.OWNER)) {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mBound = true;
return true;
}
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
return false;
}
PackageManagerService.java
final private DefaultContainerConnection mDefContainerConn =
new DefaultContainerConnection();
class DefaultContainerConnection implements ServiceConnection {
public void onServiceConnected(ComponentName name, IBinder service) {
IMediaContainerService imcs =
IMediaContainerService.Stub.asInterface(service);//DefaultContainerService.mBinder
mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs));
}
public void onServiceDisconnected(ComponentName name) { }
};
MCS_BOUND 做的事情是取出INIT_COPY add的params,並執行其startCopy()。直到mPendingInstalls.size() == 0,發送MCS_UNBIND消息斷開服務連接。
正常情況下,startCopy()調用了handleStartCopy()和handleReturnCode()。本文的安裝過程是調用的子類InstallParams的。
1) InstallParams.handleStartCopy()
//本函數只保留了部分關鍵代碼
public void handleStartCopy() throws RemoteException {
int ret = PackageManager.INSTALL_SUCCEEDED;
final InstallArgs args = createInstallArgs(this);//onsd --> AsecInstallArgs
ret = args.copyApk(mContainerService, true);
mRet = ret;
}
PackageManagerService.AsecInstallArgs.copyApk()
DefaultContainerService.mBinder.copyResourceToContainer()
DefaultContainerService.copyResourceInner()
DefaultContainerService.calculateContainerSize()
PackageHelper.createSdDir()
FileUtils.copyFile()
Libcore.os.chmod(resFile.getAbsolutePath(), 0640);
if (isForwardLocked) {
PackageHelper.extractPublicFiles(resFile.getAbsolutePath(), publicZipFile);
Libcore.os.chmod(publicZipFile.getAbsolutePath(), 0644);
}
NativeLibraryHelper.copyNativeBinariesIfNeededLI(codeFile, sharedLibraryDir);
com_android_internal_content_NativeLibraryHelper.copyFileIfChanged()
ZipFileRO::uncompressEntry()
ZipFileRO::inflateBuffer()
ALOGW("write failed in inflate (%d vs %ld)", cc, writeSize);//出錯啦
if (PackageHelper.isContainerMounted(newCid)) {
// Force a gc to avoid being killed.
Runtime.getRuntime().gc();
PackageHelper.unMountSdDir(newCid);
}
copyApk()會計算需要的container size,創建一塊ext4格式的區域來安裝apk。然後將apk文件、必要的資源和本地庫文件copy過去。
本文開頭的問題就是因爲分配的container size不夠,導致copy庫文件的時候,空間不夠用而寫操作失敗。
計算container大小是在DefaultContainerService.calculateContainerSize()裏。
這裏只是將需要copy的文件大小相加並向上取整MB,然後又加了1MB。
DefaultContainerService.java
/*
* Add buffer size because we don't have a good way to determine the
* real FAT size. Your FAT size varies with how many directory entries
* you need, how big the whole filesystem is, and other such headaches.
*/
sizeMb++;
有些apk的文件結構相對複雜,需要的額外空間較大。這裏將container大小再加1MB,就解決了上面的問題。
2)
InstallParams.handleReturnCode()
(這部分內容後續再研究)