A regression has been introduced into Android 4.3. Code that used to work in previous versions of Android now causes a native crash which shuts down the process.
The crash occurs when drawing an image that is larger than 32 kb into a canvas which is being recorded by a Picture
object which is in turn written to a stream via writeToStream()
.
The crash is occurring down in Skia when trying to write away a string (Which I believe is the Uri of the image object).
I/DEBUG(122): #00 pc 0001e3bc /system/lib/libc.so (strlen+72)
I/DEBUG(122): #01 pc 000d9858 /system/lib/libskia.so (SkWriter32::writeString(char const*, unsigned int)+256)
I/DEBUG(122): #02 pc 00113d68 /system/lib/libskia.so (SkImageRef_ashmem::flatten(SkFlattenableWriteBuffer&) const+44)
The following program shows how to reproduce this issue. All that is need is a layout with a button that has the id 'button'.
public class MainActivity extends Activity {
static final String IMAGE_FILE = Environment.getExternalStorageDirectory() + "/test.jpg";
static final String SKIA_FILE = Environment.getExternalStorageDirectory() + "/test.skia";
private static Bitmap loadBitmap(final String filename) {
Bitmap bitmap = null;
FileInputStream is;
try {
is = new FileInputStream(filename);
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inInputShareable = true;
options.inPurgeable = true;
bitmap = BitmapFactory.decodeFileDescriptor(is.getFD(), null, options);
is.close();
} catch (final FileNotFoundException e) {
e.printStackTrace();
} catch (final IOException ex) {
ex.printStackTrace();
}
return bitmap;
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
final Runnable runnable = new Runnable() {
@Override
public void run() {
// Create a Canvas and begin recording
final Picture picture = new Picture();
final Canvas canvas = picture.beginRecording(1024, 1024);
// De-compress an image from file
final Bitmap bitmap = loadBitmap(IMAGE_FILE);
// If present draw the image to the canvas and end
// recording
if (bitmap != null) {
canvas.drawBitmap(bitmap, new Matrix(), null);
}
picture.endRecording();
// Write out the Picture object to a Skia File.
FileOutputStream os;
try {
os = new FileOutputStream(SKIA_FILE);
picture.writeToStream(os);
os.close();
} catch (final FileNotFoundException e) {
e.printStackTrace();
} catch (final IOException ex) {
ex.printStackTrace();
}
}
};
new Thread(runnable).start();
}
});
}
}
The two lines setting the BitmapFactory.Options are required to get the Skia flattening code to write out the image data (Otherwise images are emitted).
options.inInputShareable = true;
options.inPurgeable = true;
I am aware that the Picture methods writeToStream()
and createFromStream()
have been deprecated but I would not expect this to introduce a stability issue.
I need to write away the Picture object as I want to pass it from the main application to a service process. I cannot use the recommended workaround in the documentation which says to 'draw the picture into a bitmap' for the following reasons:
- The desired resolution of the Picture is not known at the time of writing away.
- The Picture object needs to be scaled up via a matrix after being restored.
- Saving to a very high resolution bitmap is inefficient in terms of memory and processing time.
Does anybody know a work around that allows the images to be written into the stream without causing this crash?