您的位置:首页 > 其它

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

2013-04-09 17:08 513 查看

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: