virtualapp启动流程源码分析
virtualapp启动流程分析
1. 首先是启动本身,执行Vpp 的attachBaseContext
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
mPreferences = base.getSharedPreferences("va", Context.MODE_MULTI_PROCESS);
VASettings.ENABLE_IO_REDIRECT = true;
VASettings.ENABLE_INNER_SHORTCUT = false;
try {
VirtualCore.get().startup(base);
} catch (Throwable e) {
e.printStackTrace();
}
}
在这里执行了VirtualCore.get().startup(base);
public void startup(Context context) throws Throwable {
if (!isStartUp) {
......
detectProcessType();
InvocationStubManager invocationStubManager = InvocationStubManager.getInstance();
invocationStubManager.init();
invocationStubManager.injectAll();
ContextFixer.fixContext(context);
isStartUp = true;
......
}
}
这里最主要的是实例化了invocationStubManager ,并开启注入(injectAll),
public void init() throws Throwable {
if (isInit()) {
throw new IllegalStateException("InvocationStubManager Has been initialized.");
}
injectInternal();
sInit = true;
}
private void injectInternal() throws Throwable {
if (VirtualCore.get().isMainProcess()) {
return;
}
......
}
由于当前我们是主进程,所以这里注入是啥也没干
2. 然后进入APP 的主Activity(SplashActivity
)
protected void onCreate(Bundle savedInstanceState) {
@SuppressWarnings("unused")
boolean enterGuide = !Once.beenDone(Once.THIS_APP_INSTALL, VCommends.TAG_NEW_VERSION);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
VUiKit.defer().when(() -> {
if (!Once.beenDone("collect_flurry")) {
FlurryROMCollector.startCollect();
Once.markDone("collect_flurry");
}
long time = System.currentTimeMillis();
doActionInThread();
time = System.currentTimeMillis() - time;
long delta = 3000L - time;
if (delta > 0) {
VUiKit.sleep(delta);
}
}).done((res) -> {
HomeActivity.goHome(this);
finish();
});
}
private void doActionInThread() {
if (!VirtualCore.get().isEngineLaunched()) {
VirtualCore.get().waitForEngine();
}
}
在这里异步执行了doActionInThread,也就是执行了waitForEngine
,这一步很重要,在这里会开启另一个进程
public void waitForEngine() {
ServiceManagerNative.ensureServerStarted();
}
ServiceManagerNative.java
public static String SERVICE_CP_AUTH = "virtual.service.BinderProvider";
......
public static void ensureServerStarted() {
new ProviderCall.Builder(VirtualCore.get().getContext(), SERVICE_CP_AUTH).methodName("ensure_created").call();
}
ProviderCall.java
public static Bundle call(String authority, Context context, String method, String arg, Bundle bundle) {
Uri uri = Uri.parse("content://" + authority);
return ContentProviderCompat.call(context, uri, method, arg, bundle);
}
最终会构造出一个uri: virtual.service.BinderProvider
, 并启动这个Provider
ContentProviderCompat.java
public static Bundle call(Context context, Uri uri, String method, String arg, Bundle extras) {
if (VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
return context.getContentResolver().call(uri, method, arg, extras);
}
ContentProviderClient client = crazyAcquireContentProvider(context, uri);
Bundle res = null;
try {
res = client.call(method, arg, extras);
} catch (RemoteException e) {
e.printStackTrace();
} finally {
releaseQuietly(client);
}
return res;
}
......
private static ContentProviderClient acquireContentProviderClient(Context context, Uri uri) {
if (VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
return context.getContentResolver().acquireUnstableContentProviderClient(uri);
}
return context.getContentResolver().acquireContentProviderClient(uri);
}
也就是最终使用context.getContentResolver().acquireContentProviderClient
获得一个client引用,并执行他的client.call(method, arg, extras);
,经上面分析method的值为ensure_created
,
这里的目的就是为了唤醒这个BinderProvider
查看它在AndroidManifest.xml
中的配置
<provider
android:name="com.lody.virtual.server.BinderProvider"
android:authorities="${applicationId}.virtual.service.BinderProvider"
android:exported="false"
android:process="@string/engine_process_name" />
<string name="engine_process_name">:x</string>
那么它是存在另一个进程中的,那么根据命名规则,该进程名会是io.virtualapp:x
也就是说现在有两个进程了
主进程
io.virtualapp
服务进程
io.virtualapp:x
2. 服务进程启动流程
由于provider的启动,那么流程又会走到VApp中,在这个进程中走attachBaseContext
的VirtualCore.get().startup(base);
然后同样的走
InvocationStubManager invocationStubManager = InvocationStubManager.getInstance();
invocationStubManager.init();
invocationStubManager.injectAll();
InvocationStubManager.java
private void injectInternal() throws Throwable {
....
if (VirtualCore.get().isServerProcess()) {
addInjector(new ActivityManagerStub());
addInjector(new PackageManagerStub());
return;
....
}
void injectAll() throws Throwable {
for (IInjector injector : mInjectors.values()) {
injector.inject();
}
// XXX: Lazy inject the Instrumentation,
addInjector(AppInstrumentation.getDefault());
}
在这里会hook掉ActivityManager 和PackageManager
先查看PackageManager 的hooker PackageManagerStub
@Inject(MethodProxies.class)
public final class PackageManagerStub extends MethodInvocationProxy<MethodInvocationStub<IInterface>> {
public PackageManagerStub() {
super(new MethodInvocationStub<>(ActivityThread.sPackageManager.get()));
}
@Override
protected void onBindMethods() {
super.onBindMethods();
addMethodProxy(new ResultStaticMethodProxy("addPermissionAsync", true));
addMethodProxy(new ResultStaticMethodProxy("addPermission", true));
addMethodProxy(new ResultStaticMethodProxy("performDexOpt", true));
addMethodProxy(new ResultStaticMethodProxy("performDexOptIfNeeded", false));
addMethodProxy(new ResultStaticMethodProxy("performDexOptSecondary", true));
addMethodProxy(new ResultStaticMethodProxy("addOnPermissionsChangeListener", 0));
addMethodProxy(new ResultStaticMethodProxy("removeOnPermissionsChangeListener", 0));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
addMethodProxy(new ResultStaticMethodProxy("checkPackageStartable", 0));
}
if (BuildCompat.isOreo()) {
addMethodProxy(new ResultStaticMethodProxy("notifyDexLoad", 0));
addMethodProxy(new ResultStaticMethodProxy("notifyPackageUse", 0));
addMethodProxy(new ResultStaticMethodProxy("setInstantAppCookie", false));
addMethodProxy(new ResultStaticMethodProxy("isInstantApp", false));
}
}
@Override
public void inject() throws Throwable {
final IInterface hookedPM = getInvocationStub().getProxyInterface();
ActivityThread.sPackageManager.set(hookedPM);
BinderInvocationStub pmHookBinder = new BinderInvocationStub(getInvocationStub().getBaseInterface());
pmHookBinder.copyMethodProxies(getInvocationStub());
pmHookBinder.replaceService("package");
}
@Override
public boolean isEnvBad() {
return getInvocationStub().getProxyInterface() != ActivityThread.sPackageManager.get();
}
}
这里就是创建了一个PackageManager 的一个代理, 通过Java动态代理的方式,并通过反射写回到ActivityThread.sPackageManager
中,实现这个对象的代理,走到我们自己对象中
然后走到BinderProvider的onCreate中
@Override
public boolean onCreate() {
Context context = getContext();
DaemonService.startup(context);
if (!VirtualCore.get().isStartup()) {
return true;
}
VPackageManagerService.systemReady();
IPCBus.register(IPackageManager.class, VPackageManagerService.get());
VActivityManagerService.systemReady(context);
IPCBus.register(IActivityManager.class, VActivityManagerService.get());
IPCBus.register(IUserManager.class, VUserManagerService.get());
VAppManagerService.systemReady();
IPCBus.register(IAppManager.class, VAppManagerService.get());
BroadcastSystem.attach(VActivityManagerService.get(), VAppManagerService.get());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
IPCBus.register(IJobService.class, VJobSchedulerService.get());
}
VNotificationManagerService.systemReady(context);
IPCBus.register(INotificationManager.class, VNotificationManagerService.get());
VAppManagerService.get().scanApps();
VAccountManagerService.systemReady();
IPCBus.register(IAccountManager.class, VAccountManagerService.get());
IPCBus.register(IVirtualStorageService.class, VirtualStorageService.get());
IPCBus.register(IDeviceInfoManager.class, VDeviceManagerService.get());
IPCBus.register(IVirtualLocationManager.class, VirtualLocationService.get());
return true;
}
BinderProvideronCreate方法,在其中启动后台的DaemonService,保持:x进程不被系统杀死。然后初始化各个模拟的系统服务,如VPackageManagerService、VUserManagerService、VActivityManagerService、VAppManagerService、VNotificationManagerService、VAccountManagerService等,并注册到ServiceCache中
最终通过 IServiceFetcher.aidl 实现rpc通信,实现客户端进程来服务端访问数据
IServiceFetcher.Stub 的实现在BinderProvider.java 中
private class ServiceFetcher extends IServiceFetcher.Stub {
@Override
public IBinder getService(String name) throws RemoteException {
if (name != null) {
return ServiceCache.getService(name);
}
return null;
}
@Override
public void addService(String name, IBinder service) throws RemoteException {
if (name != null && service != null) {
ServiceCache.addService(name, service);
}
}
@Override
public void removeService(String name) throws RemoteException {
if (name != null) {
ServiceCache.removeService(name);
}
}
}
从这里可以看出, 当调用getService 时,实际时从ServiceCache中访问之前注册的service
那么客户端如何获得Fetcher呢
BinderProvider.java
@Override
public Bundle call(String method, String arg, Bundle extras) {
if ("@".equals(method)) {
Bundle bundle = new Bundle();
BundleCompat.putBinder(bundle, "_VA_|_binder_", mServiceFetcher);
return bundle;
}
if ("register".equals(method)) {
}
return null;
}
当客户段通过BinderProvider 并调用方法名为"@"时,会返回mServiceFetcher,并通过
sFetcher = IServiceFetcher.Stub.asInterface(binder);
创建客户端的interface,具体可以参考
ServiceManagerNative.java 的以下方法
private static IServiceFetcher getServiceFetcher() {
if (sFetcher == null || !sFetcher.asBinder().isBinderAlive()) {
synchronized (ServiceManagerNative.class) {
Context context = VirtualCore.get().getContext();
Bundle response = new ProviderCall.Builder(context, SERVICE_CP_AUTH).methodName("@").call();
if (response != null) {
IBinder binder = BundleCompat.getBinder(response, "_VA_|_binder_");
linkBinderDied(binder);
sFetcher = IServiceFetcher.Stub.asInterface(binder);
}
}
}
return sFetcher;
}
客户段通过这个Fetcher来访问服务端的各个服务,而各个服务都是封装成了TransformBinder 对象,TransformBinder 继承于Binder, 可通过RPC进行通信
至此virtual app 启动完毕
virtualapp启动流程源码分析的更多相关文章
- Spark(五十一):Spark On YARN(Yarn-Cluster模式)启动流程源码分析(二)
上篇<Spark(四十九):Spark On YARN启动流程源码分析(一)>我们讲到启动SparkContext初始化,ApplicationMaster启动资源中,讲解的内容明显不完整 ...
- Spark(四十九):Spark On YARN启动流程源码分析(一)
引导: 该篇章主要讲解执行spark-submit.sh提交到将任务提交给Yarn阶段代码分析. spark-submit的入口函数 一般提交一个spark作业的方式采用spark-submit来提交 ...
- springboot的启动流程源码分析
.测试项目,随便一个简单的springboot项目即可: 直接debug调试: 可见,分2步,第一步是创建SpringApplication对象,第二步是调用run方法: 1.SpringApplic ...
- Spring Boot的自动配置原理及启动流程源码分析
概述 Spring Boot 应用目前应该是 Java 中用得最多的框架了吧.其中 Spring Boot 最具特点之一就是自动配置,基于Spring Boot 的自动配置,我们可以很快集成某个模块, ...
- SpringBoot启动流程源码分析
前言 SpringBoot项目的启动流程是很多面试官面试中高级Java程序员喜欢问的问题.这个问题的答案涉及到了SpringBoot工程中的源码,也许我们之前看过别的大牛写过的有关SpringBoot ...
- SpringBoot 源码解析 (二)----- Spring Boot精髓:启动流程源码分析
本文从源代码的角度来看看Spring Boot的启动过程到底是怎么样的,为何以往纷繁复杂的配置到如今可以这么简便. 入口类 @SpringBootApplication public class He ...
- Spark On YARN启动流程源码分析(一)
本文主要参考: a. https://www.cnblogs.com/yy3b2007com/p/10934090.html 0. 说明 a. 关于spark源码会不定期的更新与补充 b. 对于spa ...
- 面试必备:Android Activity启动流程源码分析
最近大致分析了一把 Activity 启动的流程,趁着今天精神状态好,把之前记录的写成文章. 开门见山,我们直接点进去看 Activity 的 startActivity , 最终,我们都会走到 st ...
- SpringBoot一站式启动流程源码分析
一.前言 由上篇文章我们得知,SpringBoot启动时,就是有很简单的一行代码.那我们可以很清楚的看到这行代码的主角便是SpringApplication了,本文我们就来聊一聊这货,来探寻Sprin ...
- Dubbo学习笔记10:Dubbo服务消费方启动流程源码分析
同理我们看下服务消费端启动流程时序图: 在<Dubbo整体架构分析>一文中,我们提到服务消费方需要使用ReferenceConfig API来消费服务,具体是调用代码(1)get()方法来 ...
随机推荐
- 【转帖】nginx变量使用方法详解-7
https://www.diewufeiyang.com/post/581.html 在 (一) 中我们提到过,Nginx 变量的值只有一种类型,那就是字符串,但是变量也有可能压根就不存在有意义的 ...
- 【转帖】8.JVM双亲委派机制(面试常问)
目录 1.什么是双亲委派机制? 2.双亲委派机制的优势 3.沙箱安全机制 1.什么是双亲委派机制? 双亲委派机制工作原理:(面试) 1.如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这 ...
- 【转帖】route命令详解大全(route命令使用实例)
https://www.cxdtop.cn/n/225987.html 在实际的网络应用中,我们可能会遇到这样的网络环境,上外网我们使用的无线网络,内网我们使用的是有限网卡.在设置完成后会出现外网和内 ...
- [转帖]jmeter压力测试
使用jmeter 进行并发压力测试. 首先需要安装好jmeter,下面以widows操作平台为例: 1.确保电脑安装并配置好java环境:具体怎么下载和配置请自行百度: 2.登录jmeter官网htt ...
- [转帖]LTP使用和分析
一.安装及编译流程 1.下载LTP LTP 项目目前位于 GitHub,项目地址:https://github.com/linux-test-project/ltp . 获取最新版可以执行以下命令: ...
- [转帖]Intel、海光、鲲鹏920、飞腾2500 CPU性能对比
https://plantegg.github.io/2021/06/18/%E5%87%A0%E6%AC%BECPU%E6%80%A7%E8%83%BD%E5%AF%B9%E6%AF%94/ Int ...
- vuex中action中发送数据
state: { actionData: "", }, mutations: { actioncomAPi: (state, data) => { state.actionD ...
- Vant中List列表下拉加载更多
van-list上只有一层父级元素div,多了可能下拉加载出不来:或者更多 <template> <div class="scholl-declarepage"& ...
- hadoop实践02---eclipse操作hdfs的api上传文件
1.eclipse中编写代码后双击main方法--->Run as ---> java application ,然后指定的文件 就会提交到hdfs中. 2.查看文件:http://192 ...
- 使用svn.externals(外链)提升美术多个svn目录的svn up速度
svn up多个目录耗时大 svn上的美术资源项目,在打包机上对一个很久没有变化的目录进行svn up也是需要消耗不少时间的,特别打包时需要对多个目录进行svn up,比如空跑54个目录的svn up ...