为什么当我将图像设置为 ImageView 并在 android 7.1.1 上 运行 时应用程序崩溃

Why app crashes when i set image to ImageView and run it on android 7.1.1

我是 Android 的新手。我想知道为什么我的应用程序在使用显式意图将图像设置为 imageView 时在 Android 7.1.1 上崩溃,因为它在其他版本的 Android 上运行良好。我试图理解并解决问题,但无法做到。

谁能帮我理解为什么会这样,背后的原因是什么?

谢谢。

这是它显示的内容:

eAnimators on 0x97c5ee80 (RippleDrawable) with handle 0x9affd880
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
                  Process: com.example.himanshu.imageviewintent, PID: 5049
                  java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1, result=-1, data=Intent { dat=content://media/external/images/media/41 }} to activity {com.example.himanshu.imageviewintent/com.example.himanshu.imageviewintent.MainActivity}: java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider uri content://media/external/images/media/41 from pid=5049, uid=10073 requires android.permission.READ_EXTERNAL_STORAGE, or grantUriPermission()
                      at android.app.ActivityThread.deliverResults(ActivityThread.java:4089)
                      at android.app.ActivityThread.handleSendResult(ActivityThread.java:4132)
                      at android.app.ActivityThread.-wrap20(ActivityThread.java)
                      at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1533)
                      at android.os.Handler.dispatchMessage(Handler.java:102)
                      at android.os.Looper.loop(Looper.java:154)
                      at android.app.ActivityThread.main(ActivityThread.java:6119)
                      at java.lang.reflect.Method.invoke(Native Method)
                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
                   Caused by: java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider uri content://media/external/images/media/41 from pid=5049, uid=10073 requires android.permission.READ_EXTERNAL_STORAGE, or grantUriPermission()
                      at android.os.Parcel.readException(Parcel.java:1684)
                      at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:183)
                      at android.database.DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(DatabaseUtils.java:146)
                      at android.content.ContentProviderProxy.openTypedAssetFile(ContentProviderNative.java:692)
                      at android.content.ContentResolver.openTypedAssetFileDescriptor(ContentResolver.java:1147)
                      at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:984)
                      at android.content.ContentResolver.openInputStream(ContentResolver.java:704)
                      at com.example.himanshu.imageviewintent.MainActivity.onActivityResult(MainActivity.java:44)
                      at android.app.Activity.dispatchActivityResult(Activity.java:6932)
                      at android.app.ActivityThread.deliverResults(ActivityThread.java:4085)
                      at android.app.ActivityThread.handleSendResult(ActivityThread.java:4132) 
                      at android.app.ActivityThread.-wrap20(ActivityThread.java) 
                      at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1533) 
                      at android.os.Handler.dispatchMessage(Handler.java:102) 
                      at android.os.Looper.loop(Looper.java:154) 
                      at android.app.ActivityThread.main(ActivityThread.java:6119) 
                      at java.lang.reflect.Method.invoke(Native Method) 
                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886) 
                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776) 
Application terminated.

这是我的代码:

Java :

public class MainActivity extends Activity {

    final static int ImageIntentRequest = 1;

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

    public void setImage(View view) {

        Intent intent = new Intent(Intent.ACTION_PICK);
        File imageViewPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
        String path =  imageViewPath.getPath();
        intent.setDataAndType(Uri.parse(path),"image/*");
        startActivityForResult(intent,ImageIntentRequest);
    }

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

        if(resultCode == RESULT_OK && requestCode == ImageIntentRequest) {

            InputStream stream;
            ImageView imageView = (ImageView)findViewById(R.id.imageView);
            try {
             stream = getContentResolver().openInputStream(data.getData());
                Bitmap bitmap = BitmapFactory.decodeStream(stream);
                imageView.setImageBitmap(bitmap);

            } catch (FileNotFoundException e) {
                Toast.makeText(this,"There is no File Present",Toast.LENGTH_SHORT).show();;
                e.printStackTrace();
            }
        }
    }
}

XML :

   <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.himanshu.imageviewintent.MainActivity"
>

<Button
    android:text="@string/set_image_btn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/btn_set_image"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true"
    android:onClick="setImage"
    />

<ImageView
    android:src="@mipmap/ic_launcher"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/imageView"
    android:contentDescription="@null"
    android:layout_above="@+id/btn_set_image"
    android:layout_alignParentStart="true"
    android:layout_marginBottom="10dp"
    />
</RelativeLayout>

我找到了一个简单的解决方法:

我刚换了,

Intent.ACTION_PICK by -

Intent intent = Intent(Intent.ACTION_GET_CONTENT);

现在该应用程序在 Android 的旧版本和最新版本上运行完全正常。

但仍有疑问:

为什么 ACTION_PICK 适用于旧版本的 Android 但不适用于新版本? 为什么 ACTION_GET_CONTENT 适用于所有版本?

如果有人澄清一下,我将不胜感激?

您需要获得用户的运行时权限才能读取外部存储。

您可以使用以下代码请求用户的许可。

希望对您有所帮助。

if(ActivityCompat.checkSelfPermission(this,Manifest.permission.READ_EXTERNAL_STORAGE)!=PackageManager.PERMISSION_GRANTED){
        requestStoragePermission();
    } else { 
        loadImageInImageview();
    }


    private void requestStoragePermission() {
        final String[] permissions = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE};
        if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
            ActivityCompat.requestPermissions(this, permissions, 1000);
            return;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 1000) {
            if (grantResults.length != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                loadImageInImageview();
            } else {
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setTitle("Allow permission.")
                        .setMessage("Please....")
                        .setCancelable(false)
                        .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int id) {
                                Intent intent = new Intent();
                                intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                                Uri uri = Uri.fromParts("package", getPackageName(), null);
                                intent.setData(uri);
                                startActivity(intent);
                            }
                        })
                        .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                finish();
                            }
                        }).show();
            }
        }
    }

您没有向清单添加存储权限,或者您在使用之前没有要求用户接受它们。

<manifest ...>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    ...
</manifest>

您可以使用此功能请求用户许可

    public  boolean isStoragePermissionGranted() {
        if (Build.VERSION.SDK_INT >= 23) {
            if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    == PackageManager.PERMISSION_GRANTED) {
                Log.v(TAG,"Permission is granted");
                return true;
            } else {

                Log.v(TAG,"Permission is revoked");
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
                return false;
            }
        }
        else { //permission is automatically granted on sdk<23 upon installation
            Log.v(TAG,"Permission is granted");
            return true;
        }

}

如果您查看 documentation 关于 Android 牛轧糖中引入的更改,您会发现 -

For apps targeting Android 7.0, the Android framework enforces the StrictMode API policy that prohibits exposing file:// URIs outside your app. If an intent containing a file URI leaves your app, the app fails with a FileUriExposedException exception.

To share files between applications, you should send a content:// URI and grant a temporary access permission on the URI. The easiest way to grant this permission is by using the FileProvider class.

在这里,你的意图

 Intent intent = new Intent(Intent.ACTION_PICK);
    File imageViewPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
    String path =  imageViewPath.getPath();
    intent.setDataAndType(Uri.parse(path),"image/*");

让您的应用程序打开 Gallery/Camera 来选择一张图片,这会导致崩溃,因为它不遵循 Android 牛轧糖协议。

要解决您的问题,请将您的 gradle 文件中的 targetApi 从 24/25/26(无论您现在拥有哪个)向下更改为 23(棉花糖)。

或者,您可以在 File Sharing 上阅读更多内容并以不违反 StrictMode 政策的方式进行。

如果时间紧迫,可以使用EasyImage库来解决。