簡體   English   中英

從下載中選擇文件時崩潰

[英]Crash when selecting file from downloads

從設備中選擇文件時,我正在使用這個 fileutils 類:

public class FileUtils {
private FileUtils() {
}

private static final String TAG = "FileUtils";
private static final boolean DEBUG = false;

private static boolean isExternalStorageDocument(Uri uri) {
    return "com.android.externalstorage.documents".equals(uri.getAuthority());
}

private static boolean isDownloadsDocument(Uri uri) {
    return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}

private static boolean isMediaDocument(Uri uri) {
    return "com.android.providers.media.documents".equals(uri.getAuthority());
}


private static boolean isGooglePhotosUri(Uri uri) {
    return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}

private static String getDataColumn(Context context, Uri uri, String selection,
                                    String[] selectionArgs) {

    Cursor cursor = null;
    final String column = "_data";
    final String[] projection = {
            column
    };

    try {
        cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                null);
        if (cursor != null && cursor.moveToFirst()) {
            if (DEBUG)
                DatabaseUtils.dumpCursor(cursor);

            final int column_index = cursor.getColumnIndexOrThrow(column);
            return cursor.getString(column_index);
        }
    } finally {
        if (cursor != null)
            cursor.close();
    }
    return null;
}

@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static String getPath(final Context context, final Uri uri) {

    if (DEBUG)
        Log.d(TAG + " File -",
                "Authority: " + uri.getAuthority() +
                        ", Fragment: " + uri.getFragment() +
                        ", Port: " + uri.getPort() +
                        ", Query: " + uri.getQuery() +
                        ", Scheme: " + uri.getScheme() +
                        ", Host: " + uri.getHost() +
                        ", Segments: " + uri.getPathSegments().toString()
        );

    final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

    // DocumentProvider
    if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {

        //// ExternalStorageProvider
        if (isExternalStorageDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            // This is for checking Main Memory
            if ("primary".equalsIgnoreCase(type)) {
                if (split.length > 1) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1];
                } else {
                    return Environment.getExternalStorageDirectory() + "/";
                }
                // This is for checking SD Card
            } else {
                return "storage" + "/" + docId.replace(":", "/");
            }

        }
        // DownloadsProvider
        else if (isDownloadsDocument(uri)) {
            final String id = DocumentsContract.getDocumentId(uri);
            if (id.startsWith("raw:")) {
                String[] data = new String[2];
                data[0] = id.replaceFirst("raw:", "");
                data[1] = null;
                return data[0];
            }
            final Uri contentUri = ContentUris.withAppendedId(
                    Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

            return getDataColumn(context, contentUri, null, null);
        }
        // MediaProvider
        else if (isMediaDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            Uri contentUri = null;
            if ("image".equals(type)) {
                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            } else if ("video".equals(type)) {
                contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            } else if ("audio".equals(type)) {
                contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
            }

            final String selection = "_id=?";
            final String[] selectionArgs = new String[]{
                    split[1]
            };

            return getDataColumn(context, contentUri, selection, selectionArgs);
        }
    }
    // MediaStore (and general)
    else if ("content".equalsIgnoreCase(uri.getScheme())) {

        // Return the remote address
        if (isGooglePhotosUri(uri))
            return uri.getLastPathSegment();

        return getDataColumn(context, uri, null, null);
    }
    // File
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
        return uri.getPath();
    }

    return null;
}

我在onActivityResult這樣調用這個類:

String sourcePath = FileUtils.getPath(this, data.getData());

出於某種原因,我遇到了以下崩潰:

Caused by java.lang.IllegalArgumentException: Unknown URI: content://downloads/public_downloads/230
   at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:165)
   at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:135)
   at android.content.ContentProviderProxy.query(ContentProviderNative.java:418)
   at android.content.ContentResolver.query(ContentResolver.java:760)
   at android.content.ContentResolver.query(ContentResolver.java:710)
   at android.content.ContentResolver.query(ContentResolver.java:668)
   at com.HBiSoft.ProGolf.Utils.FileUtils.getDataColumn(FileUtils.java:50)
   at com.HBiSoft.ProGolf.Utils.FileUtils.getPath(FileUtils.java:116)
   at com.HBiSoft.ProGolf.MainActivity.onActivityResult(MainActivity.java:652)
   at android.app.Activity.dispatchActivityResult(Activity.java:7638)
   at android.app.ActivityThread.deliverResults(ActivityThread.java:4515)
   at android.app.ActivityThread.handleSendResult(ActivityThread.java:4563)
   at android.app.ActivityThread.-wrap21(Unknown Source)
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1779)
   at android.os.Handler.dispatchMessage(Handler.java:106)
   at android.os.Looper.loop(Looper.java:164)
   at android.app.ActivityThread.main(ActivityThread.java:7000)
   at java.lang.reflect.Method.invoke(Method.java)
   at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:441)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1408)

只有在從下載文件夾中選擇文件時才會發生崩潰。

有人可以幫我解決這個問題嗎?

我創建了一個解決這個問題的gist ,這里是鏈接


重要的!!

當您從 Google Drive 或 Dropbox 中選擇文件時,您將收到以下錯誤:

Caused by java.lang.IllegalArgumentException: column '_data' does not exist

要解決此問題,您實際上必須制作您嘗試選擇的文件的臨時副本。

首先,您必須檢查content:// uri 是否來自 Google Drive,您可以通過執行以下操作來做到這一點:

