Activity Startup process

The Activity start process analysis has many articles, why I’d like to write an article, because I think most of the articles are a little complicated, personal experience that learning a technology from the global to see as far as possible, otherwise it will in detail and let yourself to this kind of technology is not the concept of a global, watched a few articles on the net, I summarized the picture, I’ve also added a lot of comments to the image, so let’s look at the image and fill in the details

Request stage:

ActivityManagerProxy is the Binder object of ActivityManagerService in the APP process. Call ActivityManagerProxy. StartActivity () the last invoked ActivityManagerService. StartActivity (). The request goes to ActivityManagerService

Response stage:

Without considering multiple processes, the Activity startup process is a Binder two-way communication process. 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.

  1. 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.
  2. H is ActivityThread inner classes, inherit from Handler, it after receiving news of LAUNCH_ACTIVITY, invoked ActivityThread. HandlerLaunchActivity ().
  3. 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 ().

Plug-in implementation

There are many ways to implement plug-ins, and the way I’m talking about is the one I think is simpler, which seems to be the way didi’s plug-in framework is implemented.

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.

  1. Pre-register an Activity in androidmanifest.xml that is not in our project, such as ProxyActivity. We call this behavior piling.
  2. In the request to start the Activity phase, we replace TargetActivity with the pre-registered ProxyActivity in AndroidManifest.
  3. 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.

When Instrumentation in ActivityThread is created:

public static void main(String[] args) {
	/ /...
	ActivityThread thread = new ActivityThread();
	thread.attach(false);
	/ /...
}

private void attach(boolean system) {
	sCurrentActivityThread = this;
	final IActivityManager mgr = ActivityManagerNative.getDefault();
	// Communicate with AMS
	mgr.attachApplication(mAppThread);
}

public static ActivityThread currentActivityThread(a) {
	return sCurrentActivityThread;
}
Copy the code
  1. In the Main () method of the ActivityThread, 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 ().
  2. 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.

Conclusion: an App process, 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.

When Instrumentation in an Activity is set:

  1. The Instrumentation in Activtiy is passed in by activity.attach ().
  2. Activity.attach() was mentioned in the introduction to the Activity launch process. . It will be ActivityThread performLaunchActivity () is invoked.
  3. Thus the ActivtyThread passes its own internal Instrumentation to the Activity.

Ultimate purpose: 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. Didi’s VirtualAPK plug-in Framework redeclares these Framework layer classes that are not provided by the Android SDK. These classes only have method declarations, so we can use 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.

  1. Next, replace the Instrumentation for ActivitThread with reflection:
protected void hookInst rumentation(a) {
try {
	ActivityThread activityThread = ActivityThread.currentActivityThread();
	Instrumentation baseInstrumentation = activityThread.getInstrumentation();
	final VAInstrumentation instrumentation = createInstrumentation(baseInstrumentation);
	Reflector.with(activityThread).field("mInstrumentation").set(instrumentation);
} catch(Exception e) { Log.w(TAG, e); }}public class VAInstrumentation extends Instrumentation {
	private Inst rumentation mBase ;
	private PluginManager mPluginManager;
	public VAInstrumentation(PluginManager pluginManager, Instrumentation base) {
		this.mPluginManager = pluginManager;
		this.mBase = base; }}Copy the code
  1. 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.
