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中,在这个进程中走attachBaseContextVirtualCore.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启动流程源码分析的更多相关文章

  1. Spark(五十一):Spark On YARN(Yarn-Cluster模式)启动流程源码分析(二)

    上篇<Spark(四十九):Spark On YARN启动流程源码分析(一)>我们讲到启动SparkContext初始化,ApplicationMaster启动资源中,讲解的内容明显不完整 ...

  2. Spark(四十九):Spark On YARN启动流程源码分析(一)

    引导: 该篇章主要讲解执行spark-submit.sh提交到将任务提交给Yarn阶段代码分析. spark-submit的入口函数 一般提交一个spark作业的方式采用spark-submit来提交 ...

  3. springboot的启动流程源码分析

    .测试项目,随便一个简单的springboot项目即可: 直接debug调试: 可见,分2步,第一步是创建SpringApplication对象,第二步是调用run方法: 1.SpringApplic ...

  4. Spring Boot的自动配置原理及启动流程源码分析

    概述 Spring Boot 应用目前应该是 Java 中用得最多的框架了吧.其中 Spring Boot 最具特点之一就是自动配置,基于Spring Boot 的自动配置,我们可以很快集成某个模块, ...

  5. SpringBoot启动流程源码分析

    前言 SpringBoot项目的启动流程是很多面试官面试中高级Java程序员喜欢问的问题.这个问题的答案涉及到了SpringBoot工程中的源码,也许我们之前看过别的大牛写过的有关SpringBoot ...

  6. SpringBoot 源码解析 (二)----- Spring Boot精髓:启动流程源码分析

    本文从源代码的角度来看看Spring Boot的启动过程到底是怎么样的,为何以往纷繁复杂的配置到如今可以这么简便. 入口类 @SpringBootApplication public class He ...

  7. Spark On YARN启动流程源码分析(一)

    本文主要参考: a. https://www.cnblogs.com/yy3b2007com/p/10934090.html 0. 说明 a. 关于spark源码会不定期的更新与补充 b. 对于spa ...

  8. 面试必备:Android Activity启动流程源码分析

    最近大致分析了一把 Activity 启动的流程,趁着今天精神状态好,把之前记录的写成文章. 开门见山,我们直接点进去看 Activity 的 startActivity , 最终,我们都会走到 st ...

  9. SpringBoot一站式启动流程源码分析

    一.前言 由上篇文章我们得知,SpringBoot启动时,就是有很简单的一行代码.那我们可以很清楚的看到这行代码的主角便是SpringApplication了,本文我们就来聊一聊这货,来探寻Sprin ...

  10. Dubbo学习笔记10:Dubbo服务消费方启动流程源码分析

    同理我们看下服务消费端启动流程时序图: 在<Dubbo整体架构分析>一文中,我们提到服务消费方需要使用ReferenceConfig API来消费服务,具体是调用代码(1)get()方法来 ...

随机推荐

  1. [转帖]三篇文章了解 TiDB 技术内幕 - 说存储

    https://cn.pingcap.com/blog/tidb-internal-1 引言 数据库.操作系统和编译器并称为三大系统,可以说是整个计算机软件的基石.其中数据库更靠近应用层,是很多业务的 ...

  2. [转帖]xsos:一个在 Linux 上阅读 SOSReport 的工具

    https://www.sohu.com/a/315931829_100034897 xsos 是一个帮助用户轻松读取 Linux 系统上的 sosreport 的工具.另一方面,我们可以说它是 so ...

  3. ESXi虚拟化的坑-细微区别下虚拟机性能差异巨大

    ESXi虚拟化的坑-细微区别下虚拟机性能差异巨大 背景 周末在公司无偿加班. 同组的小伙伴周一有一个需求, 我想着周六乘着机器压力不大进行一下虚拟机的clone 但是截止到晚上快十点都没有完全Clon ...

  4. [转帖]第5章 WINDOWS PE/COFF

    https://www.jianshu.com/p/35db9df2514f?utm_campaign=maleskine&utm_content=note&utm_medium=se ...

  5. openEuler technical-certification

    https://gitee.com/meitingli/technical-certification/ 介绍 存放openEuler技术测评相关的文档,包括技术测评标准.流程.指导性文档等 技术测评 ...

  6. Redislabs的简单使用与benchmark测试结果

    Redislabs的简单使用与benchmark测试结果 自己的理解 仅是理解. 没有看过源码 哨兵可以实现高可用, 但是对高吞吐是没有太大帮助的. 虽然可以实现主写从读, 但是在高并发的场景下延迟肯 ...

  7. 神通奥斯卡数据库是否兼容Oracle, 以及参数修改的办法

    1. 最近公司要适配神通数据库, 但是因为一些功能异常.参数可能存在风险. 为了减少问题, 想着简单描述一下这些的处理. 开发和客户给的默认参数建议 1. 不选择 兼容oracle模式 2. 字符集选 ...

  8. 【JS 逆向百例】XHR 断点调试,Steam 登录逆向

    声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除! 逆向目标 目标:Steam ...

  9. 通过Unity导出的Android Studio和Google安卓原生工程的结构图对比

    使用Unity导出Android Studio工程前建议查看我之前的文章<Unity2019及Unity2020打包android的环境配置>,替换或修改Unity安装目录下的basePr ...

  10. 【1】windows下安装OpenCV(4.3)+VS2017安装+opencv_contrib4.3.0配置

    相关文章: [1]windows下安装OpenCV(4.3)+VS2017安装+opencv_contrib4.3.0配置 [2]Visual Studio 2017同时配置OpenCV2.4 以及O ...