virtualapp安装应用流程源码分析
1. HomeActivity 为处理的入口
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && data != null) {
List<AppInfoLite> appList = data.getParcelableArrayListExtra(VCommends.EXTRA_APP_INFO_LIST);
if (appList != null) {
for (AppInfoLite info : appList) {
mPresenter.addApp(info);
}
}
}
}
调用了 mPresenter.addApp, 这里还是使用了一个MVP的设计模式,对应的是HomePresenterImpl.java
@Override
public void addApp(AppInfoLite info) {
class AddResult {
private PackageAppData appData;
private int userId;
private boolean justEnableHidden;
}
AddResult addResult = new AddResult();
VUiKit.defer().when(() -> {
InstalledAppInfo installedAppInfo = VirtualCore.get().getInstalledAppInfo(info.packageName, 0);
addResult.justEnableHidden = installedAppInfo != null;
if (addResult.justEnableHidden) {
int[] userIds = installedAppInfo.getInstalledUsers();
int nextUserId = userIds.length;
/*
Input : userIds = {0, 1, 3}
Output: nextUserId = 2
*/
for (int i = 0; i < userIds.length; i++) {
if (userIds[i] != i) {
nextUserId = i;
break;
}
}
addResult.userId = nextUserId;
if (VUserManager.get().getUserInfo(nextUserId) == null) {
// user not exist, create it automatically.
String nextUserName = "Space " + (nextUserId + 1);
VUserInfo newUserInfo = VUserManager.get().createUser(nextUserName, VUserInfo.FLAG_ADMIN);
if (newUserInfo == null) {
throw new IllegalStateException();
}
}
boolean success = VirtualCore.get().installPackageAsUser(nextUserId, info.packageName);
if (!success) {
throw new IllegalStateException();
}
} else {
InstallResult res = mRepo.addVirtualApp(info);
if (!res.isSuccess) {
throw new IllegalStateException();
}
}
}).then((res) -> {
addResult.appData = PackageAppDataStorage.get().acquire(info.packageName);
}).done(res -> {
boolean multipleVersion = addResult.justEnableHidden && addResult.userId != 0;
if (!multipleVersion) {
PackageAppData data = addResult.appData;
data.isLoading = true;
mView.addAppToLauncher(data);
handleOptApp(data, info.packageName, true);
} else {
MultiplePackageAppData data = new MultiplePackageAppData(addResult.appData, addResult.userId);
data.isLoading = true;
mView.addAppToLauncher(data);
handleOptApp(data, info.packageName, false);
}
});
}
这里有一行
InstalledAppInfo installedAppInfo = VirtualCore.get().getInstalledAppInfo(info.packageName, 0);
本质是去访问自定义的VAppManagerService
public InstalledAppInfo getInstalledAppInfo(String packageName, int flags) {
synchronized (PackageCacheManager.class) {
if (packageName != null) {
PackageSetting setting = PackageCacheManager.getSetting(packageName);
if (setting != null) {
return setting.getAppInfo();
}
}
return null;
}
}
这里是从自己定义的包缓存信息中查询是否有安装过这个包,(后面再分析是怎么记录安装信息的)
显然现在还没有安装过这个包,那么会走到else里,即执行InstallResult res = mRepo.addVirtualApp(info);
AppRepository.java
@Override
public InstallResult addVirtualApp(AppInfoLite info) {
int flags = InstallStrategy.COMPARE_VERSION | InstallStrategy.SKIP_DEX_OPT;
if (info.fastOpen) {
flags |= InstallStrategy.DEPEND_SYSTEM_IF_EXIST;
}
return VirtualCore.get().installPackage(info.path, flags);
}
VirtualCore.java
public InstallResult installPackage(String apkPath, int flags) {
try {
return getService().installPackage(apkPath, flags);
} catch (RemoteException e) {
return VirtualRuntime.crash(e);
}
}
那么这里在借助aidl RPC的能力 ,利用VAppManagerService来安装包
VAppManagerService.java(方法有点大,会删除非核心逻辑的代码)
public synchronized InstallResult installPackage(String path, int flags, boolean notify) {
...
// 真实的apk文件路径
File packageFile = new File(path);
if (!packageFile.exists() || !packageFile.isFile()) {
return InstallResult.makeFailure("Package File is not exist.");
}
VPackage pkg = null;
try {
// 解析apk.生成VPackage对象
pkg = PackageParserEx.parsePackage(packageFile);
} catch (Throwable e) {
e.printStackTrace();
}
...
InstallResult res = new InstallResult();
res.packageName = pkg.packageName;
...
// 生成新的安装目录
//地址大概是/data/data/io.virtualapp/virtual/data/app/应用包名/
File appDir = VEnvironment.getDataAppPackageDirectory(pkg.packageName);
// 生成新的so安装目录
File libDir = new File(appDir, "lib");
...
// 判断是不是外部安装,比如sdcard安装
boolean dependSystem = (flags & InstallStrategy.DEPEND_SYSTEM_IF_EXIST) != 0
&& VirtualCore.get().isOutsideInstalled(pkg.packageName);
....
// 把原应用的lib下的数据copy过来
NativeLibraryHelperCompat.copyNativeBinaries(new File(path), libDir);
if (!dependSystem) {
//假设是基于手机已安装的应用安装,那么就是走的这里
File privatePackageFile = new File(appDir, "base.apk");
File parentFolder = privatePackageFile.getParentFile();
if (!parentFolder.exists() && !parentFolder.mkdirs()) {
VLog.w(TAG, "Warning: unable to create folder : " + privatePackageFile.getPath());
} else if (privatePackageFile.exists() && !privatePackageFile.delete()) {
VLog.w(TAG, "Warning: unable to delete file : " + privatePackageFile.getPath());
}
try {
//把原package的数据copy过来
FileUtils.copyFile(packageFile, privatePackageFile);
} catch (IOException e) {
privatePackageFile.delete();
return InstallResult.makeFailure("Unable to copy the package file.");
}
packageFile = privatePackageFile;
}
if (existOne != null) {
PackageCacheManager.remove(pkg.packageName);
}
//修改新路径的权限
chmodPackageDictionary(packageFile);
// 生成新包安装的配置信息,用于持久化和后续查询用
PackageSetting ps;
if (existSetting != null) {
ps = existSetting;
} else {
ps = new PackageSetting();
}
ps.dependSystem = dependSystem;
ps.apkPath = packageFile.getPath();
ps.libPath = libDir.getPath();
ps.packageName = pkg.packageName;
//这里为这个app生成一个appId
ps.appId = VUserHandle.getAppId(mUidSystem.getOrCreateUid(pkg));
if (res.isUpdate) {
ps.lastUpdateTime = installTime;
} else {
ps.firstInstallTime = installTime;
ps.lastUpdateTime = installTime;
for (int userId : VUserManagerService.get().getUserIds()) {
boolean installed = userId == 0;
ps.setUserState(userId, false/*launched*/, false/*hidden*/, installed);
}
}
//在/data/data/io.virtualapp/virtual/data/app/应用包名/下持久化一个 package.ini,用于记录VPackage的信息, 下次读取可以直接用
PackageParserEx.savePackageCache(pkg);
//缓存一下信息
PackageCacheManager.put(pkg, ps);
mPersistenceLayer.save();
if (!dependSystem) {
boolean runDexOpt = false;
if (VirtualRuntime.isArt()) {
try {
ArtDexOptimizer.interpretDex2Oat(ps.apkPath, VEnvironment.getOdexFile(ps.packageName).getPath());
} catch (IOException e) {
e.printStackTrace();
runDexOpt = true;
}
} else {
runDexOpt = true;
}
if (runDexOpt) {
try {
DexFile.loadDex(ps.apkPath, VEnvironment.getOdexFile(ps.packageName).getPath(), 0).close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
BroadcastSystem.get().startApp(pkg);
if (notify) {
notifyAppInstalled(ps, -1);
}
res.isSuccess = true;
return res;
}
可以看到,在VA内部安装虚拟应用,VA主要做了这几件事
- 反射创建android.pm.PackageParser实例,解析虚拟应用apk包的四大组件以及其他信息;
- 把so库复制到对应包的虚拟路径下;
- 保存、持久化部分apk包数据到硬盘内;
- 把apk包复制到对应的虚拟路径下;
virtualapp安装应用流程源码分析的更多相关文章
- [Android]Android系统启动流程源码分析
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5013863.html Android系统启动流程源码分析 首先 ...
- Spark(四十九):Spark On YARN启动流程源码分析(一)
引导: 该篇章主要讲解执行spark-submit.sh提交到将任务提交给Yarn阶段代码分析. spark-submit的入口函数 一般提交一个spark作业的方式采用spark-submit来提交 ...
- [Android]从Launcher开始启动App流程源码分析
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5017056.html 从Launcher开始启动App流程源码 ...
- Spring加载流程源码分析03【refresh】
前面两篇文章分析了super(this)和setConfigLocations(configLocations)的源代码,本文来分析下refresh的源码, Spring加载流程源码分析01[su ...
- Android笔记--View绘制流程源码分析(二)
Android笔记--View绘制流程源码分析二 通过上一篇View绘制流程源码分析一可以知晓整个绘制流程之前,在activity启动过程中: Window的建立(activit.attach生成), ...
- Android笔记--View绘制流程源码分析(一)
Android笔记--View绘制流程源码分析 View绘制之前框架流程分析 View绘制的分析始终是离不开Activity及其内部的Window的.在Activity的源码启动流程中,一并包含 着A ...
- Spark(五十一):Spark On YARN(Yarn-Cluster模式)启动流程源码分析(二)
上篇<Spark(四十九):Spark On YARN启动流程源码分析(一)>我们讲到启动SparkContext初始化,ApplicationMaster启动资源中,讲解的内容明显不完整 ...
- spring boot 加载web容器tomcat流程源码分析
spring boot 加载web容器tomcat流程源码分析 我本地的springboot版本是2.5.1,后面的分析都是基于这个版本 <parent> <groupId>o ...
- springboot 事务创建流程源码分析
springboot 事务创建流程源码分析 目录 springboot 事务创建流程源码分析 1. 自动加载配置 2. InfrastructureAdvisorAutoProxyCreator类 3 ...
- 5.Spark Streaming流计算框架的运行流程源码分析2
1 spark streaming 程序代码实例 代码如下: object OnlineTheTop3ItemForEachCategory2DB { def main(args: Array[Str ...
随机推荐
- 往返回来的数据数组Array中添加一个字段的最优写法
在工作中我们经常会对后端返回来的数据进行添加一个字段: 最优的写法是 直接在 res.data[i].xx=aa 这样的方式去添加: 添加好了之后美酒 可以去赋值了: 让表格去渲染数据 this.$a ...
- 从零开始配置 vim(10)——快捷键配置
之前我们对neovim 进行了基础的配置,这篇主要介绍我比较常用的快捷键配置.到这篇开始我们的配置已经可以为两个大的模块--基础配置和快捷键配置.我们的目录也应该按照模块来进行组织.在正式配置之前让我 ...
- 手撕Vue-实现计算属性
前言 经过上一篇的学习, 完成了将数据代理到了 Nue 的实例上方,这个我们已经撕完了.接下来要实现的是计算属性,计算属性的实现原理是通过 Object.defineProperty() 来实现的,我 ...
- 微信小程序-双线程渲染模型
微信小程序双线程渲染模型 小程序的运行环境分成渲染层和逻辑层: WXML 模板和 WXSS 样式工作在渲染层,通过 WebView 进行渲染 小程序会为每一个界面都创建一个 WebView 来渲染这个 ...
- 解决Edge浏览器提示“此网站已被人举报不安全”
今天下午微软旗下的 Microsoft Edge 浏览器将百度搜索的跳转域名 (*.baidu.com/link?url=*) 封杀,百度搜索首页可以打开,但搜索任何关键词点击搜索结果都会被拦截. 当 ...
- 20.8 OpenSSL 套接字SSL传输文件
有了上面的基础那么传输文件的实现就变得简单了,在传输时通常我们需要打开文件,并每次读入1024个字节的数据包,通过SSL加密传输即可,此处的文件传输功能在原生套接字章节中也进行过详细讲解,此处我们还是 ...
- app api 登录 流程 accessToken refreshToken 图解 过程分析
随着app 增长 带动了 api 的增长,有了api 总是离不开登录,怎么实现简单安全的登录过程至关重要. 不让用户每次超时都输入密码,不在客户端保存账号密码 ,用户体验与安全至关重要. 没有绝对的安 ...
- UIWindow的概念与使用
UIWindow的作用 UIWindow是UIView的子类用于显示程序内容.每一个UIView想要将内容显示到屏幕上都需要依赖于一个UIWindow. iOS应用程序要想正常运行至少要有一个UIWi ...
- iOS视频播放常用重点知识
iOS视频播放常见的重要知识点如下: 视频格式:iOS支持的视频格式主要有H.264.MPEG-4.H.263.Sorenson等.它们根据不同的应用场景进行使用. 视频编解码:视频编解码技术是视频播 ...
- 【算法】基于hoare快速排序的三种思想和非递归,基准值选取优化【快速排序的深度剖析-超级详细的注释和解释】你真的完全学会快速排序了吗?
文章目录 前言 什么是快速排序 快速排序的递归实现 快速排序的非递归实现 单趟排序详解 hoare思想 挖坑法 前后指针法 快速排序的优化 三数取中 小区间优化 快速排序整体代码 尾声 前言 先赞后看 ...