public class VAInstrumentation extends Instrumentation implements Handler.Callback {
	@override
	public ActivityResult execStartActivity(
		Context who, 
		IBinder contextThread, 
		IBinder token, 
		String target, 
		Intent intent, 
		int requestCode, 
		Bundle options) ( injectIntent(intent);
	return mBase.execStartActivity (who, contextThread, token, target, intent, requestCode, options);
}
private void injectIntent(Intent intent) {
	if(intent.getComponent() ! =null) {
		String targetPackageName = intent.getComponent().getPackageName();
		String targetClassName = intent.getComponent().getClassName();
		// Start the Activity in the plug-in
		if(! targetPackageName.equals(mContext.getPackageName())) (// Store the Activity's original information in the Intent
			intent.putExtra(Constants.KEY_IS_PLUGIN, true);
			intent.putExtra (Constants.KEY_TARGET_ PACKAGE, targetPackageName);
			intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName);
			// Use ProxyActivity insteaddispatchStubActivity(intent); }}}private void dispatchStubActivity(Intent intent) {
	String stubActivity = "com.like.virtualapk.ProxyActivity";
	intent.setClassName(mContext, stubActivity);
}
Copy the code
  1. Before in Instrumentation. NewActivity () will be pre-registered ProxyActivity substitution back we will start the Activity.
@override
public Activity newActivity(ClassLoader cl, String className, Intent intent) {
	try {
		cl.loadClass(className);
	} catch (ClassNotFoundException e) {
		ComponentName component = getComponent(intent);
		if ( component == null) {
			return mBase.newActivity(cl, className, intent) ;
		}
		String targetClassName = component.getClassName();
		Log. i(TAG, String. format("newActivity[%s : &s/%s]", className, component.getPackageName(), targetClassName));
		Activity activity = mBase.newActivity(cl, targetClassName, intent);
		activity.setIntent(intent);
		return activity;
	}
	return mBase.newActivity(cl, className, intent);
}
public static boolean isIntentFromPlugin(Intent intent) {
	if ( intent = null) {
		return false;
	}
	return intent.getBooleanExtra(Constants.KEY_IS_PLUGIN, false);
}
public static ComponentName getComponent(Intent intent) {
	if (intent = null) {
		return null;
	}
	if(isIntentFromPlugin(intent)) {
		return newComponentNameintent.getStringExtra(Constants.KEY_TARGET_PACKAGE), intent.getStringExtra(Constants.KEY_TARGET_ACTIVITY));  }return intent.getComponent() ;
}
Copy the code

Loading plugin resources:

  1. Reflection calls the addAssetPath method of the AssetsManager to add the external APK path to build the new Resource object.
  2. Use DexClassLoader to load R.class, obtain the Resource ID based on the Resource name, and obtain the Resource object based on the Resource and Resource ID constructed above.
/** * Reflection adds a resource path and creates a new Resources object */
private Resources getPluginResources(a) {
    try {
        AssetManager assetManager = AssetManager.class.newInstance();
        // reflection gets the addAssetPath method of the AssetManager
        Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
        // Add the add-on package address
        addAssetPath.invoke(assetManager, apkDir+ File.separator+apkName);
        Resources superRes = context.getResources();
        / / create the Resources
        Resources mResources = new Resources(assetManager, superRes.getDisplayMetrics(),
                superRes.getConfiguration());
        return mResources;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}


/** * 1. Get the id corresponding to the resource name (by reflecting the variable in the r.class file) * 2. Then get the corresponding resource object based on our constructed Resources. * /
public Drawable getApkDrawable(String drawableName){
    try {
        DexClassLoader dexClassLoader = new DexClassLoader(apkDir+File.separator+apkName,
        optimizedDirectoryFile.getPath(), null, context.getClassLoader());
 
        // Use apK's own class loader to reflect the corresponding inner class in R to get the resource ID we needClass<? > clazz = dexClassLoader.loadClass(apkPackageName +".R$drawable");
        Field field = clazz.getDeclaredField(drawableName);
        int resId = field.getInt(R.id.class);// Get the image id
        Resources mResources = getPluginResources();
        assertmResources ! =null;
        return mResources.getDrawable(resId);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return null;
}
Copy the code

Common plug-in frameworks:

  1. Dynamic-load-apk was the first to use ProxyActivity as a static proxy technology. ProxyActivity controls the life cycle of PluginActivity in plug-ins
  2. Dynamic replacement (HOOK) tends to select as few hooks as possible in terms of implementation principle, and realizes the dynamic plug-in of the four major components by embedding some components in the manifest. Like Replugin.
  3. The container framework VirtualApp can completely simulate the running environment of app, and can realize the installation free running and dual-open technology of APP.
  4. Atlas is an app base framework of Alibaba combining componentization and thermal repair technology, which claims to be a containerized framework.