Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d48d873a authored by Eric Miao's avatar Eric Miao
Browse files

Add compressed bitmaps to be included in `am dumpheap`

Bug: 328443220

`android.graphics.Bitmap` class used to have a field of byte array
`mBuffer` for its content. This allowed the bitmap content to be
included as part of `am dumpheap`. However, this field was removed
when Bitmap was migrated to use native memory.

This CL allows contents of bitmaps to be compressed and included as
part of `am dumpheap`, with added command line switch '-b <format>'.
For example, the command below will include the contents of the
bitmaps compressed in PNG format as part of the heap dump.

  `am dumpheap -b png com.google.android.apps.photos`

This is done with a few key changes below:

  1. Every bitmap instance created will be tracked by a static
     `WeakHashMap`. This is so that 1) the bitmap instances are
     used as weak keys and can be garbage collected normally,
     and 2) when a bitmap instance is garbage collected, its
     entry in `WeakHashMap` will also be removed, so the size
     of the map itself is limited

  2. A static field `Bitmap.dumpData` is introduced, and will
     record every bitmap's `nativePtr` and its compressed
     content when bitmap dump is enabled during a heap dump

  3. `Bitmap.dumpData` will be cleared after the heap is dumped,
     the recorded information as well as buffers with compressed
     contents will be garbage collected thereafter.

Change-Id: I37b6ea6b947565d1ac5a6bbc5b462c3ceedebec1
parent 9a885d74
Loading
Loading
Loading
Loading
+11 −2
Original line number Diff line number Diff line
@@ -1050,6 +1050,8 @@ public final class ActivityThread extends ClientTransactionHandler
        public boolean managed;
        public boolean mallocInfo;
        public boolean runGc;
        // compression format to dump bitmaps, null if no bitmaps to be dumped
        public String dumpBitmaps;
        String path;
        ParcelFileDescriptor fd;
        RemoteCallback finishCallback;
