1. 简介

    为了能把应用程序安装到SD卡和TF卡上,Android系统默认是不支持的,它只有一个asec mount点: /mnt/secure/asec,在我的系统中,此mount点由/mnt/sdcard给占用了,所以TF卡就支持不了。为了解决此问题,除了把代码读明白之外,无其它的办法。为了方便理解下面的描述,先看下Vold(管理外设热插拔)的系统框架图:

android 开发 下载文件到内存卡 android安装应用到sd卡_android

关于相关类图,引用其他仁兄的图:

android 开发 下载文件到内存卡 android安装应用到sd卡_应用程序_02

2. 从U盘安装应用程序流程

PackageInstallerActivity.java::initiateInstall->
                              ::startInstallConfirm->
                    
 InstallAppProgress::onCreate->
 InstallAppProgress::initView->

 PackageManager::installPackage->

 PackageManagerService::installPackage->
  installPackageWithVerification->
        mHandler.sendMessage(msg)->

 PackageHandler::handleMessage->
   PackageHandler::doHandleMessage->
   HandlerParams::startCopy->
      InstallParams::handleStartCopy-> (根据installLocation确认安装位置,只有针对/data分区有lowThreshold)
        SdInstallArgs::copyApk-> (关键的地方,下面详解)
      InstallParams::handleReturnCode->
        PackageManagerService::processPendingInstall->
          PackageManagerService::installPackageLI-> //args, res分析
            PackageManagerService::installNewPackageLI->  // or replacePackageLI
              PackageManagerService::scanPackageLI-> (KEY)->parsePackage(解析AndroidManifest.xml)
                Installer::install (mInstaller.install)-> (Socket)--->
                installd(真正做事的地方)2.1 SdInstallArgs::copyApk
SdInstallArgs::copyApk->
 DefaultContainerService::copyResourceInner->
 PackageHelper::createSdDir->
 MountService::createSecureContainer->       (cmd = String.format(new Locale("en", "Us"),
       "asec create %s %d %s %s %d", id, sizeMb, fstype, key, ownerUid);)
 NativeDaemonConnector::doCommandLocked->(向"vold" socket发送命令给vold,并等待结果)
 NativeDaemonConnector::sendCommandLocked-> (Socket)

 FrameworkListener::onDataAvailable-> (receive message)  "buffer=asec list"
                                   或"buffer=asec create smdl2tmp1 19 fat caf791d0426d682e5b2aafc5439d55a9 10023"
 FrameworkListener::dispatchCommand->
 CommandListener::AsecCmd::runCommand-> (所有与Volume::SEC_ASECDIR相关的代码都需要修改)
 VolumeManager::createAsec (决定安装文件的路径Volume::SEC_ASECDIR)2.2 复杂的mount关系
    详细代码参见Volume.cpp::
/mnt/secure/asectf,并执行mount即可。
• /mnt/secure: 下的mount点为物理外设的mount点
•/mnt/asec: 为公共挂载点,它是一个tmpfs文件系统的挂载点,安装到SD或TF上的应用程序都可以被mount到此目录下的mount点
   复杂的mount关系如下所示:
mount to /mnt/secure/staging
    2) /mnt/secure/staging/.android_secure mount to /mnt/secure/asec (MS_BIND)
    3) tmpfs  mount to /mnt/secure/staging/.android_secure (只读0字节tmpfs,把它隐藏起来, obscuring the asec image directory from non root users)
    4) 把/mnt/secure/staging下的子目录(.android_secure)移动到/mnt/sdcard下

   注:MS_BIND: 执行bind挂载,使文件或者子目录树在文件系统内的另一个点上可视。

   搞这么复杂的目的只有一个:只有root用户才能查看/mnt/secure/asec下的文件,当然把TF卡取出来,在Widows上可以看到.android_secure目录下的.asec文件。

2.3 把APK安装到SD卡或TF卡上留下的东东

      当把APK安装到SD卡或TF卡上时,将在以下地方留下它的东东:

1) /mnt/secure/asec: (安装之后产生的.asec文件,它才是真正的内容)
   -rwxrwxr-x system   sdcard_rw 20257280 2013-04-07 06:14 com.myarrow.test-1.asec


2) /mnt/asec/com.myarrow.test-1: (是被mount上去的,它是一个tmpfs,并不真正占用flash空间)
   dr-xr-xr-x system   root              2013-04-07 06:14 lib
   -r-xr-xr-x system   root      4973096 2013-04-07 06:14 pkg.apk


3) /data/data/com.myarrow.test: (只是一个链接,不真正占用flash空间)
   lrwxrwxrwx system   system            2013-04-09 06:51 lib -> /mnt/asec/com.myarrow.test-1/lib


4) /data/dalvik-cache: (包含apk包中的dex文件,它占用data分区的flash空间, 所以data分区必须预留空间)
   -rw-r--r-- system   app_58      36608 2013-04-07 06:14 mnt@asec@com.myarrow.test-1@pkg.apk@classes.dex

3. 需要修改的主要文件

    1) Volume.cpp

        修改其中的mountVol/unmountVol,把SD或TF卡也执行bindmount

    2) DefaultContainerService.java 

        修改isUnderExternalThreshold,当空间不足时,看看SD或TF卡上是否有足够的空间

    3) VolumeManager.cpp 

Volume::SEC_SD_ASECDIR

4. Mount和Unmoun asec t流程



