preface
A few days ago, I saw a colleague talking about dynamic proxies and class loaders. In fact, these technologies have been worn out on both the client side and the back side.
Dynamic proxies cglib and JDK.proxy are basically the “eight” questions for the Java backend interview.
Class loaders are also the foundation of Java, called the parent-delegate model Balabala
DexElements in Android are also derived from classloaders, and Tomcat hot deployment also relies on them.
But classloaders remind me of an earlier problem with transferring data across processes, just in case I forget.
Bundle and Parcel
Consider a counter example of data transfer across processes
TestData is the transmitted data type implements Parcelable.
The AIDL interface defines two methods for transferring data of type TestData and Bundle(bundle.putParcelable(TestData))
// TestService.aidl
interface ITestService {
void setTestData(in TestData data);
void setBundle(in Bundle data);
}
// TestData.java
public class TestData implements Parcelable {
public int data_int;
public String data_str;
protected TestData(Parcel in) {
data_int = in.readInt();
data_str = in.readString();
}
public TestData(int data_int, String data_str) {
this.data_int = data_int;
this.data_str = data_str;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(data_int);
dest.writeString(data_str);
}
@Override
public int describeContents(a) {
return 0;
}
public static final Creator<TestData> CREATOR = new Creator<TestData>() {
@Override
public TestData createFromParcel(Parcel in) {
return new TestData(in);
}
@Override
public TestData[] newArray(int size) {
return newTestData[size]; }}; }// TestService.java
public class TestService extends Service {
private static final String TAG = "TestService";
public TestService(a) {}private final ITestService.Stub mStub = new ITestService.Stub() {
@Override
public void setTestData(TestData data) throws RemoteException {
Log.e(TAG, "setTestData: " + data.data_str);
}
@Override
public void setBundle(Bundle data) throws RemoteException {
TestData testData = data.getParcelable("test");
Log.e(TAG, "setBundle: "+ testData.data_str); }};@Override
public IBinder onBind(Intent intent) {
returnmStub; }}Copy the code
We create a remote service and print the data we receive.
What’s the result?
Java. Lang. ClassNotFoundException
When we call setTestData(), nothing happens.
But when we call setBundle(), we crash, throwing a ClassNotFoundException.
The call stack is as follows:
java.lang.ClassNotFoundException: com.ptrain.testclassloader.TestData
at java.lang.Class.classForName(Native Method)
at java.lang.Class.forName(Class.java:453)
at android.os.Parcel.readParcelableCreator(Parcel.java:2811)
at android.os.Parcel.readParcelable(Parcel.java:2765)
at android.os.Parcel.readValue(Parcel.java:2668)
at android.os.Parcel.readArrayMapInternal(Parcel.java:3037)
at android.os.BaseBundle.initializeFromParcelLocked(BaseBundle.java:288)
at android.os.BaseBundle.unparcel(BaseBundle.java:232)
at android.os.Bundle.getParcelable(Bundle.java:940)
at com.ptrain.testclassloader.TestService$1.setBundle(TestService.java:30)
at com.ptrain.testclassloader.ITestService$Stub.onTransact(ITestService.java:103)
Copy the code
Bundle of this
Well, Bundle is still a subclass, Bundle extends BaseBundle. The default ClassLoader of BaseBundle is BootClassLoader, which is a singleton class that directly inherits from ClassLoader and is used to load some system classes. Therefore, TestData is not known to exist. This is where we need to specify the ClassLoader.
Rarely used bundle.setclassLoader ()
Bundle has always had this method, but I’ve never used it.
public void setClassLoader(ClassLoader loader) {
// Does the Bundle extends BaseBundle
super.setClassLoader(loader);
}
Copy the code
Using this method we can set up a ClassLoader for the Bundle.
Add a line of code to kill the crash before reading the data.
Bundle.setClassLoader(TestData.class.getClassLoader());
Copy the code
Why does setTestData() not crash
Because the Bundle reads differently, we can actually crash TestData as well.
When we use the Bundle to obtain Parcelable object, will call to Bundle. The readParcelableCreator (), This method will eventually load the Class through class. forName(String name, Boolean Initialize, ClassLoader), which is the specified ClassLoader.
The TestData object was created when createFromParcel(Parcel in) was created, so there is no process to read Parcelable.
Let the TestData not found
Modify TestData’s code so that it also throws ClassNotFoundException.
public class TestData implements Parcelable {
public int data_int;
public String data_str;
// Add a member variable to implements Parcelable
public TestData2 data_test2;
protected TestData(Parcel in) { data_int = in.readInt(); data_str = in.readString(); data_test2 = in.readParcelable(TestData2.class.getClassLoader()); }}Copy the code
Through the Android Studio auto-complete code, we have seen in the readParcelable (TestData2. Class. GetClassLoader ()) set the place of this, the Settings are correct, Autocomplete should also be a ClassNotFoundException in case we forget to specify ClassLoader.
We will just TestData2. Class. GetClassLoader () into a Bundle of this BootClassLoader or to specify a custom can not find this TestData2 Can cause the program to crash and throw a ClassNotFoundException.
conclusion
The things we take for granted are often not so simple and pure.
The essential function of a ClassLoader is to find and load classes, and the rest, such as version isolation, feels derivative.