安装apk到SD卡失败

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

相关代码:

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()

(这部分内容后续再研究)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值