FileProvider error onHuawei devices

前端 未结 4 2059
予麋鹿
予麋鹿 2021-01-31 02:27

I have an exception that happens only on Huawei devices in my app when using FileProvider.getUriForFile:

Exception: java.lang.Illeg         


        
相关标签:
4条回答
  • 2021-01-31 02:55

    Try to manually provide the uri

    var fileUri:Uri
    try{
       fileUri = FileProvider.getUriForFile(
                                this,
                                "com.example.android.fileprovider",
                                it
                            )
                        } catch (e:Exception){
                            Log.w("fileProvider Exception","$e")
    
     fileUri=Uri.parse("content://${authority}/${external-path name}/${file name}")
                        }
    
    

    get authority from android:authorites in the provider tag inside AndroidManifest.xml

    get external-path name from name in the external path tag inside file_paths.xml

    0 讨论(0)
  • 2021-01-31 03:14

    Update for Android N (leaving the original answer below and have confirmed this new approach works in production):

    As you noted in your update, many Huawei device models (e.g. KIW-L24, ALE-L21, ALE-L02, PLK-L01, and a variety of others) break the Android contract for calls to ContextCompat#getExternalFilesDirs(String).
 Rather than returning Context#getExternalFilesDir(String) (ie the default entry) as the first object in the
 array, they instead return the first object as the path to the external SD card, if one is present.

    By breaking this ordering contract, these Huawei devices with external SD cards will crash with an IllegalArgumentException on calls to FileProvider#getUriForFile(Context, String, File) for external-files-path roots. While there are a variety of solutions that you can pursue to attempt to deal with this issue (e.g. writing a custom FileProvider implementation), I've found the easiest approach is to catch this issue and:

    • Pre-N: Return Uri#fromFile(File), which won't work with Android N and above due to FileUriExposedException
    • N: Copy the file to your cache-path (note: this can introduce ANRs if done on the UI Thread) and then return FileProvider#getUriForFile(Context, String, File) for the copied file (ie avoiding the bug altogether)

    Code to accomplish this can be found below:

    public class ContentUriProvider {
    
        private static final String HUAWEI_MANUFACTURER = "Huawei";
    
        public static Uri getUriForFile(@NonNull Context context, @NonNull String authority, @NonNull File file) {
            if (HUAWEI_MANUFACTURER.equalsIgnoreCase(Build.MANUFACTURER)) {
                Log.w(ContentUriProvider.class.getSimpleName(), "Using a Huawei device Increased likelihood of failure...");
                try {
                    return FileProvider.getUriForFile(context, authority, file);
                } catch (IllegalArgumentException e) {
                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                        Log.w(ContentUriProvider.class.getSimpleName(), "Returning Uri.fromFile to avoid Huawei 'external-files-path' bug for pre-N devices", e);
                        return Uri.fromFile(file);
                    } else {
                        Log.w(ContentUriProvider.class.getSimpleName(), "ANR Risk -- Copying the file the location cache to avoid Huawei 'external-files-path' bug for N+ devices", e);
                        // Note: Periodically clear this cache
                        final File cacheFolder = new File(context.getCacheDir(), HUAWEI_MANUFACTURER);
                        final File cacheLocation = new File(cacheFolder, file.getName());
                        InputStream in = null;
                        OutputStream out = null;
                        try {
                            in = new FileInputStream(file);
                            out = new FileOutputStream(cacheLocation); // appending output stream
                            IOUtils.copy(in, out);
                            Log.i(ContentUriProvider.class.getSimpleName(), "Completed Android N+ Huawei file copy. Attempting to return the cached file");
                            return FileProvider.getUriForFile(context, authority, cacheLocation);
                        } catch (IOException e1) {
                            Log.e(ContentUriProvider.class.getSimpleName(), "Failed to copy the Huawei file. Re-throwing exception", e1);
                            throw new IllegalArgumentException("Huawei devices are unsupported for Android N", e1);
                        } finally {
                            IOUtils.closeQuietly(in);
                            IOUtils.closeQuietly(out);
                        }
                    }
                }
            } else {
                return FileProvider.getUriForFile(context, authority, file);
            }
        }
    
    }
    

    Along with the file_provider_paths.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <external-files-path name="public-files-path" path="." />
        <cache-path name="private-cache-path" path="." />
    </paths>
    

    Once you've created a class like this, replace your calls to:

    FileProvider.getUriForFile(Context, String, File)
    

    with:

    ContentUriProvider.getUriForFile(Context, String, File)
    

    Frankly, I don't think this is an especially graceful solution, but it does allow us to use formally documented Android behavior without doing anything too drastic (e.g. writing a custom FileProvider implementation). I have tested this in production, so I can confirm that it resolves these Huawei crashes. For me, this was the best approach, since I didn't wish to spend too much time addressing what is quite obviously a manufacturer's defect.

    Update from before Huawei devices with this bug updated to Android N:

    This won't work with Android N and above due to FileUriExposedException, but I have yet to encounter a Huawei device with this mis-configuration on Android N.

    public class ContentUriProvider {
    
        private static final String HUAWEI_MANUFACTURER = "Huawei";
    
        public static Uri getUriForFile(@NonNull Context context, @NonNull String authority, @NonNull File file) {
            if (HUAWEI_MANUFACTURER.equalsIgnoreCase(Build.MANUFACTURER) && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                Log.w(ContentUriProvider.class.getSimpleName(), "Using a Huawei device on pre-N. Increased likelihood of failure...");
                try {
                    return FileProvider.getUriForFile(context, authority, file);
                } catch (IllegalArgumentException e) {
                    Log.w(ContentUriProvider.class.getSimpleName(), "Returning Uri.fromFile to avoid Huawei 'external-files-path' bug", e);
                    return Uri.fromFile(file);
                }
            } else {
                return FileProvider.getUriForFile(context, authority, file);
            }
        }
    }
    
    0 讨论(0)
  • 2021-01-31 03:17

    I had the same issue and my solution in the end was to always use the ContextCompat.getExternalFilesDirs call to build the File that is used as parameter for FileProvider. That way you don't have to use any of the above workarounds.

    In other words. If you have control over the File parameter that you use to call FileProvider and/or you don't care that the file might end up being saved outside of the classic /storage/emulated/0/Android/data/ folder (which should be perfectly fine, as it's all just the same SD card) then I suggest to do what I have done.

    If it's not your case, then I suggest to use the above answer with custom getUriForFile implementation.

    0 讨论(0)
  • 2021-01-31 03:17

    My solution to this issue right now, even if it's not perfect, is to declare my FileProvider with the following path (to be able serve all files on the device):

    <?xml version="1.0" encoding="utf-8"?>
    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <root-path name="root" path="" />
    </paths>
    

    This is not officially documented and might break with a future version of the v4 Support library but I can't see any other solution to serve a file in the secondary external storage (often the SD card) using the existing FileProvider.

    0 讨论(0)
提交回复
热议问题