现在的位置: 首页 > 综合 > 正文

把APK安装到SD卡和TF卡实现方案

2013年08月30日 ⁄ 综合 ⁄ 共 7525字 ⁄ 字号 评论关闭

1. 简介

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

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


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::

     代码默认不把sdcard && udisk mount为asec,为此需要把SD卡或TF卡mount到asec挂载点,当然系统默认只有一个/mnt/secure/asec,你可以在init.rc中创建一个/mnt/secure/asecsd或/mnt/secure/asectf,并执行mount即可。

• /mnt/secure: 下的mount点为物理外设的mount点

•/mnt/asec: 为公共挂载点,它是一个tmpfs文件系统的挂载点,安装到SD或TF上的应用程序都可以被mount到此目录下的mount点

   复杂的mount关系如下所示:

   1) /dev/block/vold/31:9    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_ASECDIR相关的地方,因为我们增加了一个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

 

 

抱歉!评论已关闭.