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、启动的是PackageInstallerActivity
PackageInstallerActivity.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()
(这部分内容后续再研究)