为了能把应用程序安装到SD卡和TF卡上,Android系统默认是不支持的,它只有一个asec mount点: /mnt/secure/asec,在我的系统中,此mount点由/mnt/sdcard给占用了,所以TF卡就支持不了。为了解决此问题,除了把代码读明白之外,无其它的办法。为了方便理解下面的描述,先看下Vold(管理外设热插拔)的系统框架图:
关于相关类图,引用其他仁兄的图:
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(真正做事的地方)
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)
详细代码参见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文件。
当把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@[email protected]
1) Volume.cpp
修改其中的mountVol/unmountVol,把SD或TF卡也执行bindmount
2) DefaultContainerService.java
修改isUnderExternalThreshold,当空间不足时,看看SD或TF卡上是否有足够的空间
3) VolumeManager.cpp
扩展与Volume::SEC_ASECDIR相关的地方,因为我们增加了一个Volume::SEC_SD_ASECDIR
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)
其详细代码如下所示:
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