In the Android plug-in ClassLoader in the previous article, we were able to load apK successfully, but there was no way to start the Activity in the plug-in. We know that to start an Activity, the Activity must be registered in androidmanifest.xml. Therefore, if we want to start an Activity in the plug-in, the Activity must be pre-registered in the host’s Androidmanifest.xml. There are two problems with this:
- The Activity in the plugin is registered in the host’s Androidmanifest.xml, which is not development-friendly.
- You can only start an Activity defined in the plug-in, and there is no way to dynamically add an Activity.
Therefore, existing plug-in frameworks have a mechanism for launching an Activity over the androidmanifest.xml register. The purpose of this paper is to analyze this mechanism.
The start process of the Activity
Regardless of multiple processes, Activity startup is a two-way communication process with the Binder, which is done by our app process and the SYSTEM_server process where AMS is located. System_server is a system-level process whose internal AMS is responsible for managing the Activity state of all apps. As a result, Android doesn’t allow us to make changes to AMS. So, plug-in technology can only be used in our app process.
Below I draw the flow chart of the Activity startup according to the 6.0 source code. I omit the AMS part that we don’t need to care about.
As shown in the figure above, I split the Activity launch process into two parts. The above section describes the process from app initiating the activity request to AMS receiving the request. The following section describes the process of AMS starting the Activity in response to the APP process after the processing is complete.
The request phase
Above whether Activity statActivity () or startActivityForResult () is called Instrumentation. Eventually execStartActivity (). We continue our positioning to Instrumentation. The execStartActivity ().
ExecStartActivity () is very simple. It will further incoming parameters after packing to ActivityManagerNative. GetDefault startActivity () (). ActivityManagerNative. GetDefault () returns is actually a ActivityManagerProxy object. ActivityManagerProxy is the Binder object of ActivityManagerService in the APP process. Call ActivityManagerProxy. StartService () the last invoked ActivityManagerService. StartService (). The request goes to ActivityManagerService. ActivityManagerNative. GetDefault () as follows:
At this point, the process of requesting the Activity to start is parsed.
The response phase
As mentioned earlier, the Activity startup process is a Binder two-way communication process without considering multiple processes. AMS to take the initiative to communicate with the app process depends on the request and the Activity stage IBinder object, the IBinder object is the Instrumentation described above. The execStartActivity whoThread objects in the (), It is actually an ApplicationThreadProxy object that communicates with the ApplicationThread. AMS notify app process started the Activity is by calling ApplicationThreadProxy. ScheduleLaunchActivity (). According to the Binder communication, ApplicationThread. ScheduleLaunchActivity () is called. We are from ApplicationThread. ScheduleLaunchActivity () to analyze.
The scheduleLaunchActivity() encapsulates the parameters passed from AMS into an ActivityClientRecord object, and then sends the message to THE mH, which is a Handler object.
H is ActivityThread inner classes, inherit from Handler, it after receiving news of LAUNCH_ACTIVITY, invoked ActivityThread. HandlerLaunchActivity ().
HandleLaunchActivity () mainly calls two methods: performLaunchActivity() and handleResumeActivity(). PerformLaunchActivity () completes the creation of the Activity and calls the Activity’s onCreate(), onStart() and other methods. HandleResumeActivity () completes the call to activity.onResume (). We continue tracking performLaunchActivity().
The above code is commented in key places. We can see from the comments that Activity creation and onCreate() calls are done in Instrumentation. Note the call timing of activtiy.attach (), which we will use in the following article. The specific code of Instrumentation is as follows:
At this point, AMS notifies the app process to start the Activity.
Hook Instrumentation across the AndroidManifest detection
The above content is part of the internal work, and then it’s time to learn specific moves.
The essence of launching an Activity that is not registered in androidmanifest.xml is to change the day. So how do we do that? Let me give you an example.
If you have a TargetActivity in your plugin that is not registered in androidmanifest.xml and you want to start it, there are three steps.
- Pre-register an Activity in androidmanifest.xml that is not in our project, such as ProxyActivity. We call this behavior piling.
- In the request to start the Activity phase, we replace TargetActivity with the pre-registered ProxyActivity in AndroidManifest.
- During the AMS response phase, before the Activity instance is generated, we do the exact opposite. That is, replace the ProxyActivity to start in the response message with TargetActivity.
The first step is very simple, there is nothing to say. To implement steps 2 and 3, you need to use knowledge of the Activity launch process.
In the Activity startup process, Instrumentation plays an important role in both the request stage and the response stage. In the request phase Instrumentation. ExecStartActivity () is called, and the response Instrumentation. NewActivity () will be invoked. So if we can Hook Instrumentation, then we can perform the functions in step 2 and step 3 in execStartActivity() and newActivity(), respectively.
Talk about Instrumentation
The Instrumentation ActivityThread
As we know, every Java program has a main() method. The Main () method of the Android App is in the ActivityThread.
In the main() method, the ActivityThread is initialized and ends up storing the object in the static sCurrentActivityThread. There is only one ActivityThread instance sCurrentActivityThread in an app process. SCurrentActivityThread can ActivityThread. CurrentActivityThread ().
AttachApplication (mAppThread) in Attach () is a two-way communication process with the Binder, which mainly serves to create Application objects. The entire communication process is similar to the Activity startup process, which I won’t go into in detail. At the end of the communication, ActivtiyThread handleBindApplication () is called, and inside the method, Instrumentation is initialized.
To summarize, a process of App, only a ActivityThread object, the object stored in sCurrentActivityThread, can pass ActivityThread. CurrentActivityThread (). The mInstrumentation of the ActivityThread is initialized before the Application is created.
The Activity of the Instrumentation
The Instrumentation in Activtiy is passed in by activity.attach ().
Activity.attach() was mentioned in the introduction to the Activity launch process. . It will be ActivityThread performLaunchActivity () is invoked.
Thus the ActivtyThread passes its own internal Instrumentation to the Activity.
Hook Instrumentation
From the above analysis, we know that to Hook app Instrumentation, only need to replace the Instrumentation of ActivityThread. However, the Android SDK doesn’t provide any APIS for ActivityThread. To access classes or methods that don’t exist in the Android SDK, let’s learn how VirtualAPK does it.
There is a module called AndroidStub in VirtualAPK. Its structure is as follows:
VirtualAPK redeclares these Framework layer classes that the Android SDK does not provide. These classes only have method declarations, such as those in ActivityThread:
So we can use these classes or hidden methods that the Android SDK doesn’t provide. One thing to note is that AndroidStub should only participate in compileOnly, which is easy to do with the compileOnly dependency.
Next, we can replace ActivitThread’s Instrumentation with reflection. The code is as follows:
The VAInstrumentation above is the proxy class for system Instrumentation. Inside the VAInstrumentation we can add any logic we want.
Before in Instrumentation. ExecStartActivity () we want to start the Activity to replace pre-registered ProxyActivity.
Before in Instrumentation. NewActivity () will be pre-registered ProxyActivity substitution back we will start the Activity.
At the end
As I mentioned in the introduction to Android plugins, each article ends with a Demo, which together form a plugin framework, so I set up a project called VirtualApkLike on Github, where the Demo will be placed in different branches. The Demo in this paper is on the startActtivity branch.