使用ACTION_CREATE_DOCUMENT写入的文件在Google云端硬盘中为空,但本地存储中为空

时间:2018-07-24 03:47:18

标签: android android-intent google-drive-api

我有一个应用程序使用ACTION_CREATE_DOCUMENT导出CSV文件以获取要写入的Uri。一段时间以来,它的运行情况还不错,但是最近我去保存文件并选择Google云端硬盘时,我注意到生成的文件为空(0字节)。但是,如果我选择“下载”文件夹作为目标位置(本地存储),则会创建该文件并将其正确写入。

Result of saving to Google Drive (correctly written file is 14 bytes)

我将问题简化为一个简单的测试活动(如下),并验证了代码的所有部分实际上正在执行(没有捕获的异常,ParcelFileDescriptor不为空,等等。)。

我在Android 7和8仿真器以及Android 8物理设备上进行了测试,都显示了相同的行为,但是旧的Android 5手机可以正常工作(??)。

该应用的完整版具有并正确地请求读取和写入外部存储权限以处理某些third party file managers,但是Google云端硬盘不需要此权限,因此我在此示例中将其省略了(行为与授予的权限相同)。

我收到的uri看起来像是有效的内容Uri(content://com.google.android.apps.docs.storage/document/acc%3...

还有其他人看到过此问题吗?我在这里做错什么了吗?我该如何再次使用Google云端硬盘?

public class MainActivity extends AppCompatActivity {

    private static final int FILE_EXPORT_REQUEST_CODE = 12;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        if (resultCode != RESULT_OK)
            return;

        switch(requestCode) {
            case FILE_EXPORT_REQUEST_CODE:
                if( data != null ) {
                    Uri uri = data.getData();
                    if( uri != null ) {
                        new ExportCSV(this).execute(uri);
                    }
                }
                break;
        }
    }

    // test Activity has a single button that calls this
    public void saveFile(View v) {
        Intent exportIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
        exportIntent.addCategory(Intent.CATEGORY_OPENABLE);
        exportIntent.setType("text/csv");
        String filename = "test.csv";
        exportIntent.putExtra(Intent.EXTRA_TITLE, filename);
        startActivityForResult(exportIntent, FILE_EXPORT_REQUEST_CODE);
    }

    private static class ExportCSV extends AsyncTask<Uri, Void, Boolean> {
        private final WeakReference<Context> context;

        ExportCSV(Context c) {
            context = new WeakReference<>(c);
        }

        @Override
        protected Boolean doInBackground(Uri... uris) {
            Uri uri = uris[0];
            Context c = context.get();

            if( c == null ) {
                return false;
            }

            String data = "colA,colB\n1,2\n";
            boolean success = false;

            try {
                ParcelFileDescriptor pfd = c.getContentResolver().openFileDescriptor(uri, "w");
                if( pfd != null ) {
                    FileOutputStream fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());
                    fileOutputStream.write(data.getBytes());
                    fileOutputStream.close();
                    success = true;
                }
            }
            catch(Exception e) {
                e.printStackTrace();
            }
            return success;
        }
    }
}

2 个答案:

答案 0 :(得分:2)

请参阅https://developer.android.com/guide/topics/providers/document-provider

上的官方文档

在Android 4.3及更低版本上,如果您希望您的应用从另一个应用中检索文件,则它必须调用诸如ACTION_PICK或ACTION_GET_CONTENT之类的意图。然后,用户必须选择一个应用程序以从中选择文件,并且所选应用程序必须提供一个用户界面,供用户浏览并从可用文件中选择。

在Android 4.4(API级别19)及更高版本上,您还可以使用ACTION_OPEN_DOCUMENT意向,该附加意向可显示系统控制的选择器UI,该用户界面受控制,允许用户浏览其他应用程序提供的所有文件。用户可以通过单个UI从任何受支持的应用中选择文件。

在Android 5.0(API级别21)及更高版本上,您还可以使用ACTION_OPEN_DOCUMENT_TREE意图,该意图使用户可以选择客户端应用访问的目录。

对我来说,以下解决方案非常适合Android 9上的本地驱动器和Google驱动器

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (resultCode == RESULT_OK && data != null) {
        if (requestCode == REQUEST_SAVE_File) {
            try {
                stream = getContentResolver().openOutputStream(data.getData());
                ((BitmapDrawable) imageView.getDrawable()).getBitmap().compress(Bitmap.CompressFormat.PNG, 100, stream);
                strem.close(); ///very important
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
}

意图如下创建

    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType("image/png");
    intent.putExtra(Intent.EXTRA_TITLE, "my-image.png");

    startActivityForResult(intent, REQUEST_SAVE_FILE);

确保在写入后关闭流。否则可能无法保存文件。

答案 1 :(得分:0)

@greenapps的评论使我走上了正确的道路。将其更改为使用getContentResolver().openOutputStream(...)可解决问题:

try {
    OutputStream os = c.getContentResolver().openOutputStream(uri);
    if( os != null ) {
        os.write(data.getBytes());
        os.close();
    }
}
catch(IOException e) {
    e.printStackTrace();
}

更新:在我的应用中使用了一段时间后,该问题再次发生(写入Google云端硬盘的文件为0字节,但可以在本地写入)。清除我的应用程序的缓存数据再次解决了该问题。