@@ -1442,11 +1444,12 @@ public final class ActivityThread extends ClientTransactionHandler
        }

        @Override
        public void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, String path,
                ParcelFileDescriptor fd, RemoteCallback finishCallback) {
        public void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, String dumpBitmaps,
                String path, ParcelFileDescriptor fd, RemoteCallback finishCallback) {
            DumpHeapData dhd = new DumpHeapData();
            dhd.managed = managed;
            dhd.mallocInfo = mallocInfo;
            dhd.dumpBitmaps = dumpBitmaps;
            dhd.runGc = runGc;
            dhd.path = path;
            try {
@@ -6731,6 +6734,9 @@ public final class ActivityThread extends ClientTransactionHandler
            System.runFinalization();
            System.gc();
        }
        if (dhd.dumpBitmaps != null) {
            Bitmap.dumpAll(dhd.dumpBitmaps);
        }
        try (ParcelFileDescriptor fd = dhd.fd) {
            if (dhd.managed) {
                Debug.dumpHprofData(dhd.path, fd.getFileDescriptor());
@@ -6758,6 +6764,9 @@ public final class ActivityThread extends ClientTransactionHandler
        if (dhd.finishCallback != null) {
            dhd.finishCallback.sendResult(null);
        }
        if (dhd.dumpBitmaps != null) {
            Bitmap.dumpAll(null); // clear dump
        }
    }

    final void handleDispatchPackageBroadcast(int cmd, String[] packages) {
+1 −1
Original line number Diff line number Diff line
@@ -392,7 +392,7 @@ interface IActivityManager {
    oneway void getMimeTypeFilterAsync(in Uri uri, int userId, in RemoteCallback resultCallback);
    // Cause the specified process to dump the specified heap.
    boolean dumpHeap(in String process, int userId, boolean managed, boolean mallocInfo,
            boolean runGc, in String path, in ParcelFileDescriptor fd,
            boolean runGc, in String dumpBitmaps, in String path, in ParcelFileDescriptor fd,
            in RemoteCallback finishCallback);
    @UnsupportedAppUsage
    boolean isUserRunning(int userid, int flags);
+2 −1
Original line number Diff line number Diff line
@@ -116,7 +116,8 @@ oneway interface IApplicationThread {
    void scheduleSuicide();
    void dispatchPackageBroadcast(int cmd, in String[] packages);
    void scheduleCrash(in String msg, int typeId, in Bundle extras);
    void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, in String path,
    void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc,
            in String dumpBitmaps, in String path,
            in ParcelFileDescriptor fd, in RemoteCallback finishCallback);
    void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix,
            in String[] args);
+91 −0
Original line number Diff line number Diff line
@@ -41,12 +41,15 @@ import dalvik.annotation.optimization.CriticalNative;
import libcore.util.NativeAllocationRegistry;

import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.WeakHashMap;

public final class Bitmap implements Parcelable {
    private static final String TAG = "Bitmap";
@@ -119,6 +122,11 @@ public final class Bitmap implements Parcelable {
        return sDefaultDensity;
    }

    /**
     * @hide
     */
    private static final WeakHashMap<Bitmap, Void> sAllBitmaps = new WeakHashMap<>();

    /**
     * Private constructor that must receive an already allocated native bitmap
     * int (pointer).
@@ -162,6 +170,9 @@ public final class Bitmap implements Parcelable {
                    Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount);
        }
        registry.registerNativeAllocation(this, nativeBitmap);
        synchronized (Bitmap.class) {
          sAllBitmaps.put(this, null);
        }
    }

    /**
@@ -1509,6 +1520,86 @@ public final class Bitmap implements Parcelable {
        final int nativeInt;
    }

    /**
     * @hide
     */
    private static final class DumpData {
        private int count;
        private int format;
        private long[] natives;
        private byte[][] buffers;
        private int max;

        public DumpData(@NonNull CompressFormat format, int max) {
            this.max = max;
            this.format = format.nativeInt;
            this.natives = new long[max];
            this.buffers = new byte[max][];
            this.count = 0;
        }

        public void add(long nativePtr, byte[] buffer) {
            natives[count] = nativePtr;
            buffers[count] = buffer;
            count = (count >= max) ? max : count + 1;
        }

        public int size() {
            return count;
        }
    }

    /**
     * @hide
     */
    private static DumpData dumpData = null;


    /**
     * @hide
     *
     * Dump all the bitmaps with their contents compressed into dumpData
     *
     * @param format  format of the compressed image, null to clear dump data
     */
    public static void dumpAll(@Nullable String format) {
        if (format == null) {
            /* release the dump data */
            dumpData = null;
            return;
        }
        final CompressFormat fmt;
        if (format.equals("jpg") || format.equals("jpeg")) {
            fmt = CompressFormat.JPEG;
        } else if (format.equals("png")) {
            fmt = CompressFormat.PNG;
        } else if (format.equals("webp")) {
            fmt = CompressFormat.WEBP_LOSSLESS;
        } else {
            Log.w(TAG, "No bitmaps dumped: unrecognized format " + format);
            return;
        }

        final ArrayList<Bitmap> allBitmaps;
        synchronized (Bitmap.class) {
          allBitmaps = new ArrayList<>(sAllBitmaps.size());
          for (Bitmap bitmap : sAllBitmaps.keySet()) {
            if (bitmap != null && !bitmap.isRecycled()) {
              allBitmaps.add(bitmap);
            }
          }
        }

        dumpData = new DumpData(fmt, allBitmaps.size());
        for (Bitmap bitmap : allBitmaps) {
            ByteArrayOutputStream bas = new ByteArrayOutputStream();
            if (bitmap.compress(fmt, 90, bas)) {
                dumpData.add(bitmap.getNativeInstance(), bas.toByteArray());
            }
        }
        Log.i(TAG, dumpData.size() + "/" + allBitmaps.size() + " bitmaps dumped");
    }

    /**
     * Number of bytes of temp storage we use for communicating between the
     * native compressor and the java OutputStream.
+4 −2
Original line number Diff line number Diff line
@@ -17297,7 +17297,8 @@ public class ActivityManagerService extends IActivityManager.Stub
    @Override
    public boolean dumpHeap(String process, int userId, boolean managed, boolean mallocInfo,
            boolean runGc, String path, ParcelFileDescriptor fd, RemoteCallback finishCallback) {
            boolean runGc, String dumpBitmaps,
            String path, ParcelFileDescriptor fd, RemoteCallback finishCallback) {
        try {
            // note: hijacking SET_ACTIVITY_WATCHER, but should be changed to
            // its own permission (same as profileControl).
@@ -17331,7 +17332,8 @@ public class ActivityManagerService extends IActivityManager.Stub
                        }
                    }, null);
                thread.dumpHeap(managed, mallocInfo, runGc, path, fd, intermediateCallback);
                thread.dumpHeap(managed, mallocInfo, runGc, dumpBitmaps,
                                path, fd, intermediateCallback);
                fd = null;
                return true;
            }
Loading