Files
frameworks_base/graphics/java/android/graphics/Picture.java
Hans Boehm ffa84e008c Reduce risk of memory corruption due to finalization.
Many classes in graphics/java and elsewhere deallocate native memory
in a finalizer on the assumption that instance methods can no longer
be called once the finalizer has been called.  This is incorrect if
the object can be used, possibly indirectly, from another finalizer,
possibly one in the application.

This is the initial installment of a patch to cause such post-finalization
uses to at least see a null pointer rather than causing memory corruption
by accessing deallocated native memory. This should make it possible to
identify and fix such finalization ordering issues.

There are more graphics classes that need this treatment, and probably
many more in other subsystems.

This solution is < 100% effective if finalizers can be invoked
concurrently.  We currently promise that they aren't.

(In my opinion, the real cause here is a language spec bug.  But that ship
has sailed.)

Bug: 18178237
Change-Id: I844cf1e0fbb190407389c4f8e8f072752cca6198
2015-09-08 18:27:36 -07:00

217 lines
7.9 KiB
Java

/*
* Copyright (C) 2007 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.graphics;
import java.io.InputStream;
import java.io.OutputStream;
/**
* A Picture records drawing calls (via the canvas returned by beginRecording)
* and can then play them back into Canvas (via {@link Picture#draw(Canvas)} or
* {@link Canvas#drawPicture(Picture)}).For most content (e.g. text, lines, rectangles),
* drawing a sequence from a picture can be faster than the equivalent API
* calls, since the picture performs its playback without incurring any
* method-call overhead.
*/
public class Picture {
private Canvas mRecordingCanvas;
private long mNativePicture;
private static final int WORKING_STREAM_STORAGE = 16 * 1024;
/**
* Creates an empty picture that is ready to record.
*/
public Picture() {
this(nativeConstructor(0));
}
/**
* Create a picture by making a copy of what has already been recorded in
* src. The contents of src are unchanged, and if src changes later, those
* changes will not be reflected in this picture.
*/
public Picture(Picture src) {
this(nativeConstructor(src != null ? src.mNativePicture : 0));
}
private Picture(long nativePicture) {
if (nativePicture == 0) {
throw new RuntimeException();
}
mNativePicture = nativePicture;
}
@Override
protected void finalize() throws Throwable {
try {
nativeDestructor(mNativePicture);
mNativePicture = 0;
} finally {
super.finalize();
}
}
/**
* To record a picture, call beginRecording() and then draw into the Canvas
* that is returned. Nothing we appear on screen, but all of the draw
* commands (e.g. {@link Canvas#drawRect(Rect, Paint)}) will be recorded.
* To stop recording, call endRecording(). After endRecording() the Canvas
* that was returned must no longer be used, and nothing should be drawn
* into it.
*/
public Canvas beginRecording(int width, int height) {
long ni = nativeBeginRecording(mNativePicture, width, height);
mRecordingCanvas = new RecordingCanvas(this, ni);
return mRecordingCanvas;
}
/**
* Call endRecording when the picture is built. After this call, the picture
* may be drawn, but the canvas that was returned by beginRecording must not
* be used anymore. This is automatically called if {@link Picture#draw}
* or {@link Canvas#drawPicture(Picture)} is called.
*/
public void endRecording() {
if (mRecordingCanvas != null) {
mRecordingCanvas = null;
nativeEndRecording(mNativePicture);
}
}
/**
* Get the width of the picture as passed to beginRecording. This
* does not reflect (per se) the content of the picture.
*/
public int getWidth() {
return nativeGetWidth(mNativePicture);
}
/**
* Get the height of the picture as passed to beginRecording. This
* does not reflect (per se) the content of the picture.
*/
public int getHeight() {
return nativeGetHeight(mNativePicture);
}
/**
* Draw this picture on the canvas.
* <p>
* Prior to {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this call could
* have the side effect of changing the matrix and clip of the canvas
* if this picture had imbalanced saves/restores.
*
* <p>
* <strong>Note:</strong> This forces the picture to internally call
* {@link Picture#endRecording()} in order to prepare for playback.
*
* @param canvas The picture is drawn to this canvas
*/
public void draw(Canvas canvas) {
if (canvas.isHardwareAccelerated()) {
throw new IllegalArgumentException(
"Picture playback is only supported on software canvas.");
}
if (mRecordingCanvas != null) {
endRecording();
}
nativeDraw(canvas.getNativeCanvasWrapper(), mNativePicture);
}
/**
* Create a new picture (already recorded) from the data in the stream. This
* data was generated by a previous call to writeToStream(). Pictures that
* have been persisted across device restarts are not guaranteed to decode
* properly and are highly discouraged.
*
* <p>
* <strong>Note:</strong> a picture created from an input stream cannot be
* replayed on a hardware accelerated canvas.
*
* @see #writeToStream(java.io.OutputStream)
* @deprecated The recommended alternative is to not use writeToStream and
* instead draw the picture into a Bitmap from which you can persist it as
* raw or compressed pixels.
*/
@Deprecated
public static Picture createFromStream(InputStream stream) {
return new Picture(nativeCreateFromStream(stream, new byte[WORKING_STREAM_STORAGE]));
}
/**
* Write the picture contents to a stream. The data can be used to recreate
* the picture in this or another process by calling createFromStream(...)
* The resulting stream is NOT to be persisted across device restarts as
* there is no guarantee that the Picture can be successfully reconstructed.
*
* <p>
* <strong>Note:</strong> a picture created from an input stream cannot be
* replayed on a hardware accelerated canvas.
*
* @see #createFromStream(java.io.InputStream)
* @deprecated The recommended alternative is to draw the picture into a
* Bitmap from which you can persist it as raw or compressed pixels.
*/
@Deprecated
public void writeToStream(OutputStream stream) {
// do explicit check before calling the native method
if (stream == null) {
throw new NullPointerException();
}
if (!nativeWriteToStream(mNativePicture, stream,
new byte[WORKING_STREAM_STORAGE])) {
throw new RuntimeException();
}
}
// return empty picture if src is 0, or a copy of the native src
private static native long nativeConstructor(long nativeSrcOr0);
private static native long nativeCreateFromStream(InputStream stream, byte[] storage);
private static native int nativeGetWidth(long nativePicture);
private static native int nativeGetHeight(long nativePicture);
private static native long nativeBeginRecording(long nativeCanvas, int w, int h);
private static native void nativeEndRecording(long nativeCanvas);
private static native void nativeDraw(long nativeCanvas, long nativePicture);
private static native boolean nativeWriteToStream(long nativePicture,
OutputStream stream, byte[] storage);
private static native void nativeDestructor(long nativePicture);
private static class RecordingCanvas extends Canvas {
private final Picture mPicture;
public RecordingCanvas(Picture pict, long nativeCanvas) {
super(nativeCanvas);
mPicture = pict;
}
@Override
public void setBitmap(Bitmap bitmap) {
throw new RuntimeException("Cannot call setBitmap on a picture canvas");
}
@Override
public void drawPicture(Picture picture) {
if (mPicture == picture) {
throw new RuntimeException("Cannot draw a picture into its recording canvas");
}
super.drawPicture(picture);
}
}
}