public boolean isGoogleDrive(Uri uri) {
    return String.valueOf(uri).toLowerCase().contains("com.google.android.apps");
}

如果它是來自 Google Drive 的文件,則返回 true。 如果是,我將調用AsyncTask ,如下所示:

//The data.getData() below refers to the uri you get in onActivityResult
if (isGoogleDrive(data.getData())) {
    DownloadAsyncTask asyntask = new DownloadAsyncTask(data.getData(), this);
    asyntask.execute();
    asyntask.callback = this;
}

AsyncTask我們將使用Uri打開InputStream並取回字節數據,我們將使用它來制作文件的副本。

這是AsyncTask類(我添加了注釋以使其更易於理解):

class DownloadAsyncTask extends AsyncTask<Uri, Void, String> {
    private Uri mUri;
    CallBackTask callback;
    Context mContext;
    private AlertDialog mdialog;

    DownloadAsyncTask(Uri uri, Context context) {
        this.mUri = uri;
        mContext = context;
    }

    // In the onPreExecute() I'm displaying a custom dialog, this is not necessary, but recommended for when the user selects a large file
    @Override
    protected void onPreExecute() {
        final AlertDialog.Builder mPro = new AlertDialog.Builder(new ContextThemeWrapper(mContext, R.style.myDialog));
        @SuppressLint("InflateParams") 
        //Get reference to dialog layout
        final View mPView = LayoutInflater.from(mContext).inflate(R.layout.dialog, null);
        //Get reference to dialog title
        final TextView title = mPView.findViewById(R.id.txtTitle);
        //Get reference to dialog description
        final TextView desc = mPView.findViewById(R.id.txtDesc);

        //Set title text
        title.setText("Please wait..");
        //Set description text
        desc.setText("Drive files needs to be imported, this might take some time depending on the file size.");

        mPro.setView(mPView);
        mdialog = mPro.create();
        mdialog.show();

    }

    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    @Override
    protected String doInBackground(Uri... params) {
        //This will be the file we will use (the one that will be copied)
        File file = null;
        try {
            //Create a temporary folder where the copy will be saved to
            File temp_folder = mContext.getExternalFilesDir("TempFolder");

            //Use ContentResolver to get the name of the original name
            //Create a cursor and pass the Uri to it
            Cursor cursor = mContext.getContentResolver().query(mUri, null, null, null, null);
            //Check that the cursor is not null
            assert cursor != null;
            int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
            cursor.moveToFirst();
            //Get the file name
            String filename = cursor.getString(nameIndex);
            //Close the cursor
            cursor.close();

            //open a InputStream by passing it the Uri
            //We have to do this in a try/catch
            InputStream is = null;
            try {
                is = mContext.getContentResolver().openInputStream(mUri);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }

            //We now have a folder and a file name, now we can create a file
            file = new File(temp_folder + "/" + filename);

            //We can now use a BufferedInputStream to pass the InputStream we opened above to it
            BufferedInputStream bis = new BufferedInputStream(is);
            //We will write the byte data to the FileOutputStream, but we first have to create it
            FileOutputStream fos = new FileOutputStream(file);

            byte data[] = new byte[1024];
            long total = 0;
            int count;
            //Below we will read all the byte data and write it to the FileOutputStream
            while ((count = bis.read(data)) != -1) {
                total += count;
                fos.write(data, 0, count);
            }
            //The FileOutputStream is done and the file is created and we can clean and close it
            fos.flush();
            fos.close();

        } catch (IOException e) {
            Log.e("IOException = ", String.valueOf(e));
        }

        //Finally we can pass the path of the file we have copied
        return file.getAbsolutePath();


    }

    protected void onPostExecute(String result) {
        //We are done and can cancel the dialog
        if (mdialog != null && mdialog.isShowing()) {
            mdialog.cancel();
        }
        //I'm using a callback to let my Activity know that the AsyncTask is done. I pass the path along.
        callback.getResultFromAsynTask(result);


    }
}

你會看到在上面的onPostExecute()callback.getResultFromAsynTask(result); . 正如我在評論中提到的,我正在使用回調方法讓 Activity 知道我已完成並將路徑傳遞給回調。

在您的 Activity 中,您必須實現回調,如下所示:

public class MainActivity extends AppCompatActivity implements CallBackTask {

CallBackTask將如下所示:

interface CallBackTask {
    void getResultFromAsynTask(String result);
 }

現在你必須實現它在你的Activity獲得的結果在你的Activity

@Override
public void getResultFromAsynTask(String result) {
    // Do what you need with the result like starting your new Activity and passing the path
    final Intent intent = new Intent();
    intent.setClass(MainActivity.this, Player.class);
    intent.putExtra("path", result);
    startActivity(intent);
}

太好了,您現在有一個來自您臨時復制的File:// (而不是content:// )的Uri 但是,完成后不要忘記刪除該文件,否則您的應用程序大小會越來越大。

下面,我將刪除我們之前創建的TempFolder ,我將在活動onBackPressed執行此onBackPressed (這也應該在 onDestroy() 中完成):

@Override
public void onBackPressed() {
    super.onBackPressed();
    File dir = getBaseContext().getExternalFilesDir("TempFolder");
    deleteRecursive(dir);
}

void deleteRecursive(File fileOrDirectory) {
    if (fileOrDirectory.isDirectory())
        for (File child : fileOrDirectory.listFiles())
            deleteRecursive(child);

    fileOrDirectory.delete();
}

這是很多閱讀但是...通過這樣做,您將永遠不會遇到任何content:// Uri的問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM