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 @SuppressWarnings("FieldCanBeLocal") 115 private final Class<T> mClass; 116 DelegateManager(Class<T> theClass)117 public DelegateManager(Class<T> theClass) { 118 mClass = theClass; 119 } 120 dump(PrintStream out)121 public synchronized static void dump(PrintStream out) { 122 for (Object reference : sJavaReferences) { 123 int idx = sDelegates.indexOfValue(reference); 124 out.printf("[%d] %s\n", sDelegates.keyAt(idx), reference.getClass().getSimpleName()); 125 } 126 out.printf("\nTotal number of objects: %d\n", sJavaReferences.size()); 127 } 128 129 /** 130 * Returns the delegate from the given native int. 131 * <p> 132 * If the int is zero, then this will always return null. 133 * <p> 134 * If the int is non zero and the delegate is not found, this will throw an assert. 135 * 136 * @param native_object the native int. 137 * @return the delegate or null if not found. 138 */ 139 @Nullable getDelegate(long native_object)140 public T getDelegate(long native_object) { 141 if (native_object > 0) { 142 Object delegate; 143 synchronized (DelegateManager.class) { 144 delegate = sDelegates.get(native_object); 145 } 146 147 if (Debug.DEBUG) { 148 if (delegate == null) { 149 System.err.println("Unknown " + mClass.getSimpleName() + " with int " + 150 native_object); 151 } 152 } 153 154 assert delegate != null; 155 //noinspection unchecked 156 return (T)delegate; 157 } 158 return null; 159 } 160 161 /** 162 * Adds a delegate to the manager and returns the native int used to identify it. 163 * @param newDelegate the delegate to add 164 * @return a unique native int to identify the delegate 165 */ addNewDelegate(T newDelegate)166 public long addNewDelegate(T newDelegate) { 167 long native_object = sDelegateCounter.getAndIncrement(); 168 synchronized (DelegateManager.class) { 169 sDelegates.put(native_object, newDelegate); 170 // Only for development: assert !sJavaReferences.contains(newDelegate); 171 sJavaReferences.add(newDelegate); 172 } 173 174 if (Debug.DEBUG) { 175 System.out.println( 176 "New " + mClass.getSimpleName() + " " + 177 "with int " + 178 native_object); 179 } 180 181 return native_object; 182 } 183 184 /** 185 * Removes the main reference on the given delegate. 186 * @param native_object the native integer representing the delegate. 187 */ removeJavaReferenceFor(long native_object)188 public void removeJavaReferenceFor(long native_object) { 189 synchronized (DelegateManager.class) { 190 T delegate = getDelegate(native_object); 191 192 if (Debug.DEBUG) { 193 System.out.println("Removing main Java ref on " + mClass.getSimpleName() + 194 " with int " + native_object); 195 } 196 197 if (!sJavaReferences.remove(delegate)) { 198 // We didn't have any strong references to the delegate so it might be tracked by 199 // the native allocations tracker. If so, we want to remove that reference to 200 // make it available to collect ASAP. 201 synchronized (sNativeAllocations) { 202 WeakReference<NativeAllocationHolder> holderRef = sNativeAllocationsReferences.get(delegate); 203 NativeAllocationHolder holder = holderRef.get(); 204 if (holder != null) { 205 // We only null the referred delegate. We do not spend the time in finding 206 // the holder in the list and removing it since the "garbage collection" in 207 // markAsNativeAllocation will do it for us. 208 holder.mReferred = null; 209 } 210 } 211 } 212 } 213 } 214 215 /** 216 * This method marks the given native_object as a native allocation of the passed referent. 217 * This means that the lifecycle of the native_object can now be attached to the referent and 218 * if the referent is disposed, we can safely dispose the delegate. 219 * This method is called by the {@link NativeAllocationRegistry_Delegate} and allows the 220 * DelegateManager to remove the strong reference to the delegate. 221 */ markAsNativeAllocation(Object referent, long native_object)222 public void markAsNativeAllocation(Object referent, long native_object) { 223 NativeAllocationHolder holder; 224 synchronized (DelegateManager.class) { 225 T delegate = getDelegate(native_object); 226 if (Debug.DEBUG) { 227 if (delegate == null) { 228 System.err.println("Unknown " + mClass.getSimpleName() + " with int " + 229 native_object); 230 } 231 else { 232 System.err.println("Marking element as native " + native_object); 233 } 234 } 235 236 assert delegate != null; 237 if (sJavaReferences.remove(delegate)) { 238 // We had a strong reference, move to the native allocation tracker. 239 holder = new NativeAllocationHolder(referent, delegate); 240 } 241 else { 242 holder = null; 243 } 244 } 245 246 if (holder != null) { 247 synchronized (sNativeAllocations) { 248 sNativeAllocations.add(holder); 249 // The value references the key in this case but we use a WeakReference value. 250 sNativeAllocationsReferences.put(holder.mReferred, new WeakReference<>(holder)); 251 252 if (++sNativeAllocationsCount % 50 == 0) { 253 // Do garbage collection 254 boolean collected = sNativeAllocations.removeIf(e -> e.mReferent.get() == null); 255 if (Debug.DEBUG && collected) { 256 System.err.println("Elements collected"); 257 } 258 } 259 } 260 } 261 } 262 263 private static class NativeAllocationHolder { 264 private final WeakReference<Object> mReferent; 265 // The referred object is not null so we can null them on demand 266 private Object mReferred; 267 NativeAllocationHolder(Object referent, Object referred)268 private NativeAllocationHolder(Object referent, Object referred) { 269 mReferent = new WeakReference<>(referent); 270 mReferred = referred; 271 } 272 } 273 } 274