android.os.FileUriExposedException : Intent.getData ()를 통해 앱 외부에 노출 된 file : ///storage/emulated/0/test.txt
파일을 열려고 할 때 앱이 충돌합니다. Android Nougat 아래에서 작동하지만 Android Nougat에서는 충돌합니다. 시스템 파티션이 아닌 SD 카드에서 파일을 열려고 할 때만 충돌합니다. 권한 문제가 있습니까?
샘플 코드 :
File file = new File("/storage/emulated/0/test.txt");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "text/*");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent); // Crashes on this line
로그:
android.os.FileUriExposedException : Intent.getData ()를 통해 앱 외부에 노출 된 file : ///storage/emulated/0/test.txt
편집하다:
Android Nougat를 타겟팅 할 때 file://
URI는 더 이상 허용되지 않습니다. content://
대신 URI를 사용해야 합니다. 그러나 내 앱은 루트 디렉터리에있는 파일을 열어야합니다. 어떤 아이디어?
이면 클래스 targetSdkVersion >= 24
를 사용 FileProvider
하여 특정 파일 또는 폴더에 대한 액세스 권한을 부여하여 다른 앱에서 액세스 할 수 있도록해야합니다. FileProvider
FileProvider가 여기에 설명 된대로 가져온 종속성에 선언 된 FileProviders와 충돌하지 않도록하기 위해 상속 하는 자체 클래스 를 만듭니다 .
file://
URI를 content://
URI 로 바꾸는 단계 :
클래스 확장 추가
FileProvider
public class GenericFileProvider extends FileProvider {}
<provider>
태그AndroidManifest.xml
아래에 FileProvider 태그를 추가합니다<application>
.android:authorities
충돌을 피하기 위해 속성에 대한 고유 권한을 지정하십시오 . 가져온 종속성은 지정할 수${applicationId}.provider
있으며 기타 일반적으로 사용되는 권한을 지정할 수 있습니다 .
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...
<application
...
<provider
android:name=".GenericFileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
</application>
</manifest>
- 그런 다음 폴더에
provider_paths.xml
파일을 만듭니다res/xml
. 폴더가없는 경우 생성해야 할 수 있습니다. 파일의 내용은 아래와 같습니다.(path=".")
이름이 external_files 인 루트 폴더의 외부 저장소에 대한 액세스를 공유하고 싶다고 설명합니다 .
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
마지막 단계는 아래 코드 줄을 변경하는 것입니다.
Uri photoURI = Uri.fromFile(createImageFile());
...에
Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".my.package.name.provider", createImageFile());
편집 : 시스템에서 파일을 열도록 인 텐트를 사용하는 경우 다음 코드 줄을 추가해야 할 수 있습니다.
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
를 사용하는 솔루션 외에도이 문제를 해결할 수있는 FileProvider
다른 방법이 있습니다. 간단히 말해서
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
에서 Application.onCreate()
. 이러한 방식으로 VM은 파일 URI
노출을 무시합니다 .
방법
builder.detectFileUriExposure()
파일 노출 검사를 활성화합니다. 이는 VmPolicy를 설정하지 않은 경우 기본 동작이기도합니다.
a content://
URI
를 사용하여 무언가를 보내면 일부 앱에서 이해할 수 없다는 문제가 발생 했습니다. 그리고 target SDK
버전을 다운 그레이드하는 것은 허용되지 않습니다. 이 경우 내 솔루션이 유용합니다.
최신 정보:
주석에서 언급했듯이 StrictMode는 진단 도구이며이 문제에 사용해서는 안됩니다. 1 년 전에이 답변을 게시했을 때 많은 앱이 파일 URI 만받을 수 있습니다. FileProvider uri를 보내려고 할 때 충돌이 발생합니다. 이것은 현재 대부분의 앱에서 수정되었으므로 FileProvider 솔루션을 사용해야합니다.
앱이 API 24+를 대상으로하고 여전히 file : // 인 텐트를 사용하고 싶거나 사용해야하는 경우 해키 방식을 사용하여 런타임 확인을 비활성화 할 수 있습니다.
if(Build.VERSION.SDK_INT>=24){
try{
Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure");
m.invoke(null);
}catch(Exception e){
e.printStackTrace();
}
}
방법 StrictMode.disableDeathOnFileUriExposure
은 숨겨져 있으며 다음과 같이 문서화됩니다.
/**
* Used by lame internal apps that haven't done the hard work to get
* themselves off file:// Uris yet.
*/
문제는 내 앱이 절름발이가 아니라 많은 앱에서 이해하지 못하는 content : // 인 텐트를 사용하여 불구가되기를 원하지 않는다는 것입니다. 예를 들어, content : // 스키마로 mp3 파일을 열면 file : // 스키마를 통해 동일한 앱을 열 때보 다 훨씬 적은 수의 앱이 제공됩니다. 내 앱의 기능을 제한하여 Google의 설계 결함에 대해 비용을 지불하고 싶지 않습니다.
Google은 개발자가 콘텐츠 체계를 사용하기를 원하지만 시스템은이를 위해 준비되지 않았습니다. 수년 동안 앱이 '콘텐츠'가 아닌 파일을 사용하도록 만들어졌고 파일을 편집하고 다시 저장할 수있는 반면 콘텐츠 체계를 통해 제공되는 파일은 사용할 수 없습니다. 그들?).
경우 targetSdkVersion
보다 높은 24 다음 FileProvider는 액세스 권한을 부여하는 데 사용됩니다.
xml 파일 (경로 : res \ xml) provider_paths.xml 생성
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
추가 제공자 에 의 AndroidManifest.xml을
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
androidx 를 사용 하는 경우 FileProvider 경로는 다음과 같아야합니다.
android:name="androidx.core.content.FileProvider"
및 교체
Uri uri = Uri.fromFile(fileImagePath);
...에
Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath);
편집 : URI를 포함하는 동안 Intent
아래 줄을 추가해야합니다.
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
그리고 당신은 갈 수 있습니다. 도움이되기를 바랍니다.
targetSdkVersion
24 세 이상인 경우 Android 7.0 이상 기기 에서 값을 사용할 수 없습니다file:
Uri
Intents
.
선택 사항은 다음과 같습니다.
targetSdkVersion
23 이하로 떨어 뜨리 거나콘텐츠를 내부 저장소에 저장 한 다음 다른 앱에서 선택적으로 사용할
FileProvider
수 있도록 사용
예를 들면 :
Intent i=new Intent(Intent.ACTION_VIEW, FileProvider.getUriForFile(this, AUTHORITY, f));
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(i);
( 이 샘플 프로젝트에서 )
먼저 AndroidManifest에 공급자를 추가해야합니다.
<application
...>
<activity>
....
</activity>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.your.package.fileProvider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
이제 xml 리소스 폴더에 파일을 만듭니다 (android studio를 사용하는 경우 file_paths를 강조 표시 한 후 Alt + Enter 키를 누르고 xml 리소스 만들기 옵션을 선택할 수 있음).
다음으로 file_paths 파일에 다음을 입력하십시오.
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path path="Android/data/com.your.package/" name="files_root" />
<external-path path="." name="external_storage_root" />
</paths>
이 예제는 더 많은 옵션을 위해 여기 에서 참조 할 수있는 외부 경로에 대한 것입니다. 이렇게하면 해당 폴더 및 하위 폴더에있는 파일을 공유 할 수 있습니다.
이제 남은 것은 다음과 같이 인 텐트를 만드는 것입니다.
MimeTypeMap mime = MimeTypeMap.getSingleton();
String ext = newFile.getName().substring(newFile.getName().lastIndexOf(".") + 1);
String type = mime.getMimeTypeFromExtension(ext);
try {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(getContext(), "com.your.package.fileProvider", newFile);
intent.setDataAndType(contentUri, type);
} else {
intent.setDataAndType(Uri.fromFile(newFile), type);
}
startActivityForResult(intent, ACTIVITY_VIEW_ATTACHMENT);
} catch (ActivityNotFoundException anfe) {
Toast.makeText(getContext(), "No activity found to open this attachment.", Toast.LENGTH_LONG).show();
}
편집 : file_paths에 sd 카드의 루트 폴더를 추가했습니다. 이 코드를 테스트했으며 작동합니다.
@palash k 대답은 정확하고 내부 저장소 파일에 대해 작동했지만 제 경우에는 외부 저장소에서 파일을 열고 싶을 때 sdcard 및 usb와 같은 외부 저장소에서 파일을 열 때 내 앱이 충돌했지만 수정하여 문제를 해결할 수 있습니다. 수락 된 답변의 provider_paths.xml
provider_paths.xml을 아래와 같이 변경하십시오.
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path path="Android/data/${applicationId}/" name="files_root" />
<root-path
name="root"
path="/" />
</paths>
및 자바 클래스에서 (허용되는 답변으로 변경 사항 없음)
Uri uri=FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".provider", File)
이것은 외부 저장소의 파일에 대한 충돌을 해결하는 데 도움이됩니다. 이것이 내 것과 동일한 문제가있는 일부에게 도움이되기를 바랍니다. :)
onCreate () 활동에 아래 코드를 붙여 넣으십시오.
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());
URI 노출을 무시합니다.
내 솔루션은 Uri.fromFile ()을 사용하는 대신 파일 경로를 문자열로 'Uri.parse'하는 것이 었습니다.
String storage = Environment.getExternalStorageDirectory().toString() + "/test.txt";
File file = new File(storage);
Uri uri;
if (Build.VERSION.SDK_INT < 24) {
uri = Uri.fromFile(file);
} else {
uri = Uri.parse(file.getPath()); // My work-around for new SDKs, causes ActivityNotFoundException in API 10.
}
Intent viewFile = new Intent(Intent.ACTION_VIEW);
viewFile.setDataAndType(uri, "text/plain");
startActivity(viewFile);
fromFile ()이 A 파일 포인터를 사용하는 것 같습니다. 메모리 주소가 모든 앱에 노출되면 안전하지 않을 수 있습니다. 그러나 파일 경로 String은 누구에게도 해를 끼치 지 않으므로 FileUriExposedException을 던지지 않고 작동합니다.
API 레벨 9 ~ 27에서 테스트되었습니다! 다른 앱에서 편집 할 텍스트 파일을 성공적으로 엽니 다. FileProvider 또는 Android 지원 라이브러리가 전혀 필요하지 않습니다.
fileProvider를 사용하는 것이 좋습니다. 그러나 다음과 같은 간단한 해결 방법을 사용할 수 있습니다.
경고 : 다음 Android 릴리스에서 수정 될 예정입니다 -https : //issuetracker.google.com/issues/37122890#comment4
바꾸다:
startActivity(intent);
으로
startActivity(Intent.createChooser(intent, "Your title"));
activity에 아래 코드를 붙여 넣으세요 onCreate()
.
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
URI 노출을 무시합니다.
해피 코딩 :-)
위에 주어진 Palash의 대답을 사용했지만 다소 불완전하여 이와 같은 권한을 제공해야했습니다
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
uri = FileProvider.getUriForFile(this, getPackageName() + ".provider", new File(path));
List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}else {
uri = Uri.fromFile(new File(path));
}
intent.setDataAndType(uri, "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
onCreate () 활동에 아래 코드를 붙여 넣으십시오.
StrictMode.VmPolicy.Builder 빌더 = 새로운 StrictMode.VmPolicy.Builder (); StrictMode.setVmPolicy (builder.build ());
URI 노출을 무시합니다.
이 두 줄을 onCreate에 추가하십시오.
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
공유 방법
File dir = new File(Environment.getExternalStorageDirectory(), "ColorStory");
File imgFile = new File(dir, "0.png");
Intent sendIntent = new Intent(Intent.ACTION_VIEW);
sendIntent.setType("image/*");
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + imgFile));
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(sendIntent, "Share images..."));
서버에서 pdf를 다운로드하려면 서비스 클래스에 아래 코드를 추가하십시오. 도움이 되었기를 바랍니다.
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName + ".pdf");
intent = new Intent(Intent.ACTION_VIEW);
//Log.e("pathOpen", file.getPath());
Uri contentUri;
contentUri = Uri.fromFile(file);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= 24) {
Uri apkURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", file);
intent.setDataAndType(apkURI, "application/pdf");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
intent.setDataAndType(contentUri, "application/pdf");
}
그리고 예, 매니페스트에 권한과 공급자를 추가하는 것을 잊지 마십시오.
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
이유를 모르겠습니다. Pkosta ( https://stackoverflow.com/a/38858040 ) 와 똑같은 모든 작업을 수행 했지만 계속 오류가 발생했습니다.
java.lang.SecurityException: Permission Denial: opening provider redacted from ProcessRecord{redacted} (redacted) that is not exported from uid redacted
이 문제에 시간을 낭비했습니다. 그 범인? Kotlin.
val playIntent = Intent(Intent.ACTION_VIEW, uri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent
getIntent().addFlags
새로 선언 된 playIntent에서 작동하는 대신 실제로 설정 되었습니다.
여기 내 솔루션 :
에서 Manifest.xml
<application
android:name=".main.MainApp"
android:allowBackup="true"
android:icon="@drawable/ic_app"
android:label="@string/application_name"
android:logo="@drawable/ic_app_logo"
android:theme="@style/MainAppBaseTheme">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
의 고해상도 / XML / provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
내 조각에는 다음 코드가 있습니다.
Uri myPhotoFileUri = FileProvider.getUriForFile(getActivity(), getActivity().getApplicationContext().getPackageName() + ".provider", myPhotoFile);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(MediaStore.EXTRA_OUTPUT, myPhotoFileUri);
Тhat's all you need.
Also not need to create
public class GenericFileProvider extends FileProvider {}
I test on Android 5.0, 6.0 and Android 9.0 and it's success work.
i put this method so imageuri path easily get in content.
enter code here
public Uri getImageUri(Context context, Bitmap inImage)
{
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
inImage.compress(Bitmap.CompressFormat.PNG, 100, bytes);
String path = MediaStore.Images.Media.insertImage(context.getContentResolver(),
inImage, "Title", null);
return Uri.parse(path);
}
I know this is a pretty old question but this answer is for future viewers. So I've encountered a similar problem and after researching, I've found an alternative to this approach.
Your Intent here for eg: To view your image from your path in Kotlin
val intent = Intent()
intent.setAction(Intent.ACTION_VIEW)
val file = File(currentUri)
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val contentURI = getContentUri(context!!, file.absolutePath)
intent.setDataAndType(contentURI,"image/*")
startActivity(intent)
Main Function below
private fun getContentUri(context:Context, absPath:String):Uri? {
val cursor = context.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
arrayOf<String>(MediaStore.Images.Media._ID),
MediaStore.Images.Media.DATA + "=? ",
arrayOf<String>(absPath), null)
if (cursor != null && cursor.moveToFirst())
{
val id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID))
return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, Integer.toString(id))
}
else if (!absPath.isEmpty())
{
val values = ContentValues()
values.put(MediaStore.Images.Media.DATA, absPath)
return context.getContentResolver().insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
}
else
{
return null
}
}
Likewise, instead of an image, you can use any other file format like pdf and in my case, it worked just fine
https://stackoverflow.com/a/38858040/395097 this answer is complete.
This answer is for - you already have an app which was targeting below 24, and now you are upgrading to targetSDKVersion >= 24.
In Android N, only the file uri exposed to 3rd party app is changed. (Not the way we were using it before). So change only the places where you are sharing the path with 3rd party app (Camera in my case)
In our app we were sending uri to Camera app, in that location we are expecting the camera app to store the captured image.
- For android N, we generate new Content:// uri based url pointing to file.
- We generate usual File api based path for the same (using older method).
Now we have 2 different uri for same file. #1 is shared with Camera app. If the camera intent is success, we can access the image from #2.
Hope this helps.
Xamarin.Android
Note: The path xml/provider_paths.xml (.axml) couldn't be resolved, even after making the xml folder under Resources (maybe it can be put in an existing location like Values, didn't try), so I resorted to this which works for now. Testing showed that it only needs to be called once per application run (which makes sense being that it changes the operational state of the host VM).
Note: xml needs to be capitalized, so Resources/Xml/provider_paths.xml
Java.Lang.ClassLoader cl = _this.Context.ClassLoader;
Java.Lang.Class strictMode = cl.LoadClass("android.os.StrictMode");
System.IntPtr ptrStrictMode = JNIEnv.FindClass("android/os/StrictMode");
var method = JNIEnv.GetStaticMethodID(ptrStrictMode, "disableDeathOnFileUriExposure", "()V");
JNIEnv.CallStaticVoidMethod(strictMode.Handle, method);
@Pkosta 's answer is one way of doing this.
Besides using FileProvider
, you can also insert the file into MediaStore
(especially for image and video files), because files in MediaStore are accessible to every app:
The MediaStore is primarily aimed at video, audio and image MIME types, however beginning with Android 3.0 (API level 11) it can also store non-media types (see MediaStore.Files for more info). Files can be inserted into the MediaStore using scanFile() after which a content:// style Uri suitable for sharing is passed to the provided onScanCompleted() callback. Note that once added to the system MediaStore the content is accessible to any app on the device.
For example, you can insert a video file to MediaStore like this:
ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.DATA, videoFilePath);
Uri contentUri = context.getContentResolver().insert(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
contentUri
is like content://media/external/video/media/183473
, which can be passed directly to Intent.putExtra
:
intent.setType("video/*");
intent.putExtra(Intent.EXTRA_STREAM, contentUri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
activity.startActivity(intent);
This works for me, and save the hassles of using FileProvider
.
In my case I got rid of the exception by replacing SetDataAndType
with just SetData
.
'Development Tip' 카테고리의 다른 글
Java에서 배열을 집합으로 변환하는 방법 (0) | 2020.09.30 |
---|---|
테이블이있는 경우 삭제하는 방법은 무엇입니까? (0) | 2020.09.30 |
TypeScript 문자열을 숫자로 변환 (0) | 2020.09.30 |
공백 문자를 인코딩하는 URL : + 또는 % 20? (0) | 2020.09.30 |
HTML의 레이아웃에 테이블을 사용하지 않는 이유는 무엇입니까? (0) | 2020.09.30 |