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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用ACTION_CREATE_DOCUMENT写入的文件在Google云端硬盘上为空,但在本地存储中为空相关的知识,希望对你有一定的参考价值。

我有一个应用程序使用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
1,2
";
            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;
        }
    }
}
答案

@ 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字节,但可以在本地写入)。清除我的应用程序的缓存数据再次解决了。

另一答案

请参阅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);

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

以上是关于使用ACTION_CREATE_DOCUMENT写入的文件在Google云端硬盘上为空,但在本地存储中为空的主要内容,如果未能解决你的问题,请参考以下文章

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

如何使用 ActivityResultContracts.CreateDocument() 添加意图类型

如何使用 android 范围/共享存储获取可用空间?

使用存储访问框架创建的文件在包含 (1) 时显示为灰色以供选择

使用markdown写博客

多线程文件写同一个文件写不进去