First of all, the Android Plugin Development Guide is based on the Android Android6.0 (API level 23), and this book introduces a variety of plug-in solutions, as well as supporting more than 70 examples, in Android7.0 (API Level 24) The test on the phone is working properly.
If the reader your phone is Android 26, 27, or even 28 (that is, Android P), then there will be 30 plug-in examples do not work, this is due to the Android system underlying source code changes.
This article is devoted to the impact of the Android O changes on the plugin, and the corresponding plug-in solution.
(a) from the reconstruction of Activitymanagernative
The first is the Gdefault field of the Activitymanagernative class, which is defined in API 25 and previous versions as follows:
Public Abstract classActivitymanagernativeextendsBinderImplementsIactivitymanager {Private Static FinalSingleton<iactivitymanager> Gdefault =NewSingleton<iactivitymanager>() { protectedIactivitymanager Create () {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; } };}
So, we can get the activitymanagernative gdefault field by reflection, execute its Create method, get the object of the Iactivitymanager interface type.
See this interface type, we are bright, can through the Proxy.newproxyinstance method, hook off the Iactivitymanager object, intercept its StartActivity method, to start the Instead of the activity declared in manifest, replace it with the occupied pit Stubactivity, the code is as follows:
Public Static voidHookamn ()throwsclassnotfoundexception, Nosuchmethodexception, InvocationTargetException, Illegalaccessexceptio N, nosuchfieldexception {//get Amn Gdefault Singleton Gdefault,gdefault is final staticObject Gdefault = Refinvoke.getstaticfieldobject ("android.app.ActivityManagerNative", "Gdefault"); //Gdefault is a android.util.singleton<t> object; we take out the Minstance field in this singleton .Object minstance = Refinvoke.getfieldobject ("Android.util.Singleton", Gdefault, "Minstance"); //Create a proxy object for this object MockClass1, then replace this field and let our proxy object help with the workclass<?> classb2interface = Class.forName ("Android.app.IActivityManager"); Object Proxy=proxy.newproxyinstance (Thread.CurrentThread (). Getcontextclassloader (),NewClass<?>[] {classb2interface},NewMockClass1 (minstance)); //Change the Minstance field of Gdefault to proxyClass Class1 =Gdefault.getclass (); Refinvoke.setfieldobject ("Android.util.Singleton", Gdefault, "Minstance", proxy); }
We have explained the above code in detail in the 5th chapter of the book. Unfortunately, this code does not work in the system version above Android O (API level 26), and when running to this sentence, the value of Gdefault is null:
Object Gdefault = Refinvoke.getstaticfieldobject ("android.app.ActivityManagerNative", "Gdefault");
This is because Google in Android O, the activitymanagernative in the Gdefault field deleted, transferred to the Activitymanager class, but at this time, This field is renamed Iactivitymanagersingleton, so in Android p, this sentence should be changed to:
Object Gdefault = Refinvoke.getstaticfieldobject ("Android.app.ActivityManager", "Iactivitymanagersingleton");
But this is not compatible with Android O version below, so write a if-else conditional statement, depending on the version of the Android system, to do different processing, as follows:
NULL ; if (Android.os.Build.VERSION.SDK_INT <=) {// get amn gdefault single case gdefault,gdefault is static Gdefault = Refinvoke.getstaticfieldobject ("android.app.ActivityManagerNative", "Gdefault"); Else { // get Activitymanager's singleton Iactivitymanagersingleton, he is actually the former Gdefault Gdefault = Refinvoke.getstaticfieldobject ("Android.app.ActivityManager", "Iactivitymanagersingleton"); }
(ii) The Sovranty of element and Dexfile
We then shifted our gaze to the loading of the plugin class. We have introduced 3 types of loading methods in our book:
1. Create a classloader for each plugin, and use the plugin ClassLoader to load the class in the plugin.
2. Combine the Dex in all plugins into the Dex array of the host app.
3. Replace the ClassLoader used by the host app with the ClassLoader that we created, in this new ClassLoader, there is a container variable that hosts all of the plug-in ClassLoader to load the class in the plugin.
The implementation of this 2nd approach is the simplest, that is, to merge all of the plug-in Dex into an array, the specific code implementation is as follows:
Public Final classBasedexclassloaderhookhelper { Public Static voidPatchclassloader (ClassLoader cl, file apkfile, file optdexfile)throwsillegalaccessexception, Nosuchmethodexception, IOException, InvocationTargetException, InstantiationException, nosuchfieldexception {//Get Basedexclassloader:pathlistObject pathlistobj = Refinvoke.getfieldobject (dexclassloader.class. Getsuperclass (), cl, "PathList"); //get pathlist:element[] dexelementsObject[] dexelements = (object[]) refinvoke.getfieldobject (pathlistobj, "dexelements"); //Element TypeClass<?> Elementclass =Dexelements.getclass (). Getcomponenttype (); //create an array to replace the original arrayObject[] newelements = (object[]) array.newinstance (Elementclass, Dexelements.length + 1); //construction Plug-in element (file file, boolean isdirectory, file zip, Dexfile dexfile) This constructorClass[] P1 = {File.class,Boolean.class, File.class, Dexfile.class}; object[] V1= {Apkfile,false, Apkfile, Dexfile.loaddex (Apkfile.getcanonicalpath (), Optdexfile.getabsolutepath (), 0)}; Object o=Refinvoke.createobject (Elementclass, p1, v1); Object[] Toaddelementarray=Newobject[] {o}; //Copy the original elements in.System.arraycopy (dexelements, 0, newelements, 0, dexelements.length); //The element of the plug-in is copied inSystem.arraycopy (Toaddelementarray, 0, Newelements, Dexelements.length, toaddelementarray.length); //ReplaceRefinvoke.setfieldobject (Pathlistobj, "dexelements", newelements); }}
There is no problem with this idea. Pay attention to these words:
Class[] P1 = {File. ClassBoolean. class, File. class, Dexfile. class false, Apkfile, Dexfile.loaddex (Apkfile.getcanonicalpath (), Optdexfile.getabsolutepath (), 0 =new object[] {o};
In these words, a constructor with 4 parameters is executed by reflection, but unfortunately, in Android O and later versions, this constructor with 4 parameters is discarded.
In addition, the Dexfile class, which is used in this constructor, is deprecated, and Google's explanation is that only the Android system can use the Dexfile,app level to not use it.
So, we had to do this by implementing the Makedexelements method of the Dexpathlist class to generate the Dex in the plugin:
New Arraylist<>(); Legalfiles.add (Apkfile); Listnew arraylist<ioexception>= {list. Class, File. class, List. class, ClassLoader. class == (object[]) Refinvoke.invokestaticmethod ("Dalvik.system.DexPathList", " Makedexelements ", p1, v1);
This code is also applicable in previous versions of Android O. So, we found a better method of makedexelements than Dexfile to hook.
Android plugin compatibility (ON): Android O adaptation