Chromium Code Reviews| Index: web_apks/minting_example/src/org/chromium/minting/MintingApplication.java |
| diff --git a/web_apks/minting_example/src/org/chromium/minting/MintingApplication.java b/web_apks/minting_example/src/org/chromium/minting/MintingApplication.java |
| index 0ce9de0ae2e63fd1c23fee9fe67d0655e037dcde..eb54f733342eed09ed409a41e21e784bed5caf93 100644 |
| --- a/web_apks/minting_example/src/org/chromium/minting/MintingApplication.java |
| +++ b/web_apks/minting_example/src/org/chromium/minting/MintingApplication.java |
| @@ -9,9 +9,8 @@ import android.content.Context; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.util.Log; |
| -import java.io.File; |
| import java.lang.reflect.Array; |
| -import java.lang.reflect.Field; |
| +import java.util.List; |
| /** |
| * Example application for a minted APK. |
| @@ -25,49 +24,102 @@ public class MintingApplication extends Application { |
| private Context mRemoteContext = null; |
| private static final String TAG = "cr.MintingApplication"; |
| - private static final String SECONDARY_FOLDER_NAME = "code_cache" + File.separator |
| - + "secondary-dexes"; |
| - |
| - private static Field findField(Object instance, String name) throws NoSuchFieldException { |
| - for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) { |
| - try { |
| - Field field = clazz.getDeclaredField(name); |
| - if (!field.isAccessible()) { |
| - field.setAccessible(true); |
| - } |
| - return field; |
| - } catch (NoSuchFieldException e) { |
| - // ignore and search next |
| - } |
| - } |
| - throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass()); |
| - } |
| - |
| /** |
| - * Copy all the objects from a specified field of the second instance to the same field of the |
| - * first instance. As a result, the given field of the first instance will contain all the |
| + * Copy all the objects from a specified field of the hostInstance to the same field of the |
| + * mintInstance. As a result, the given field of the mintInstance will contain all the |
| * objects from both instances. |
| - * @param instance the first instance which contains a field of the given fieldName. |
| + * @param mintInstance the first instance which contains a field of the given fieldName. |
| * @param fieldName the name of field to expand to an Object array. |
| - * @param instance2 the second instance which has a field of the given fieldName. |
| + * @param hostInstance the second instance which has a field of the given fieldName. |
| * @throws NoSuchFieldException |
| * @throws IllegalArgumentException |
| * @throws IllegalAccessException |
| */ |
| - private static void expandFieldArray(Object instance, String fieldName, |
| - Object instance2) throws NoSuchFieldException, IllegalArgumentException, |
| + private static void expandField(Object mintInstance, String fieldName, Object hostInstance) |
| + throws NoSuchFieldException, IllegalArgumentException, |
| IllegalAccessException { |
| - Field jlrField = findField(instance, fieldName); |
| - Object[] original = (Object[]) jlrField.get(instance); |
| - Object[] additional = (Object[]) jlrField.get(instance2); |
| - Object[] combined = (Object[]) Array.newInstance( |
| - original.getClass().getComponentType(), original.length + additional.length); |
| - System.arraycopy(original, 0, combined, 0, original.length); |
| - System.arraycopy(additional, 0, combined, original.length, additional.length); |
| - jlrField.set(instance, combined); |
| + try { |
| + Object hostCurrentDirs = Reflect.getField(hostInstance, fieldName); |
| + Object mintCurrentDirs = Reflect.getField(mintInstance, fieldName); |
| + // Switched from an array to an ArrayList in Lollipop. |
| + if (mintCurrentDirs instanceof List) { |
| + List<Object> mintDirsAsList = (List<Object>) mintCurrentDirs; |
| + concatAndRemoveEndDuplicates(mintDirsAsList, (List<Object>) hostCurrentDirs); |
|
Yaron
2016/02/19 22:10:20
Are the duplicates a concern? Are they not just ig
Xi Han
2016/02/24 19:09:38
If simply inserting the Chrome's array before WebA
Yaron
2016/02/25 05:32:50
Perhaps you're reight and we shouldn't have the /v
|
| + List<Object> combined = (List<Object>) Reflect.getField(mintInstance, fieldName); |
| + for (Object o: combined) { |
|
Yaron
2016/02/19 22:10:20
nit: the previous line and this section are just f
Xi Han
2016/02/24 19:09:38
Done.
Yaron
2016/02/25 05:32:50
You can also get rid of Reflect.getField from both
Xi Han
2016/02/25 14:55:38
Good catch, removed.
|
| + Log.w(TAG, "element in the path of " + fieldName + " : " + o.toString()); |
| + } |
| + } else { |
| + Object[] mintDirsAsArray = (Object[]) mintCurrentDirs; |
| + Object[] hostDirsAsArray = (Object[]) hostCurrentDirs; |
| + Reflect.setField(mintInstance, fieldName, |
| + concatAndRemoveEndDuplicates(mintDirsAsArray, hostDirsAsArray)); |
| + Object[] combined = (Object[]) Reflect.getField(mintInstance, fieldName); |
| + for (Object o: combined) { |
| + Log.w(TAG, "element in the path of " + fieldName + ": " + o.toString()); |
| + } |
| + } |
| + } catch (ReflectiveOperationException e) { |
| + e.printStackTrace(); |
| + } |
| + } |
| + |
| + /** |
| + * Combines two arrays into a new array. |
| + * If the two arrays end with duplicated elements, keep one copy only. For example, |
| + * the first array is [A, B, C, D], and the second array is [E, F, C, D], the combined one is |
| + * [A, B, E, F, C, D]. The unique elements are stored before the duplicates. |
| + * The arrays must be of the same type. |
| + * @param mintArrays: the array from a WebAPK |
| + * @param hostArrays: the array from the host |
| + * @return: a combined array consists of [WebAPK unique elements, host's unique elements, |
| + * duplicated one] |
| + */ |
| + private static Object[] concatAndRemoveEndDuplicates(Object[] mintArrays, Object[] hostArrays) { |
| + int duplicate1 = mintArrays.length - 1; |
| + int duplicate2 = hostArrays.length - 1; |
| + int count = 0; |
| + while (duplicate1 >= 0 && duplicate2 >= 0 |
| + && mintArrays[duplicate1].toString().equals(hostArrays[duplicate2].toString())) { |
| + --duplicate1; |
| + --duplicate2; |
| + ++count; |
| + } |
| + Object[] combined = (Object[]) Array.newInstance(mintArrays.getClass().getComponentType(), |
| + mintArrays.length + hostArrays.length - count); |
| + if (mintArrays.length - count > 0) { |
| + System.arraycopy(mintArrays, 0, combined, 0, mintArrays.length - count); |
| + } |
| + System.arraycopy(hostArrays, 0, combined, mintArrays.length - count, hostArrays.length); |
| + return combined; |
| } |
| - private void addExternalLoader() { |
| + /** |
| + * Add the unique elements of the second list to the first one. |
| + * If the two lists end with duplicated elements, keep one copy only. For example, |
| + * the first list is [A, B, C, D], and the second list is [E, F, C, D], the combined one is |
| + * [A, B, E, F, C, D]. The unique elements are store before the duplicates. |
| + * The lists must be of the same type. |
| + * @param mintList: the list from a WebAPK |
| + * @param hostList: the list from the host |
| + * @return: a combined list consists of [WebAPK unique elements, host's unique elements, |
| + * duplicated one] |
| + */ |
| + private static void concatAndRemoveEndDuplicates(List<Object> mintList, List<Object> hostList) { |
| + int duplicate1 = mintList.size() - 1; |
| + int duplicate2 = hostList.size() - 1; |
| + while (duplicate1 >= 0 && duplicate2 >= 0 |
| + && mintList.get(duplicate1).toString().equals(hostList.get(duplicate2).toString())) |
| + { |
| + --duplicate1; |
| + --duplicate2; |
| + } |
| + for (int i = 0; i < duplicate2 + 1; i++) { |
| + mintList.add(duplicate1 + i, hostList.get(i)); |
| + } |
| + } |
| + |
| + private void addExternalLoader() throws ReflectiveOperationException { |
| if (mRemoteContext == null) { |
| Log.w(TAG, "Failed to add external loader since the remote context is null."); |
| return; |
| @@ -75,15 +127,28 @@ public class MintingApplication extends Application { |
| ClassLoader externalLoader = mRemoteContext.getClassLoader(); |
| ClassLoader mintLoader = getClassLoader(); |
| - try { |
| - Field field = findField(externalLoader, "pathList"); |
| - Object dexOriginal = field.get(mintLoader); |
| - Object dexAdditional = field.get(externalLoader); |
| - expandFieldArray(dexOriginal, "dexElements", dexAdditional); |
| - } catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e1) { |
| - e1.printStackTrace(); |
| - throw new RuntimeException(e1); |
| + Object extDexPathList = Reflect.getField(externalLoader, "pathList"); |
| + Object mintDexPathList = Reflect.getField(mintLoader, "pathList"); |
| + expandField(mintDexPathList, "dexElements", extDexPathList); |
| + } |
| + |
| + private void addNativeLibrarySearchPath() throws ReflectiveOperationException { |
| + if (mRemoteContext == null) { |
| + Log.w(TAG, "Failed to add external loader since the remote context is null."); |
| + return; |
| } |
| + ClassLoader externalLoader = mRemoteContext.getClassLoader(); |
| + ClassLoader mintLoader = getClassLoader(); |
| + |
| + // Since both WebAPK and its host have the "system/lib" and "vendor/lib" at the end of |
| + // the native library directory path list, we want to add host's directories before the |
| + // system directory, and only keep one copy of the system directories. |
| + // This is because when System.loadLibrary() is called, we want it search the host's |
| + // native library directories first. If a ".so" file doesn't exit, then search /system/lib. |
| + Object extDexPathList = Reflect.getField(externalLoader, "pathList"); |
| + Object mintDexPathList = Reflect.getField(mintLoader, "pathList"); |
| + expandField(mintDexPathList, "nativeLibraryDirectories", extDexPathList); |
| + expandField(mintDexPathList, "nativeLibraryPathElements", extDexPathList); |
| } |
| @Override |
| @@ -96,7 +161,12 @@ public class MintingApplication extends Application { |
| mRemoteContext = getApplicationContext().createPackageContext( |
| packageString, |
| Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE); |
| - addExternalLoader(); |
| + try { |
| + addExternalLoader(); |
| + addNativeLibrarySearchPath(); |
| + } catch (ReflectiveOperationException e) { |
| + e.printStackTrace(); |
| + } |
| Log.w(TAG, "Successfully add external loader."); |
| } catch (NameNotFoundException e) { |
| e.printStackTrace(); |