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.