0

我有以下问题:

我们的 Android 应用使用LevelDB将文件写入设备的外部存储。LevelDB内部使用mmap写入。到目前为止,我们的问题只发生在三星 Galaxy S4上。文件被写入和读取存储没有任何问题。但重启设备后,文件已损坏。

有没有人经历过类似的事情?

我编写了一个小型演示应用程序来检查mmap问题是否存在,实际上似乎是。演示应用程序显示应用程序附带的图像和图像下方的按钮。
如果按下按钮

  • FileChannel.map()使用(相当于mmap)将图像写入外部存储
  • 图像从外部存储中读取并显示在按钮下方。

按下按钮一次并将图像写入外部存储后,应用程序会显示图像的两个副本。即使在重新启动应用程序后也可以使用。然而,在 Galaxy S4 重新启动后,外部存储上的文件已损坏,仅显示第一张图像。

注意:此问题在使用写入文件时不会发生,FileOutputStream并且仅在Galaxy S4上发生。

如果有人知道如何使用LevelDB来规避这个问题,那就太好了。
为了让您更容易重现问题,这里是演示应用程序的一些代码:

主要的.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:textAlignment="center"
        android:layout_gravity="center_horizontal"
        android:text="Original image as included in the app:" />

    <ImageView
        android:layout_width="300dp"
        android:layout_height="200dp"
        android:layout_margin="10dp"
        android:src="@drawable/test_image"
        android:layout_gravity="center_horizontal" />

    <Button
        android:layout_width="300dp"
        android:layout_height="44dp"
        android:layout_gravity="center_horizontal"
        android:text="Write image file to storage"
        android:onClick="writeFile" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:textAlignment="center"
        android:layout_gravity="center_horizontal"
        android:text="Image as read from storage:" />

    <ImageView
        android:id="@+id/storageImage"
        android:layout_width="300dp"
        android:layout_height="200dp"
        android:layout_gravity="center_horizontal" />
</LinearLayout>

开始活动.java

package net.skoobe.StorageWrite;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class StartActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        retrieveImage();
    }

    public void writeFile(View v) {
        String state = Environment.getExternalStorageState();

        if (Environment.MEDIA_MOUNTED.equals(state)) {
            // we can read and write the media

            try {

                // convert resource to Bitmap
                BitmapDrawable bm = ((BitmapDrawable)getResources().getDrawable(R.drawable.test_image));
                Bitmap b = bm.getBitmap();

                // store bitmap data in byte array
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                b.compress(Bitmap.CompressFormat.PNG, 0 /*ignored for PNG*/, bos);
                byte[] bitmapdata = bos.toByteArray();
                bos.close();

                File dir = getApplicationContext().getExternalCacheDir();

                // write bytes to external storage
                FileChannel readWriteChannel = new RandomAccessFile(dir.getPath() + "/test_image_s.png", "rw").getChannel();
                ByteBuffer readWriteBuf = readWriteChannel.map(FileChannel.MapMode.READ_WRITE, 0, bitmapdata.length);
                readWriteBuf.put(bitmapdata);
                readWriteChannel.close();

            } catch (Exception e) {
                Log.e("StorageWrite", e.toString());
            }

            retrieveImage();

        } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            Log.e("StorageWrite", "storage not writable");
        } else {
            Log.e("StorageWrite", "storage neither writable nor readable");
        }
    }

    private void retrieveImage() {
        String state = Environment.getExternalStorageState();

        if (Environment.MEDIA_MOUNTED.equals(state)) {
            // we can read and write the media

            try {

                // create Drawable from PNG file on external storage
                File dir = getApplicationContext().getExternalCacheDir();
                String pathName = dir.getPath() + "/test_image_s.png";
                Drawable d = Drawable.createFromPath(pathName);

                // display the image
                ((ImageView)findViewById(R.id.storageImage)).setImageDrawable(d);

            } catch (Exception e) {
                Log.e("StorageWrite", e.toString());
            }

        } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            Log.e("StorageWrite", "storage not writable");
        } else {
            Log.e("StorageWrite", "storage neither writable nor readable");
        }
    }
}
4

1 回答 1

0

我的一位同事找到了解决 S4 问题的方法。他意识到这个设备上的外部存储实际上是模拟的。在内部,它使用内部存储。所以我们也可以直接使用内部存储。

幸运的是 - 从 API 级别 11 开始 - 有一个 SDK 调用来确定存储是否被模拟:isExternalStorageEmulated()

我认为这是一个干净的解决方案,因为它不仅仅针对单个设备。每当模拟外部存储时,只需使用内部存储即可。

但是,如果数据是通过 mmap(2) 写入的,我们仍然认为模拟的外部存储存在 Galaxy S4 特定的问题。

于 2013-07-23T11:43:17.670 回答