Here determined: I will strive to graduate from university into BAT
Practice objective: An Activity can be started with an intent without having to register it in an intent.
- Some articles are called Hook technology. The general content is to listen to the method or call or trigger, during the modification of method parameters or return value to modify app without changing app source code. Such as Xpose plug-ins can prevent QQ undo messages.
- Today we listen for the activity to start and then make method changes, using dynamic proxies and lots of reflection
Activity Startup process Analysis Chapter 1
First, learn the activity launch process: Suppose we have an onclick method that triggers an activity when a button is clicked
//activity
public void onClick(View view) {
Intent intent = new Intent(this,SecondActivity.class);
startActivity(intent);
}
Copy the code
The next thing we understand is that the interface jumps. I mainly studystartActivityInternal implementation of the call procedure to start oneactivityProcess.
The implementation of this method is inContextImpl.javaThen we look at the inheritance diagram:
The resulting method inheritance relationship does not existContextImpl.java. He was actually wrapped up inContextWrapper.javaIn the
//ContextWrapper.java
public class ContextWrapper extends Context { Context mBase; . Omit other code@Override
public void startActivity(Intent intent) { mBase.startActivity(intent); }... Omit other code}Copy the code
You can see there’s an mBase property object in there. The startActivity method of mBase is called when startActivity is called. This mBase is contextimpl.java
Now looking at contextimpl. Java we’ll look at the implementation of the startActivity method of this class
//ContextImpl.java
@Override
public void startActivity(Intent intent) {
warnIfCallingFromSystemProcess();
startActivity(intent, null);
}
Copy the code
StartActivity (Intent, null); Method, the former method is used for process detection.
@Override
public void startActivity(Intent intent, Bundle options) {
/ /... Omit the code and just look at the point of the method
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intent, -1, options);
}
Copy the code
. You can see the method call mMainThread getInstrumentation () to obtain an object and then call the object’s methods execStartActivity. MMainThread is declared as final ActivityThread mMainThread.
MMainThread. GetInstrumentation () to check the implementation is as follows
//ActivtyThread.java
public Instrumentation getInstrumentation(a)
{
return mInstrumentation;
}
Copy the code
So we continue to look at the execStartActivity method of the Instrumentation object
//Instrumentation.java
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {
/ /... Omit methods other methods
intResult = ActivityManagerNative. GetDefault (.) startActivity (a bunch of parameters);/ /...
return null;
}
Copy the code
The above code is passed too many arguments in particular. So use four words instead. We see ActivityManagerNative. GetDefault () to obtain an object and then call startActivity ()
ActivityManagerNative. GetDefault () method is explored
//ActivityManagerNative.java
static public IActivityManager getDefault(a) {
return gDefault.get();
}
Copy the code
GDefault is ActivityManagerNative Java properties object
The statement is as follows:
//ActivityManagerNative.java
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create(a) {
IBinder b = ServiceManager.getService("activity");
if (false) {
Log.v("ActivityManager"."default service binder = " + b);
}
IActivityManager am = asInterface(b);
if (false) {
Log.v("ActivityManager"."default service = " + am);
}
returnam; }};Copy the code
What is Singleton? This is a singleton utility class. The first time you call this class get() calls back to the abstract method create() that you implement
Complete code for this class:
//Singleton.java
package android.util;
public abstract class Singleton<T> {
private T mInstance;
protected abstract T create(a);
public final T get(a) {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
returnmInstance; }}}Copy the code
This kind of reflection we later get to IActivityManager you can simply can
Let’s go back a little bit and look at the method that implements the abstraction create()
//ActivityManagerNative.java
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create(a) {
// Get activityMnagerServer from the System Services Manager (this is a system service, so AIDL is required for interprocess communication)
IBinder b = ServiceManager.getService("activity");
if (false) {
Log.v("ActivityManager"."default service binder = " + b);
}
// Convert the IBinder object into the corresponding instantiation interface
IActivityManager am = asInterface(b);
if (false) {
Log.v("ActivityManager"."default service = " + am);
}
returnam; }};Copy the code
From the example above you can see, see ActivityManagerNative getDefault () returns the remote service objects IActivityManager interface.
Look again at the execStartActivity method of the Instrumentation object
//Instrumentation.java
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {
/ /... Omit methods other methods
intResult = ActivityManagerNative. GetDefault (.) startActivity (a bunch of parameters);/ /...
return null;
}
Copy the code
Here’s the conclusion: Get a remote service object interface IActivityManager (and it’s a singleton) and call the startActivity method of the IActivityManager. The real startActivity interface method of the remote service ActivityManagerServer is called back. It checks to see if the Activity you’re passing in is in the manifest. If your Activity isn’t in the manifest, it’s an exception. But we don’t need to look at the ActivityManagerServer source code, because it’s another app process, so we can’t interfere. If you can interfere, Android will be too unstable. There will be rogue developers who will let you click on one app and interfere with other apps. So we can’t hook into the remote service, but we can use IActivityManager by hand and foot. Because that is the server side there is a client side of the proxy interface object, (this piece needs a simple understanding of the knowledge of AIDL. It doesn’t matter if you don’t understand it, just memorize it and manipulate it)
After looking at the above is very messy, look at the picture below to often train of thought:
(You can right-click to see the larger image)
The reader must ask, right? sinceActivityManagerServerStart theactivityWe can’t interfere. So what’s next?
IActivityManager calls the Intent of the Activity to be launched to ActivityManagerServer. Suppose we use the dynamic proxy before IActivityManager calls the remote service. Replace the Intent with an Activity registered in the manifest file. Of course, now that the substitution is validated, what’s the point if all the activities we actually want to start are replaced? This validates that the system does not actually start the Activity, because the ActivityManagerServer eventually calls back to the ActivityManagerServer. Here we don’t worry, first through the verification again.
The first small goal is to implement the substitution
So let’s say we have three activities the first one is MainActivity, the second placeholder Activity and the third one is SecondActivity
- MainActivity is registered in the manifest file, so it doesn’t matter which Activity it inherits
- Placeholder Activity is registered in the manifest file, so it doesn’t matter which Activity you inherit
- SecondActivity has no manifest file registered and inherits activities. (Don’t inherit or you will fail AppCompatActivity)
Where we click a button on MainActivity to trigger its onclick method to call startActivity to jump to SecondActivity
//MainActivity.java
public void onClick(View view) {
Intent intent = new Intent(this,SecondActivity.class);
startActivity(intent);
}
Copy the code
So, when a SecondActivity fails because it is not registered in a manifest file, our intention is to change that intention to start a placeholder activity
Step 1
Create an Application class named app.java (remember to associate it in the manifest) and override the attachBaseContext method
- AttachBaseContext: this method is called before the onCreate method. Remember ContextWrapper and ContextImpl? Take a look back (Application also inherits ContextWrapper).
public class ContextWrapper extends Context { Context mBase; protected void attachBaseContext(Context base) { if (mBase ! = null) { throw new IllegalStateException("Base context already set"); } mBase = base; }Copy the code
AttachBaseContext is a callback to the mBase object assignment passed in. This is where you can actually use context.
At this point our code looks like this:
//APP.java
public class APP extends Application {
// The Intent KEY used to store the original target Activity
private static final String KEY_EXTRA_TARGET_INTENT = "EXTRA_TARGET_INTENT";
private static final String TAG = "APP";
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base); }}Copy the code
In order to validate an Activity’s manifest file, you must perform an Intent substitution before the IActivityManager calls the startActivity method. So we use the dynamic proxy IActivityManager for IActivityManager directly
To first dynamically delegate you have to get the IActivityManager object. This object is stored in ActivityManagerNative’s property object gDefault. We call the get method on the property object gDefault(Singleton class) or get mInstance on its internal property object
MInstance is used to save an instance of a singleton object. Confusing? Go back to Singleton
//Singleton.java
public abstract class Singleton<T> {
// Save the object instance
private T mInstance;
// If the Singleton object is empty, this method is called to get the instance
protected abstract T create(a);
public final T get(a) {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
returnmInstance; }}}Copy the code
So let’s get the gDefault object and see how ActivityManagerNative declares it
//ActivityManagerNative.java
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create(a) {
IBinder b = ServiceManager.getService("activity");
if (false) {
Log.v("ActivityManager"."default service binder = " + b);
}
IActivityManager am = asInterface(b);
if (false) {
Log.v("ActivityManager"."default service = " + am);
}
returnam; }};Copy the code
We found that it was a static class, so we should laugh, static classes are easy to get object instances through reflection
- So you get the following code:
public class APP extends Application { private static final String KEY_EXTRA_TARGET_INTENT = "EXTRA_TARGET_INTENT"; private static final String TAG = "APP"; @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); try { Class<? > ams_class = Class.forName("android.app.ActivityManagerNative"); Field gDefault = ams_class.getDeclaredField("gDefault"); Gdefault.setaccessible (true); // Since gDefault is private, use the emission mechanism to break access restrictions. GDefault_instance = gdefault. get(null); gDefault_instance = gdefault. get(null); // singleton tool class class <? > singleton_class = Class.forName("android.util.Singleton"); // A singleton tool class holds an IActivityManager instance Field mInstance = singleton_class.getDeclaredField("mInstance"); // Break the encapsulation access mInstance. SetAccessible (true); // Pass gDefault to get IActivityManager final Object mInstance_instance = mInstance. Get (gDefault_instance); // Dynamic proxy. This method returns an instance Object that implements the IActivityManager interface // Replace mInstance with the singleton utility class gDefault to implement Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), mInstance_instance.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// IActivityManager interface method too many we only care about it to start the activity interface method if (method.getName().equals("startActivity")) {// Get the parameters that call this method. i < args.length; i++) { Object arg = args[i]; if (arg instanceof Intent) { Intent intent = new Intent(); So, whether it is a placeholder, or not, you create a new Intent that replaces the original Intent of the SecondActivity because it is registered in the manifest file. Use the / / and save it to the new Intent in the ComponentName the ComponentName = new the ComponentName (" com. Example. Fmy. Myapplication ", PlaceholdActivity.class.getName()); intent.setComponent(componentName); intent.putExtra(KEY_EXTRA_TARGET_INTENT, ((Intent) arg)); args[i] = intent; return method.invoke(mInstance_instance, args); } } } return method.invoke(mInstance_instance, args); }}); // Replace mInstance of singleton tool class gDefault to implement dynamic proxy mInstance. Set (gDefault_instance, proxyInstance); } catch (Exception e) { e.printStackTrace(); }}}Copy the code
So when you run the code and you find out that no matter what you put in startActivity the intention is to start placeholder Activity you succeed, right
Activity start process analysis Handler source code analysis chapter 2
I’m just going to talk a little bit about it and not overdo it. It then disengage from the main Looper loop, endlessly fetching the mesaage from the corresponding thread MassgaeQueue and sending it to the corresponding handler
The handler will call the following method if it has a message:
//Handler.java
public void dispatchMessage(Message msg) {
if(msg.callback ! =null) {
handleCallback(msg);
} else {
if(mCallback ! =null) {
if (mCallback.handleMessage(msg)) {
return; }}If mCallback is not empty and returns true, we will not be called to write our own handlerMessage to handle the message.handleMessage(msg); }}Copy the code
- We’ll replace the Intent again later with this mCallback
We know that when learning Javase we program from the main method to enter, so Android app entry method where? The main method that exists in ActivityThread. Java is
//ActivityThread.java
public static void main(String[] args) {
/ /... The code is omitted
// Some initialization of the Looper class takes the current thread's Looper from the ThreadLocal object
// We won't talk too much about ThreadLocal, just copy it with thread resources.
// Suppose you have a variable a and you can make three copies to different threads in ThreadLocal
Looper.prepareMainLooper();
/ /... The code is omitted
ActivityThread thread = new ActivityThread();
// Start a subthread, also called binder thread, for AMS (ActivityManagerServer communicates with AIDL and sends messages from the subthread to the main thread handler for processing)
thread.attach(false);
/ /... The code is omitted
// Start an infinite loop through the messageQueue (where the messager is stored).
Looper.loop();
/ /... The code is omitted
}
Copy the code
Look at the thread. The attach (false); methods
//ActivityThread.java
private void attach(boolean system) {
sCurrentActivityThread = this;
/ /... Ignore the code
}
Copy the code
We only see the method that saves the current ActivityThread to the sCurrentActivityThread which we will use later so this is explained here.
We saw in the first chapter that the startActivity method was sent to ActivityManagerServer via IActivityManager, and then we knew that the child thread was started in the ActivityThread class before the main method loops. This thread receives information from ActivityManagerServer, encapsulates it in a Message and adds it to the main thread MeassgeQueue, which passes it to the main thread Handler when the main Loop loops through the Message.
- When ActivityManagerServer receives the startActivity message, it sends a message to the Handler. Message. what=LAUNCH_ACTIVITY, LAUNCH_ACTIVITY=100
Let’s see where is the Handler in our Activity
public class AcitvityThread{ final H mH = new H(); Private class H extends Handler {//... Code omitted}}Copy the code
How does mH extends Handler handle a private Class H extends Handler event
//ActivityThread.java
class H extends Handler{
/ /...
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
// Take the ActivityClientRecord object from the MSG that contains the Intent that starts the activity
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
// Start the activity with an ActivityClientRecord object
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break; }}}Copy the code
You can get an ActivityClientRecord object from an MSG that contains information such as the Intent to start an Intent. Remember when we saved an Intent to a new Intent? Now you can actually pull it out of this Intent. Call the handleLaunchActivity to start it, and finally you want to explore the lifecycle on your own.
//ActivityThread.java
static final class ActivityClientRecord {
IBinder token;
int ident;
Intent intent;
String referrer;
//....
}
Copy the code
Let’s clean up the callback process:
So we can assign a value to handler’s mCallback, and then when handler calls back to lancherActivity, we know that mCallback is not null, The mCallback is called and the value it returns is used to determine whether or not to implement the handlerMessage.
So let’s get the handler object that resides in ActivityThread and see what activityThread.java declares for it
//ActivityThread.java
public final class ActivityThread {
final H mH = new H();
}
Copy the code
Because only mH is not a static type, you need to get the ActivityThread object before you can get the mH instance. Remember the main method we used to analyze ActivityThread earlier? Internal calls
public static void main(String[] args) {
/ /...
ActivityThread thread = new ActivityThread();
thread.attach(false);
/ /...
}
Copy the code
Look at the thread. The attach (false); methods
//ActivityThread.java
private void attach(boolean system) {
sCurrentActivityThread = this;
//....
}
Copy the code
The ActivityThread property object sCurrentActivityThread is saved in the ActivityThread property object
//ActivityThread.java
public final class ActivityThread {
/ /...
private static ActivityThread sCurrentActivityThread;
/ /...
}
Copy the code
Static property proof you know, you can use reflection to get objects
Therefore, the code synthesis above can be concluded as follows:
//APP.java package com.example.fmy.myapplication; public class APP extends Application { private static final String KEY_EXTRA_TARGET_INTENT = "EXTRA_TARGET_INTENT"; private static final String TAG = "APP"; @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); // check code omitted Class<? > activityThread_class = Class.forName("android.app.ActivityThread"); Field sCurrentActivityThread_field = activityThread_class.getDeclaredField("sCurrentActivityThread"); sCurrentActivityThread_field.setAccessible(true); Object activityThread_instance = scurrenTactivityThread_field. get(null); Field mH_field = activityThread_class.getDeclaredField("mH"); mH_field.setAccessible(true); Object mH_insance = mh_field. get(activityThread_instance); Field mCallback_field = Handler.class.getDeclaredField("mCallback"); mCallback_field.setAccessible(true); // Add mCallback McAllback_field. set(mH_insance, new Handler.Callback() { @Override public boolean handleMessage(Message msg) { if (msg.what == 100) { // Get ActivityClientRecord Object obj = msg.obj; Intent_field = obj.getClass().getDeclaredField(" Intent "); intent_field.setAccessible(true); Intent intent = (Intent) intent_field.get(obj); / / take out ahead of us in the Intent was not registered in the manifest file of the Intent of the Activity of Intent target_intent = Intent. GetParcelableExtra (KEY_EXTRA_TARGET_INTENT); if (target_intent ! = null) { intent.setComponent(target_intent.getComponent()); } } catch (Exception e) { e.printStackTrace(); } } return false; }}); } catch (Exception e) { e.printStackTrace(); } } @Override public void onCreate() { super.onCreate(); }}Copy the code
Finally, integrate the verified code as follows:
//APP.java
package com.example.fmy.myapplication;
public class APP extends Application {
private static final String KEY_EXTRA_TARGET_INTENT = "EXTRA_TARGET_INTENT";
private static final String TAG = "APP";
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
try{ Class<? > ams_class = Class.forName("android.app.ActivityManagerNative");
Field gDefault = ams_class.getDeclaredField("gDefault");
// Since gDefault is private, use the emission mechanism to break access restrictions
gDefault.setAccessible(true);
// Get the gDefault instance object
Object gDefault_instance = gDefault.get(null);
// singleton utility classClass<? > singleton_class = Class.forName("android.util.Singleton");
// A property object of the singleton utility class holds the IActivityManager object instance
Field mInstance = singleton_class.getDeclaredField("mInstance");
// Break encapsulated access
mInstance.setAccessible(true);
// Pass the gDefault object instance to get the IActivityManager object instance
final Object mInstance_instance = mInstance.get(gDefault_instance);
// Dynamic proxy. This method returns an instance object that implements the IActivityManager interface
// Replace mInstance with this object for the singleton utility class gDefault
Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), mInstance_instance.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// IActivityManager has too many interface methods. We only care about the interface method that starts the activity
if (method.getName().equals("startActivity")) {
// Get the parameters passed in for calling this method. We only need Intent substitution, so we only need Intent substitution
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (arg instanceof Intent) {
Intent intent = new Intent();
So, whether it is a placeholder, or not, you create a new Intent that replaces the original Intent of the SecondActivity because it is registered in the manifest file. Use the back
// Save it in a new Intent
ComponentName componentName = new ComponentName("com.example.fmy.myapplication", PlaceholdActivity.class.getName());
intent.setComponent(componentName);
intent.putExtra(KEY_EXTRA_TARGET_INTENT, ((Intent) arg));
args[i] = intent;
returnmethod.invoke(mInstance_instance, args); }}}returnmethod.invoke(mInstance_instance, args); }});// Replace mInstance of the singleton utility class gDefault for dynamic proxy
mInstance.set(gDefault_instance, proxyInstance);
// The inspection is completeClass<? > activityThread_class = Class.forName("android.app.ActivityThread");
Field sCurrentActivityThread_field = activityThread_class.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThread_field.setAccessible(true);
// Get an instance of a static property object
Object activityThread_instance = sCurrentActivityThread_field.get(null);
Field mH_field = activityThread_class.getDeclaredField("mH");
mH_field.setAccessible(true);
// Get the handler object instance
Object mH_insance = mH_field.get(activityThread_instance);
Field mCallback_field = Handler.class.getDeclaredField("mCallback");
mCallback_field.setAccessible(true);
// Add mCallback to handler
mCallback_field.set(mH_insance, new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == 100) {
/ / get ActivityClientRecord
Object obj = msg.obj;
try {
// Get the Intent object
Field intent_field = obj.getClass().getDeclaredField("intent");
intent_field.setAccessible(true);
Intent intent = (Intent) intent_field.get(obj);
// Retrieve the Intent in which we previously stored the Activity that was not registered in the manifest
Intent target_intent = intent.getParcelableExtra(KEY_EXTRA_TARGET_INTENT);
if(target_intent ! =null) { intent.setComponent(target_intent.getComponent()); }}catch(Exception e) { e.printStackTrace(); }}return false; }}); }catch(Exception e) { e.printStackTrace(); }}@Override
public void onCreate(a) {
super.onCreate(); }}Copy the code
GIT source attached with the source link