1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.layoutlib.bridge.impl; 18 19 import com.android.internal.annotations.GuardedBy; 20 import com.android.layoutlib.bridge.util.Debug; 21 import com.android.layoutlib.bridge.util.SparseWeakArray; 22 23 import android.annotation.Nullable; 24 import android.util.SparseArray; 25 26 import java.io.PrintStream; 27 import java.lang.ref.WeakReference; 28 import java.util.HashSet; 29 import java.util.LinkedList; 30 import java.util.Set; 31 import java.util.WeakHashMap; 32 import java.util.concurrent.atomic.AtomicLong; 33 34 import libcore.util.NativeAllocationRegistry_Delegate; 35 36 /** 37 * Manages native delegates. 38 * 39 * This is used in conjunction with layoublib_create: certain Android java classes are mere 40 * wrappers around a heavily native based implementation, and we need a way to run these classes 41 * in our Android Studio rendering framework without bringing all the native code from the Android 42 * platform. 43 * 44 * Thus we instruct layoutlib_create to modify the bytecode of these classes to replace their 45 * native methods by "delegate calls". 46 * 47 * For example, a native method android.graphics.Matrix.init(...) will actually become 48 * a call to android.graphics.Matrix_Delegate.init(...). 49 * 50 * The Android java classes that use native code uses an int (Java side) to reference native 51 * objects. This int is generally directly the pointer to the C structure counterpart. 52 * Typically a creation method will return such an int, and then this int will be passed later 53 * to a Java method to identify the C object to manipulate. 54 * 55 * Since we cannot use the Java object reference as the int directly, DelegateManager manages the 56 * int -> Delegate class link. 57 * 58 * Native methods usually always have the int as parameters. The first thing the delegate method 59 * will do is call {@link #getDelegate(long)} to get the Java object matching the int. 60 * 61 * Typical native init methods are returning a new int back to the Java class, so 62 * {@link #addNewDelegate(Object)} does the same. 63 * 64 * The JNI references are counted, so we do the same through a {@link WeakReference}. Because 65 * the Java object needs to count as a reference (even though it only holds an int), we use the 66 * following mechanism: 67 * 68 * - {@link #addNewDelegate(Object)} and {@link #removeJavaReferenceFor(long)} adds and removes 69 * the delegate to/from a set. This set holds the reference and prevents the GC from reclaiming 70 * the delegate. 71 * 72 * - {@link #addNewDelegate(Object)} also adds the delegate to a {@link SparseArray} that holds a 73 * {@link WeakReference} to the delegate. This allows the delegate to be deleted automatically 74 * when nothing references it. This means that any class that holds a delegate (except for the 75 * Java main class) must not use the int but the Delegate class instead. The integers must 76 * only be used in the API between the main Java class and the Delegate. 77 * 78 * @param <T> the delegate class to manage 79 */ 80 public final class DelegateManager<T> { 81 private static final SparseWeakArray<Object> sDelegates = new SparseWeakArray<>(); 82 /** Set used to store delegates when their main object holds a reference to them. 83 * This is to ensure that the WeakReference in the SparseWeakArray doesn't get GC'ed 84 * @see #addNewDelegate(Object) 85 * @see #removeJavaReferenceFor(long) 86 */ 87 private static final Set<Object> sJavaReferences = new HashSet<>(); 88 private static final AtomicLong sDelegateCounter = new AtomicLong(1); 89 /** 90 * Tracks "native" allocations. This means that we know of the object in the Java side and we 91 * can attach the delegate lifecycle to the lifecycle of the Java object. If the Java object 92 * is disposed, it means we can get rid of the delegate allocation. 93 * Ideally, we would use a {@link WeakHashMap} but we do not control the equals() method of the 94 * referents so we can not safely rely on them. 95 */ 96 private static final LinkedList<NativeAllocationHolder> sNativeAllocations = new LinkedList<>(); 97 /** 98 * Map that allows to do a quick lookup of delegates that have been marked as native 99 * allocations. 100 * This allows us to quickly check if, when a manual dispose happens, there is work we have 101 * to do. 102 */ 103 @GuardedBy("sNativeAllocations") 104 private static final WeakHashMap<Object, WeakReference<NativeAllocationHolder>> 105 sNativeAllocationsReferences = new WeakHashMap<>(); 106 /** 107 * Counter of the number of native allocations. We use this counter to trigger the collection 108 * of unlinked references in the sNativeAllocations list. We do not need to do this process 109 * on every allocation so only run it every 50 allocations. 110 */ 111 @GuardedBy("sNativeAllocations") 112 private static long sNativeAllocationsCount = 0; 113 114 private final Class<T> mClass; 115 DelegateManager(Class<T> theClass)116 public DelegateManager(Class<T> theClass) { 117 mClass = theClass; 118 } 119 dump(PrintStream out)120 public synchronized static void dump(PrintStream out) { 121 for (Object reference : sJavaReferences) { 122 int idx = sDelegates.indexOfValue(reference); 123 out.printf("[%d] %s\n", sDelegates.keyAt(idx), reference.getClass().getSimpleName()); 124 } 125 out.printf("\nTotal number of objects: %d\n", sJavaReferences.size()); 126 } 127 128 /** 129 * Returns the delegate from the given native int. 130 * <p> 131 * If the int is zero, then this will always return null. 132 * <p> 133 * If the int is non zero and the delegate is not found, this will throw an assert. 134 * 135 * @param native_object the native int. 136 * @return the delegate or null if not found. 137 */ 138 @Nullable getDelegate(long native_object)139 public T getDelegate(long native_object) { 140 if (native_object > 0) { 141 Object delegate; 142 synchronized (DelegateManager.class) { 143 delegate = sDelegates.get(native_object); 144 } 145 146 if (Debug.DEBUG) { 147 if (delegate == null) { 148 System.err.println("Unknown " + mClass.getSimpleName() + " with int " + 149 native_object); 150 } 151 } 152 153 assert delegate != null; 154 //noinspection unchecked 155 return (T)delegate; 156 } 157 return null; 158 } 159 160 /** 161 * Adds a delegate to the manager and returns the native int used to identify it. 162 * @param newDelegate the delegate to add 163 * @return a unique native int to identify the delegate 164 */ addNewDelegate(T newDelegate)165 public long addNewDelegate(T newDelegate) { 166 long native_object = sDelegateCounter.getAndIncrement(); 167 synchronized (DelegateManager.class) { 168 sDelegates.put(native_object, newDelegate); 169 // Only for development: assert !sJavaReferences.contains(newDelegate); 170 sJavaReferences.add(newDelegate); 171 } 172 173 if (Debug.DEBUG) { 174 System.out.println( 175 "New " + mClass.getSimpleName() + " " + 176 "with int " + 177 native_object); 178 } 179 180 return native_object; 181 } 182 183 /** 184 * Removes the main reference on the given delegate. 185 * @param native_object the native integer representing the delegate. 186 */ removeJavaReferenceFor(long native_object)187 public void removeJavaReferenceFor(long native_object) { 188 synchronized (DelegateManager.class) { 189 T delegate = getDelegate(native_object); 190 191 if (Debug.DEBUG) { 192 System.out.println("Removing main Java ref on " + mClass.getSimpleName() + 193 " with int " + native_object); 194 } 195 196 if (!sJavaReferences.remove(delegate)) { 197 // We didn't have any strong references to the delegate so it might be tracked by 198 // the native allocations tracker. If so, we want to remove that reference to 199 // make it available to collect ASAP. 200 synchronized (sNativeAllocations) { 201 WeakReference<NativeAllocationHolder> holderRef = sNativeAllocationsReferences.get(delegate); 202 NativeAllocationHolder holder = holderRef.get(); 203 if (holder != null) { 204 // We only null the referred delegate. We do not spend the time in finding 205 // the holder in the list and removing it since the "garbage collection" in 206 // markAsNativeAllocation will do it for us. 207 holder.mReferred = null; 208 } 209 } 210 } 211 } 212 } 213 214 /** 215 * This method marks the given native_object as a native allocation of the passed referent. 216 * This means that the lifecycle of the native_object can now be attached to the referent and 217 * if the referent is disposed, we can safely dispose the delegate. 218 * This method is called by the {@link NativeAllocationRegistry_Delegate} and allows the 219 * DelegateManager to remove the strong reference to the delegate. 220 */ markAsNativeAllocation(Object referent, long native_object)221 public void markAsNativeAllocation(Object referent, long native_object) { 222 NativeAllocationHolder holder; 223 synchronized (DelegateManager.class) { 224 T delegate = getDelegate(native_object); 225 if (Debug.DEBUG) { 226 if (delegate == null) { 227 System.err.println("Unknown " + mClass.getSimpleName() + " with int " + 228 native_object); 229 } 230 else { 231 System.err.println("Marking element as native " + native_object); 232 } 233 } 234 235 assert delegate != null; 236 if (sJavaReferences.remove(delegate)) { 237 // We had a strong reference, move to the native allocation tracker. 238 holder = new NativeAllocationHolder(referent, delegate); 239 } 240 else { 241 holder = null; 242 } 243 } 244 245 if (holder != null) { 246 synchronized (sNativeAllocations) { 247 sNativeAllocations.add(holder); 248 // The value references the key in this case but we use a WeakReference value. 249 sNativeAllocationsReferences.put(holder.mReferred, new WeakReference<>(holder)); 250 251 if (++sNativeAllocationsCount % 50 == 0) { 252 // Do garbage collection 253 boolean collected = sNativeAllocations.removeIf(e -> e.mReferent.get() == null); 254 if (Debug.DEBUG && collected) { 255 System.err.println("Elements collected"); 256 } 257 } 258 } 259 } 260 } 261 262 private static class NativeAllocationHolder { 263 private final WeakReference<Object> mReferent; 264 // The referred object is not null so we can null them on demand 265 private Object mReferred; 266 NativeAllocationHolder(Object referent, Object referred)267 private NativeAllocationHolder(Object referent, Object referred) { 268 mReferent = new WeakReference<>(referent); 269 mReferred = referred; 270 } 271 } 272 } 273