从Android应用程序共享SQLite数据库,无需中间副本

时间:2015-03-31 18:41:23

标签: android database sqlite export uri

我想允许我的Android应用的用户导出他们创建的内容的SQLite数据库文件。我当前的解决方案将文件复制到专用存储(/data/data/com.package.name/files/Content.db),然后为此文件创建URI并打开“共享”对话框。这是有效的,允许我使用Dropbox导出数据库文件。这是我正在使用的代码,部分改编自https://stackoverflow.com/a/2661882 -

private void exportContent() {
    copyContentToPrivateStorage();

    Intent intent = new Intent(Intent.ACTION_SEND);
    intent.setType("application/octet-stream");

    Uri uri = new FileProvider().getDatabaseURI(this);

    intent.putExtra(Intent.EXTRA_STREAM, uri);
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

    startActivity(Intent.createChooser(intent, "Backup via:"));
}

private void copyContentToPrivateStorage() {
    // From https://stackoverflow.com/a/2661882
    try {
        File data = Environment.getDataDirectory();
        File sd = getFilesDir();

        if (sd.canWrite()) {
            String currentDBPath = "//data//com.package.name//databases//Content.db";
            String backupDBPath = "Content.db";
            File currentDB = new File(data, currentDBPath);
            File backupDB = new File(sd, backupDBPath);

            if (currentDB.exists()) {
                FileChannel src = new FileInputStream(currentDB).getChannel();
                FileChannel dst = new FileOutputStream(backupDB).getChannel();
                dst.transferFrom(src, 0, src.size());
                src.close();
                dst.close();
            }
        }
    } catch (Exception e) {
        Toast.makeText(this, e.toString(), Toast.LENGTH_LONG).show();
    }
}

public class FileProvider extends android.support.v4.content.FileProvider {

    public Uri getDatabaseURI(Context c) {
        File exportFile = new File(c.getFilesDir(), "Content.db");
        Uri uri = getUriForFile(c, "com.package.name.fileprovider", exportFile);

        c.grantUriPermission("*", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);

        return uri;
    }
}

似乎我应该能够从现有数据库路径直接创建URI,而不是执行中间副本。有没有办法做到这一点?

我可以继续做中间副本,但我认为将数据库的第二个副本留在数据目录中的时间超过必要时间是不好的做法。有没有办法清理它并在所选应用程序完成使用URI共享文件后删除它?

3 个答案:

答案 0 :(得分:4)

我自己解决了这个问题。根据Neil的要求,我在这里记录它。

这是我从我的活动中启动导出/备份的地方:

public class MyActivity {

    private void exportUserContent() {
        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.setType("application/octet-stream");

        Uri uri = new FileProvider().getDatabaseURI(this);

        intent.putExtra(Intent.EXTRA_STREAM, uri);

        startActivity(Intent.createChooser(intent, "Backup via:"));
    }

}

FileProvider:

public class FileProvider extends android.support.v4.content.FileProvider {

    public Uri getDatabaseURI(Context c) {
        // https://developer.android.com/reference/android/support/v4/content/FileProvider.html

        File data = Environment.getDataDirectory();
        String dbName = "UserContent.db";
        String currentDBPath = "//data//com.url.myapp//databases//" + dbName;

        File exportFile = new File(data, currentDBPath);

        return getFileUri(c, exportFile);
    }

    public Uri getFileUri(Context c, File f){
        return getUriForFile(c, "com.url.myapp.fileprovider", f);
    }

}

AndroidManifest.xml内部:

<manifest ...>
    <application ...>
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.url.myapp.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths" />
        </provider>

在\ app \ src \ main \ res \ xml \ filepaths.xml内 (我认为第一个条目是相关条目,但我会将整个文件包括在内):

<paths>
    <files-path
        path="../databases/"
        name="mydatabases"/>

    <files-path
        path=""
        name="migrations"/>

    <external-path
        path=""
        name="external"/>
</paths>

答案 1 :(得分:0)

使用内容提供商共享SQLite数据库。本教程可以为您提供有关SQLite数据库和内容提供程序的更多信息:Android SQLite DB and Content Provider

答案 2 :(得分:0)

以下是我使用自定义ContentProvider解决此问题的方法:

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.provider.OpenableColumns;

import java.io.File;
import java.io.FileNotFoundException;

/**
 * ContentProvider to share SQLite database
 *
 * Credit: https://android.googlesource.com/platform/frameworks/support/+/android-support-lib-19.1.0/v4/java/android/support/v4/content/FileProvider.java
 */
public class MyProvider extends ContentProvider {

    private final File file = new File("/data/data/com.example.provider/databases", "mydatabase.db");

    @Override
    public boolean onCreate() {
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        if (projection == null) {
            projection = new String[] { OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE };
        }

        String[] cols = new String[projection.length];
        Object[] values = new Object[projection.length];
        int i = 0;
        for (String col : projection) {
            if (OpenableColumns.DISPLAY_NAME.equals(col)) {
                cols[i] = OpenableColumns.DISPLAY_NAME;
                values[i++] = file.getName();
            } else if (OpenableColumns.SIZE.equals(col)) {
                cols[i] = OpenableColumns.SIZE;
                values[i++] = file.length();
            }
        }
        cols = copyOf(cols, i);
        values = copyOf(values, i);
        final MatrixCursor cursor = new MatrixCursor(cols, 1);
        cursor.addRow(values);
        return cursor;
    }

    @Override
    public String getType(Uri uri) {
        return "application/octet-stream";
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        throw new UnsupportedOperationException("No external inserts");
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        throw new UnsupportedOperationException("No external updates");
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
        return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
    }

    private static String[] copyOf(String[] original, int newLength) {
        final String[] result = new String[newLength];
        System.arraycopy(original, 0, result, 0, newLength);
        return result;
    }

    private static Object[] copyOf(Object[] original, int newLength) {
        final Object[] result = new Object[newLength];
        System.arraycopy(original, 0, result, 0, newLength);
        return result;
    }
}

然后在清单中:

<provider
    android:name="com.example.appname.MyProvider"
    android:authorities="com.example.provider">
</provider>