diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 69e3369ccba12..efcd3c9389c23 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -50,6 +50,8 @@ import android.app.ActivityManager; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageParserCacheHelper.ReadHelper; +import android.content.pm.PackageParserCacheHelper.WriteHelper; import android.content.pm.split.DefaultSplitAssetLoader; import android.content.pm.split.SplitAssetDependencyLoader; import android.content.pm.split.SplitAssetLoader; @@ -1024,11 +1026,17 @@ public class PackageParser { @VisibleForTesting protected Package fromCacheEntry(byte[] bytes) throws IOException { - Parcel p = Parcel.obtain(); + final ReadHelper helper = new ReadHelper(); + + final Parcel p = Parcel.obtain(); + p.setReadWriteHelper(helper); + p.unmarshall(bytes, 0, bytes.length); p.setDataPosition(0); + helper.start(p); PackageParser.Package pkg = new PackageParser.Package(p); + p.recycle(); return pkg; @@ -1036,8 +1044,15 @@ public class PackageParser { @VisibleForTesting protected byte[] toCacheEntry(Package pkg) throws IOException { - Parcel p = Parcel.obtain(); + final WriteHelper helper = new WriteHelper(); + + final Parcel p = Parcel.obtain(); + p.setReadWriteHelper(helper); + pkg.writeToParcel(p, 0 /* flags */); + + helper.finish(p); + byte[] serialized = p.marshall(); p.recycle(); diff --git a/core/java/android/content/pm/PackageParserCacheHelper.java b/core/java/android/content/pm/PackageParserCacheHelper.java new file mode 100644 index 0000000000000..638bc1359b854 --- /dev/null +++ b/core/java/android/content/pm/PackageParserCacheHelper.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.os.Parcel; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Helper classes to read from and write to Parcel with pooled strings. + * + * @hide + */ +public class PackageParserCacheHelper { + private PackageParserCacheHelper() { + } + + /** + * Parcel read helper with a string pool. + */ + public static class ReadHelper extends Parcel.ReadWriteHelper { + private final ArrayList mStrings = new ArrayList<>(); + + public ReadHelper() { + } + + /** + * Prepare a parcel. + */ + public void start(Parcel p) { + mStrings.clear(); + + final int poolPosition = p.readInt(); + final int startPosition = p.dataPosition(); + + // The pool is at the end of the parcel. + p.setDataPosition(poolPosition); + p.readStringList(mStrings); + + // Then move back. + p.setDataPosition(startPosition); + } + + /** + * Read an string index from a parcel, and returns the corresponding string from the pool. + */ + @Override + public String readString(Parcel p) { + return mStrings.get(p.readInt()); + } + } + + /** + * Parcel write helper with a string pool. + */ + public static class WriteHelper extends Parcel.ReadWriteHelper { + private final ArrayList mStrings = new ArrayList<>(); + + private final HashMap mIndexes = new HashMap<>(); + + public WriteHelper() { + } + + /** + * Instead of writing a string directly to a parcel, this method adds it to the pool, + * and write the index in the pool to the parcel. + */ + @Override + public void writeString(Parcel p, String s) { + final Integer cur = mIndexes.get(s); + if (cur != null) { + // String already in the pool. Just write the index. + p.writeInt(cur); // Already in the pool. + } else { + // Note in the pool. Add to the pool, and write the index. + final int index = mStrings.size(); + mIndexes.put(s, index); + mStrings.add(s); + + p.writeInt(index); + } + } + + /** + * Closes a parcel by appending the string pool at the end and updating the pool offset, + * which it assumes is at the first byte. + */ + public void finish(Parcel p) { + final int poolPosition = p.readInt(); + p.writeStringList(mStrings); + + p.setDataPosition(0); + p.writeInt(poolPosition); + + // Move back to the end. + p.setDataPosition(p.dataSize()); + } + } +} diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 10331b9229e3c..5284e0de45c99 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -291,7 +291,7 @@ public final class Parcel { private static native void nativeWriteFloat(long nativePtr, float val); @FastNative private static native void nativeWriteDouble(long nativePtr, double val); - private static native void nativeWriteString(long nativePtr, String val); + static native void nativeWriteString(long nativePtr, String val); private static native void nativeWriteStrongBinder(long nativePtr, IBinder val); private static native long nativeWriteFileDescriptor(long nativePtr, FileDescriptor val); @@ -306,7 +306,7 @@ public final class Parcel { private static native float nativeReadFloat(long nativePtr); @CriticalNative private static native double nativeReadDouble(long nativePtr); - private static native String nativeReadString(long nativePtr); + static native String nativeReadString(long nativePtr); private static native IBinder nativeReadStrongBinder(long nativePtr); private static native FileDescriptor nativeReadFileDescriptor(long nativePtr); @@ -338,6 +338,33 @@ public final class Parcel { } }; + /** + * @hide + */ + public static class ReadWriteHelper { + public static final ReadWriteHelper DEFAULT = new ReadWriteHelper(); + + /** + * Called when writing a string to a parcel. Subclasses wanting to write a string + * must use {@link #writeStringNoHelper(String)} to avoid + * infinity recursive calls. + */ + public void writeString(Parcel p, String s) { + nativeWriteString(p.mNativePtr, s); + } + + /** + * Called when reading a string to a parcel. Subclasses wanting to read a string + * must use {@link #readStringNoHelper()} to avoid + * infinity recursive calls. + */ + public String readString(Parcel p) { + return nativeReadString(p.mNativePtr); + } + } + + private ReadWriteHelper mReadWriteHelper = ReadWriteHelper.DEFAULT; + /** * Retrieve a new Parcel object from the pool. */ @@ -352,6 +379,7 @@ public final class Parcel { if (DEBUG_RECYCLE) { p.mStack = new RuntimeException(); } + p.mReadWriteHelper = ReadWriteHelper.DEFAULT; return p; } } @@ -385,6 +413,16 @@ public final class Parcel { } } + /** + * Set a {@link ReadWriteHelper}, which can be used to avoid having duplicate strings, for + * example. + * + * @hide + */ + public void setReadWriteHelper(ReadWriteHelper helper) { + mReadWriteHelper = helper != null ? helper : ReadWriteHelper.DEFAULT; + } + /** @hide */ public static native long getGlobalAllocSize(); @@ -625,6 +663,17 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writeString(String val) { + mReadWriteHelper.writeString(this, val); + } + + /** + * Write a string without going though a {@link ReadWriteHelper}. Subclasses of + * {@link ReadWriteHelper} must use this method instead of {@link #writeString} to avoid + * infinity recursive calls. + * + * @hide + */ + public void writeStringNoHelper(String val) { nativeWriteString(mNativePtr, val); } @@ -1996,6 +2045,17 @@ public final class Parcel { * Read a string value from the parcel at the current dataPosition(). */ public final String readString() { + return mReadWriteHelper.readString(this); + } + + /** + * Read a string without going though a {@link ReadWriteHelper}. Subclasses of + * {@link ReadWriteHelper} must use this method instead of {@link #readString} to avoid + * infinity recursive calls. + * + * @hide + */ + public String readStringNoHelper() { return nativeReadString(mNativePtr); } @@ -2996,6 +3056,7 @@ public final class Parcel { if (mOwnsNativeParcelObject) { updateNativeSize(nativeFreeBuffer(mNativePtr)); } + mReadWriteHelper = ReadWriteHelper.DEFAULT; } private void destroy() { @@ -3006,6 +3067,7 @@ public final class Parcel { } mNativePtr = 0; } + mReadWriteHelper = null; } @Override