The beginning of a question
Have you ever wondered if you can open a web page in your browser, and there’s a button on the page that invokes your App?
How does this effect work? The browser is an app; Why can one app call up another app’s page?
Speaking of cross-app page calls, can you think of a mechanism: implicit calls for activities?
First, implicit priming principle
Implicit invocation is the API we use when we need to call up another app’s page.
For example, we have an app that declares an Activity like this:
<activity android:name=".OtherActivity"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="mdove"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
Copy the code
Other apps want to start this Activity with a call like this:
val intent = Intent()
intent.action = "mdove"
startActivity(intent)
Copy the code
We don’t actively declare an Activity class, so how does the system find the corresponding Activity for us? In fact, this is the same as the normal Activity start process, but the if/else implementation is different.
Let’s review the Activity startup process. To avoid getting bogged down in detail, we’ll just expand the familiar class and call stack, focusing on the serial process.
1.1. Cross-process
First of all, we must be clear: whether it is an implicit boot or a display boot; Both in-app and out-of-app activities are cross-process. Take our example above, where one App wants to launch the page of another App.
Note that the process incubated by the system cannot be seen on a mobile phone without root. That’s why some code doesn’t break.
Those of you who have followed startActivity() will be familiar with the following call flow, and after following a few methods you find yourself in a class called ActivityTread.
What are the features of the ActivityTread class? We have main, which is our main thread.
Soon we can see a call to a more common class: Instrumentation:
// Activity.java
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options) {
mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options);
/ / to omit
}
Copy the code
Note that mInstrumentation#execStartActivity() has a yellow entry parameter, which is the inner class ApplicationThread in ActivityThread.
The ApplicationThread class implements iApplicationThread. Stub, which is aiDL’s “client-side callback for cross-process calls.”
Another famous call to mInstrumentation#execStartActivity() :
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {
/ / to omit...ActivityManager.getService() .startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target ! =null ? target.mEmbeddedID : null,
requestCode, 0.null, options);
return null;
}
Copy the code
We click on getService() to see the IActivityManager class marked in red.
It’s not a.Java file, it’s an AIDL file.
So ActivityManager. GetService () essentially returns an instance of the “server side of the process” interface, namely:
1.2, ActivityManagerService
public class ActivityManagerService extends IActivityManager.Stub
So execution at this point goes to the system process (system_process process). To omit the code details, take a look at the call stack:
From the above debug screenshot, you can see that relevant information of our target Activitiy has been obtained at this time.
Here is a simplification of the source code to get the target class, directly into the conclusion:
1.3, PackageManagerService
This class is equivalent to parsing all apKs in the phone and constructing their information into memory, as shown in the following figure:
Small tips: phone directory/data/system/packages. The XML, you can see all the apk path information, process name, permissions, etc.
1.4. Start a new process
The prerequisite for starting the target Activity is that the process of the target Activity is started. So the first time you want to start the target Activity, you start the process.
The code to start the process is in the method that starts the Activity:
ResumeTopActivityInnerLocked – > startProcessLocked.
Here comes another class that’s another big name: ZygoteInit. So basically, we’re going to start the App using ZygoteInit.
1.5, ApplicationThread
After the process starts, continue back to the startup process of the target Activity. There is still a series of system_processes going around and around before the IApplicationThread enters the target process.
Notice here that the callback to ActivityThread is again via IApplicationThread.
class H extends Handler {
/ / to omit
public void handleMessage(Message msg) {
switch (msg.what) {
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);
/ / to omit
break;
case RELAUNCH_ACTIVITY:
handleRelaunchActivityLocally((IBinder) msg.obj);
break;
}
/ / to omit...}}/ / Callback execution
public void execute(ClientTransaction transaction) {
final IBinder token = transaction.getActivityToken();
executeCallbacks(transaction);
}
Copy the code
The so-called CallBack implementation is LaunchActivityItem#execute(), which corresponds to:
public void execute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) {
ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
mPendingResults, mPendingNewIntents, mIsForward,
mProfilerInfo, client);
client.handleLaunchActivity(r, pendingActions, null);
}
Copy the code
ActivityThread#handleLaunchActivity() now goes to our daily life cycle and the call stack looks like this:
The call chain in the screenshot above implies the Activity instantiation process (reflection) :
public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className, @Nullable Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return (Activity) cl.loadClass(className).newInstance();
}
Copy the code
Two, browser startup principle
A backflow page in a Helo site is a standard, browser-evoked example of another App.
2.1 Interactive process
HTML tags have an attribute href, for example: .
That is to jump to Baidu after the click.
Since this is a front-end tag, depending on the browser and its kernel implementation, jumping to a web page seems “natural”.
Of course, the interaction process here is basically the same as android: declare the Activity to be started in the way of implicit invocation; Then pass in the corresponding scheme. Such as:
Front page:
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
</head>
<body>
<a href="mdove1://haha">Start the OtherActivity</a>
</body>
Copy the code
Android statement:
<activity
android:name=".OtherActivity"
android:screenOrientation="portrait">
<intent-filter>
<data
android:host="haha"
android:scheme="mdove1" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Copy the code
2.2 Inference realization
The browser can load Scheme, which can be interpreted as the browser kernel encapsulation. Is it up to the browser kernel to make Scheme parsing available on Android?
Obviously, it’s impossible to build a mobile operating system and then let the browser implement it.
So you can probably guess that the browser app on the phone is doing the processing. Let’s take a look at the implementation of browser. Apk based on this conjecture.
2.3 browser implementation
Based on the above said/data/system/packages. The XML files, we can pull out of the browser. Apk.
Then jADX decompile browser.apk WebView related source code:
We can see that the href processing comes from an implicit jump, so everything is strung together with the above flow.
The end of the
A quick question at the end: If I write a WebView to load a front-end page, can I jump implicitly?