1) Mount:
MountService.java (notifyVolumeStateChange/onEvent)->
 MountService.jvav (updatePublicVolumeState) ->
 PackageManagerService.java (updateExternalMediaStatus) ->
                           (updateExternalMediaStatusInner) ->
                            (loadMediaPackages) -> 
                            SdInstallArgs.doPreInstall->
 PackageHelper.java (mountSdDir) ->
 MountService.java(mountSecureContainer)->
 CommandListener.cpp(CommandListener::AsecCmd::runCommand) 
2) Unmount:MountService.java (notifyVolumeStateChange/onEvent)->
 MountService.jvav (updatePublicVolumeState) ->
 PackageManagerService.java (updateExternalMediaStatus) ->
                           (updateExternalMediaStatusInner) ->
                            (unloadMediaPackages) ->(send UPDATED_MEDIA_STATUS)
                            PackageHandler.doHandleMessage (get UPDATED_MEDIA_STATUS)->
                            unloadAllContainers ->
                            (SdInstallArgs.doPostDeleteLI)->PackageHelper.java (unMountSdDir) ->
 MountService.java (unmountSecureContainer)->
 CommandListener.cpp(CommandListener::AsecCmd::runCommand)

5.  copyResourceInner

其详细代码如下所示:

private String copyResourceInner(Uri packageURI, String newCid, String key, String resFileName) {
        // Make sure the sdcard is mounted.
        String status = Environment.getExternalStorageState();
        if (!status.equals(Environment.MEDIA_MOUNTED)) {
            Slog.w(TAG, "Make sure sdcard is mounted.");
            return null;
        }

        // The .apk file
        String codePath = packageURI.getPath();
        File codeFile = new File(codePath);

        // Calculate size of container needed to hold base APK.
        int sizeMb;
        try {
            sizeMb = calculateContainerSize(codeFile);
        } catch (FileNotFoundException e) {
            Slog.w(TAG, "File does not exist when trying to copy " + codeFile.getPath());
            return null;
        }

        // Create new container
        final String newCachePath;
        if ((newCachePath = PackageHelper.createSdDir(sizeMb, newCid, key, Process.myUid())) == null) {
            Slog.e(TAG, "Failed to create container " + newCid);
            return null;
        }

        /*if (localLOGV)*/ {
            Slog.i(TAG, "Created container for " + newCid + " at path : " + newCachePath);
        }

        final File resFile = new File(newCachePath, resFileName);
        if (FileUtils.copyFile(new File(codePath), resFile)) {
            /*if (localLOGV)*/ {
                Slog.i(TAG, "Copied " + codePath + " to " + resFile);
            }
        } else {
            Slog.e(TAG, "Failed to copy " + codePath + " to " + resFile);
            // Clean up container
            PackageHelper.destroySdDir(newCid);
            return null;
        }

        final File sharedLibraryDir = new File(newCachePath, LIB_DIR_NAME);
        if (sharedLibraryDir.mkdir()) {
            int ret = NativeLibraryHelper.copyNativeBinariesIfNeededLI(codeFile, sharedLibraryDir);
            if (ret != PackageManager.INSTALL_SUCCEEDED) {
                Slog.e(TAG, "Could not copy native libraries to " + sharedLibraryDir.getPath());
                PackageHelper.destroySdDir(newCid);
                return null;
            }
        } else {
            Slog.e(TAG, "Could not create native lib directory: " + sharedLibraryDir.getPath());
            PackageHelper.destroySdDir(newCid);
            return null;
        }

        if (!PackageHelper.finalizeSdDir(newCid)) {
            Slog.e(TAG, "Failed to finalize " + newCid + " at path " + newCachePath);
            // Clean up container
            PackageHelper.destroySdDir(newCid);
            return null;
        }

        if (localLOGV) {
            Slog.i(TAG, "Finalized container " + newCid);
        }

        if (PackageHelper.isContainerMounted(newCid)) {
            if (localLOGV) {
                Slog.i(TAG, "Unmounting " + newCid + " at path " + newCachePath);
            }

            // Force a gc to avoid being killed.
            Runtime.getRuntime().gc();
            PackageHelper.unMountSdDir(newCid);
        } else {
            if (localLOGV) {
                Slog.i(TAG, "Container " + newCid + " not mounted");
            }
        }

        return newCachePath;
    }

其主要功能为:

1)创建.asec文件

2)copy apk

3)copy lib

4)final .asec文件

其详细log信息如下:

D/VoldVolumeManager(   85):  createAsec(367) call createImageFile   
E/Vold    (   85): createImageFile(236) call creat                  
E/Vold    (   85): createImageFile(241) call ftruncate              
E/Vold    (   85): createImageFile(247) END                         
D/VoldVolumeManager(   85): createAsec(374) call asecHash           
D/VoldVolumeManager(   85): createAsec(383) call Loop::create       
D/VoldVolumeManager(   85): createAsec(397) call Devmapper::create  
D/VoldVolumeManager(   85): createAsec(414) call open               
D/VoldVolumeManager(   85): createAsec(436) write sb                
D/VoldVolumeManager(   85): createAsec(453) Fat::format             
D/VoldVolumeManager(   85): createAsec(477) Fat::doMount            
D/VoldVolumeManager(   85): createAsec(491) End                     
                                                                    
****copy apk and lib*****                                           
                                                                    
D/VoldVolumeManager(   85): finalizeAsec(502) start                 
D/VoldVolumeManager(   85): finalizeAsec(520) End                   
D/VoldVolumeManager(   85): renameAsec:538 Volume::SEC_ASECDIR start
D/VoldVolumeManager(   85): renameAsec:538 Volume::SEC_ASECDIR start