android.os.FileUriExposedException: file: /// st Storage / emulation / 0 / test.txt được hiển thị ngoài ứng dụng thông qua Intent.getData ()


738

Ứng dụng bị sập khi tôi đang cố mở một tệp. Nó hoạt động dưới Android Nougat, nhưng trên Android Nougat thì nó gặp sự cố. Nó chỉ gặp sự cố khi tôi cố mở một tệp từ thẻ SD, không phải từ phân vùng hệ thống. Một số vấn đề cho phép?

Mã mẫu:

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

Nhật ký:

android.os.FileUriExposedException: file: /// st Storage / emulation / 0 / test.txt được hiển thị ngoài ứng dụng thông qua Intent.getData ()

Biên tập:

Khi nhắm mục tiêu Android Nougat, file://URI không được phép nữa. Chúng ta nên sử dụng content://URI thay thế. Tuy nhiên, ứng dụng của tôi cần mở tệp trong thư mục gốc. Có ý kiến ​​gì không?


20
Tôi cảm thấy như đây là một sai lầm khiến cuộc sống của các nhà phát triển ứng dụng trở nên khó khăn không cần thiết. Phải gói "FileProvider" và "thẩm quyền" với mỗi ứng dụng, có vẻ như là bản tóm tắt Enterprisey. Phải thêm một cờ cho mọi mục đích tập tin có vẻ khó xử và có thể không cần thiết. Phá vỡ khái niệm thanh lịch của "con đường" là khó chịu. Và lợi ích là gì? Chọn lọc cấp quyền truy cập bộ nhớ cho các ứng dụng (trong khi hầu hết các ứng dụng có quyền truy cập sdcard đầy đủ, đặc biệt là các ứng dụng hoạt động trên các tệp)?
nyanpasu64

2
Hãy thử điều này, stackoverflow
hảo.com/a/52695444/4997704

Câu trả lời:


1316

Nếu là của bạn targetSdkVersion >= 24, thì chúng ta phải sử dụng FileProviderlớp để cấp quyền truy cập vào tệp hoặc thư mục cụ thể để làm cho chúng có thể truy cập được đối với các ứng dụng khác. Chúng tôi tạo lớp kế thừa của riêng mình FileProviderđể đảm bảo FileProvider của chúng tôi không xung đột với FileProviders được khai báo trong các phụ thuộc đã nhập như được mô tả ở đây .

Các bước để thay thế file://URI bằng content://URI:

  • Thêm một lớp mở rộng FileProvider

    public class GenericFileProvider extends FileProvider {}
  • Thêm một FileProvider <provider>thẻ trong AndroidManifest.xmldưới <application>thẻ. Chỉ định một quyền hạn duy nhất cho android:authoritiesthuộc tính để tránh xung đột, các phụ thuộc được nhập có thể chỉ định ${applicationId}.providervà các quyền hạn thường được sử dụng khác.

<?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}.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"/>
        </provider>
    </application>
</manifest>
  • Sau đó tạo một provider_paths.xmltập tin trong res/xmlthư mục. Thư mục có thể cần thiết để tạo nếu nó không tồn tại. Nội dung của tập tin được hiển thị dưới đây. Nó mô tả rằng chúng tôi muốn chia sẻ quyền truy cập vào Bộ nhớ ngoài tại thư mục gốc (path=".")với tên bên ngoài .
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>
  • Bước cuối cùng là thay đổi dòng mã bên dưới trong

    Uri photoURI = Uri.fromFile(createImageFile());

    đến

    Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", createImageFile());
  • Chỉnh sửa: Nếu bạn đang sử dụng ý định làm cho hệ thống mở tệp của mình, bạn có thể cần thêm dòng mã sau:

    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

Vui lòng tham khảo, mã đầy đủ và giải pháp đã được giải thích ở đây.


62
Tôi chỉ cần thêm aim.setFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION);
alorma

24
Nó sẽ hoạt động cho tất cả các phiên bản Android hay chỉ từ API 24?
nhà phát triển Android


11
@rockhammer Tôi mới thử nghiệm điều này với Android 5.0, 6.0, 7.1 và 8.1, nó hoạt động trong mọi trường hợp. Vì vậy, (Build.VERSION.SDK_INT > M)điều kiện là vô ích.
Sébastien

66
FileProviderchỉ nên được mở rộng nếu bạn muốn ghi đè bất kỳ hành vi mặc định nào, nếu không thì sử dụng android:name="android.support.v4.content.FileProvider". Xem developer.android.com/reference/android/support/v4/content/
trộm

313

Bên cạnh giải pháp sử dụng FileProvider, có một cách khác để giải quyết vấn đề này. Chỉ cần đặt

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());

trong Application.onCreate(). Theo cách này, VM bỏ qua URIphơi sáng tập tin .

phương pháp

builder.detectFileUriExposure()

cho phép kiểm tra phơi sáng tập tin, đây cũng là hành vi mặc định nếu chúng tôi không thiết lập VmPolicy.

Tôi gặp phải một vấn đề là nếu tôi sử dụng content:// URIđể gửi một cái gì đó, một số ứng dụng không thể hiểu được. Và hạ cấp target SDKphiên bản không được phép. Trong trường hợp này giải pháp của tôi là hữu ích.

Cập nhật:

Như đã đề cập trong bình luận, StrictMode là công cụ chẩn đoán và không được sử dụng cho vấn đề này. Khi tôi đăng câu trả lời này một năm trước, nhiều ứng dụng chỉ có thể nhận được Tệp uris. Họ chỉ gặp sự cố khi tôi cố gửi uri FileProvider cho họ. Điều này đã được sửa trong hầu hết các ứng dụng, vì vậy chúng ta nên sử dụng giải pháp FileProvider.


1
@LaurynasG Từ API 18 đến 23, android không kiểm tra phơi nhiễm uri theo mặc định. Gọi phương thức này cho phép kiểm tra này. Từ API 24, android thực hiện kiểm tra này theo mặc định. Nhưng chúng ta có thể vô hiệu hóa nó bằng cách thiết lập một cái mới VmPolicy.
hqzxzwb

Có bất kỳ bước nào khác cần thiết để làm việc này? Không hoạt động vì nó là viết tắt của Moto G của tôi chạy Android 7.0.
CKP78

3
Làm thế nào điều này có thể giải quyết vấn đề này, tuy nhiên, StrictMode là một công cụ chẩn đoán nên được bật trong chế độ nhà phát triển chứ không phải chế độ phát hành ???
Imene Noomene

1
@ImeneNoomene Thật ra chúng tôi đang vô hiệu hóa StrictMode tại đây. Có vẻ hợp lý rằng StrictMode không nên được bật trong chế độ phát hành, nhưng trên thực tế, Android cho phép một số tùy chọn StrictMode theo mặc định bất kể chế độ gỡ lỗi hoặc chế độ phát hành. Nhưng bằng cách này hay cách khác, câu trả lời này chỉ có nghĩa là một cách giải quyết khi một số ứng dụng mục tiêu chưa được chuẩn bị để nhận uris nội dung. Bây giờ hầu hết các ứng dụng đã thêm hỗ trợ cho uris nội dung, chúng ta nên sử dụng mẫu FileProvider.
hqzxzwb

3
@ImeneNoomene Tôi hoàn toàn với bạn trong sự phẫn nộ của bạn. Bạn nói đúng, đây là một công cụ chẩn đoán, hoặc ít nhất là nó đã có từ lâu khi tôi thêm nó vào các dự án của mình. Điều này là siêu bực bội! StrictMode.enableDefaults();, mà tôi chỉ chạy trên các bản dựng phát triển của mình sẽ không xảy ra sự cố này - vì vậy bây giờ tôi có một ứng dụng sản xuất gặp sự cố nhưng nó không bị sập khi đang phát triển. Về cơ bản, cho phép một công cụ chẩn đoán ở đây ẩn giấu một vấn đề nghiêm trọng. Cảm ơn @hqzxzwb đã giúp tôi làm sáng tỏ điều này.
Jon

174

Nếu targetSdkVersioncao hơn 24 , thì FileProvider được sử dụng để cấp quyền truy cập.

Tạo một file xml (Đường dẫn: 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>


Thêm một nhà cung cấp trong 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>

Nếu bạn đang sử dụng androidx , đường dẫn FileProvider sẽ là:

 android:name="androidx.core.content.FileProvider"

thay thế

Uri uri = Uri.fromFile(fileImagePath);

đến

Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider",fileImagePath);

Chỉnh sửa: Trong khi bạn bao gồm URI với Intentđảm bảo thêm dòng bên dưới:

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

và bạn tốt để đi. Hy vọng nó giúp.


2
@MaksimKniazev Bạn có thể mô tả ngắn gọn về lỗi của mình không? Để tôi có thể giúp bạn
Pankaj Lilan

1
@PankajLilan, tôi đã làm chính xác những gì bạn nói. Nhưng mỗi khi tôi mở pdf của mình trong ứng dụng khác thì nó lại xuất hiện trống (nó lưu chính xác). Tôi có cần chỉnh sửa xml không? Tôi cũng đã thêm FLAG_GRANT_READ_URI_PERMISSION;
Felipe Castilhos

1
Sai lầm của tôi, tôi đã thêm sự cho phép vào mục đích sai. Đây là câu trả lời đúng nhất và đơn giản nhất. Cảm ơn bạn!
Felipe Castilhos

2
Nó ném ngoại lệ java.lang.IllegalArgumentException: Không thể tìm thấy root được cấu hình có chứa / Đường dẫn tệp của tôi là / st Storage / emulation / 0 / SAMManage_DCIM / Document / Decice7298file_74.pdf. bạn có thể giúp tôi không
Jagdish Bhavsar

1
Không biết tại sao nhưng tôi phải thêm cả hai quyền READ và WRITE: target.addFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION) target.addFlags (Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
hộp

160

Nếu ứng dụng của bạn nhắm mục tiêu API 24+ và bạn vẫn muốn / cần sử dụng tệp: // ý định, bạn có thể sử dụng cách hacky để vô hiệu hóa kiểm tra thời gian chạy:

if(Build.VERSION.SDK_INT>=24){
   try{
      Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure");
      m.invoke(null);
   }catch(Exception e){
      e.printStackTrace();
   }
}

Phương thức StrictMode.disableDeathOnFileUriExposuređược ẩn và ghi lại là:

/**
* Used by lame internal apps that haven't done the hard work to get
* themselves off file:// Uris yet.
*/

Vấn đề là ứng dụng của tôi không khập khiễng, nhưng không muốn bị tê liệt bằng cách sử dụng nội dung: // ý định không được hiểu bởi nhiều ứng dụng ngoài kia. Ví dụ: mở tệp mp3 có nội dung: // lược đồ cung cấp ít ứng dụng hơn nhiều so với khi mở cùng tệp: // lược đồ. Tôi không muốn trả tiền cho các lỗi thiết kế của Google bằng cách giới hạn chức năng của ứng dụng.

Google muốn các nhà phát triển sử dụng lược đồ nội dung, nhưng hệ thống chưa được chuẩn bị cho điều này, trong nhiều năm, các ứng dụng đã được tạo để sử dụng Tệp không phải là "nội dung", các tệp có thể được chỉnh sửa và lưu lại, trong khi các tệp được cung cấp qua lược đồ nội dung không thể (có thể họ?).


3
"trong khi các tệp được phân phát trên lược đồ nội dung không thể (có thể không?)." - chắc chắn, nếu bạn có quyền truy cập ghi vào nội dung. ContentResolvercó cả openInputStream()openOutputStream(). Một cách ít rắc rối hơn để làm điều này là chỉ tự cấu hình các quy tắc VM và không kích hoạt file Uriquy tắc này.
CommonsWare

1
Chính xác. Thật vất vả khi bạn xây dựng toàn bộ ứng dụng của mình, sau đó tìm hiểu sau khi nhắm mục tiêu 25 tất cả các phương pháp máy ảnh của bạn bị hỏng. Điều này làm việc cho tôi cho đến khi tôi có thời gian để làm điều đó đúng cách.
Matt W

5
Hoạt động trên Android 7. Cảm ơn
Anton Kizema

4
Hoạt động trên Android 8 cũng vậy, đã thử nghiệm trên Huawei Nexus 6P.
Gonzalo Ledezma Torres

4
Tôi xác nhận điều này đang hoạt động trên sản xuất (tôi có hơn 500.000 người dùng), hiện tại phiên bản 8.1 là phiên bản cao nhất và nó hoạt động trên đó.
Eli

90

Nếu bạn từ targetSdkVersion24 tuổi trở lên, bạn không thể sử dụng file: Uricác giá trị trong Intentscác thiết bị Android 7.0+ .

Lựa chọn của bạn là:

  1. Thả bạn targetSdkVersionxuống 23 hoặc thấp hơn, hoặc

  2. Đặt nội dung của bạn vào bộ nhớ trong, sau đó sử dụngFileProvider để cung cấp nội dung có chọn lọc cho các ứng dụng khác

Ví dụ:

Intent i=new Intent(Intent.ACTION_VIEW, FileProvider.getUriForFile(this, AUTHORITY, f));

i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(i);

(từ dự án mẫu này )


Cảm ơn câu trả lời. Điều gì xảy ra khi tôi sử dụng điều này với các tập tin trên /systemphân vùng? Mọi ứng dụng sẽ có thể truy cập phân vùng này mà không cần root.
Thomas Vos

2
@SuperThomasLab: Tôi sẽ không tin vào mọi thứ /systemcó thể đọc được trên thế giới. Điều đó đang được nói, tôi đoán là bạn vẫn sẽ có ngoại lệ này. Tôi nghi ngờ rằng họ chỉ đang kiểm tra lược đồ và không cố xác định xem tập tin có thực sự dễ đọc không. Tuy nhiên, FileProvidersẽ không giúp bạn, vì bạn không thể dạy nó phục vụ từ đó /system. Bạn có thể tạo một chiến lược tùy chỉnh cho tôiStreamProvider , hoặc tự mình thực hiện ContentProvider, để vượt qua vấn đề.
CommonsWare

Vẫn đang suy nghĩ làm thế nào tôi sẽ giải quyết vấn đề này .. Ứng dụng tôi đang cập nhật với hỗ trợ Android N là một trình duyệt gốc. Nhưng bây giờ bạn không thể mở bất kỳ tập tin nào nữa trong thư mục gốc. ( /data, /system), vì "sự thay đổi tốt" này.
Thomas Vos

1
những hạn chế quan trọng nhất để giảm targetSdkVersion xuống 23 là gì? thnx
rommex

2
@rommex: Tôi không biết những gì đủ điều kiện là "quan trọng nhất". Ví dụ: người dùng làm việc ở chế độ chia đôi màn hình hoặc trên các thiết bị đa cửa sổ dạng tự do (Chromebook, Samsung DeX) sẽ được thông báo rằng ứng dụng của bạn có thể không hoạt động với nhiều cửa sổ. Điều đó có quan trọng hay không là tùy thuộc vào bạn.
CommonsWare

47

Trước tiên, bạn cần thêm một nhà cung cấp vào AndroidManifest của bạn

  <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>

bây giờ tạo một tệp trong thư mục tài nguyên xml (nếu sử dụng android studio, bạn có thể nhấn Alt + Enter sau khi tô sáng file_paths và chọn tạo tùy chọn tài nguyên xml)

Tiếp theo trong tập tin file_paths nhập

<?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>

Ví dụ này dành cho đường dẫn bên ngoài, bạn có thể tham khảo tại đây để có thêm tùy chọn. Điều này sẽ cho phép bạn chia sẻ các tệp trong thư mục đó và thư mục con của nó.

Bây giờ tất cả những gì còn lại là để tạo ra ý định như sau:

    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();
    }

EDIT : Tôi đã thêm thư mục gốc của thẻ sd trong file_paths. Tôi đã kiểm tra mã này và nó hoạt động.


1
Cảm ơn vì điều này. Tôi cũng muốn cho bạn biết rằng có một cách tốt hơn để có được phần mở rộng tập tin. String extension = android.webkit.MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString()); Ngoài ra, tôi khuyên mọi người tìm kiếm câu trả lời để đọc qua FileProvider trước và hiểu những gì bạn đang giải quyết ở đây với quyền truy cập tệp trong Android N trở lên. Có các tùy chọn cho lưu trữ nội bộ so với lưu trữ ngoài và cũng cho đường dẫn tệp thông thường so với đường dẫn bộ đệm.
praneetloke

2
Tôi đã nhận được ngoại lệ sau: java.lang.IllegalArgumentException: Failed to find configured root ...và điều duy nhất hoạt động là <files-path path="." name="files_root" />trên tệp xml thay vì <external-path .... Tập tin của tôi đã được lưu trong bộ nhớ trong.
steliosf

26

@palash k answer là chính xác và hoạt động cho các tệp lưu trữ nội bộ, nhưng trong trường hợp của tôi, tôi cũng muốn mở tệp từ bộ nhớ ngoài, ứng dụng của tôi đã bị sập khi mở tệp từ bộ nhớ ngoài như sdcard và usb, nhưng tôi quản lý để giải quyết vấn đề bằng cách sửa đổi Carrier_paths.xml từ câu trả lời được chấp nhận

thay đổi Carrier_paths.xml như bên dưới

<?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>

và trong lớp java (Không thay đổi vì câu trả lời được chấp nhận chỉ là một chỉnh sửa nhỏ)

Uri uri=FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".provider", File)

Điều này giúp tôi khắc phục sự cố cho các tệp từ kho lưu trữ bên ngoài, Hy vọng điều này sẽ giúp một số người có vấn đề tương tự như của tôi :)


1
Bạn đã tìm về <root-pathđâu xin vui lòng? Nó đang hoạt động. <external-path path="Android/data/${applicationId}/" name="files_root" />không có tác dụng cho các tập tin mở từ bộ nhớ ngoài.
t0m

tôi tìm thấy điều này từ các kết quả tìm kiếm khác nhau, hãy để tôi kiểm tra lại và quay lại với bạn
càng sớm càng tốt

còn bộ nhớ ngoài mà bạn đề cập là thẻ sd hay bộ lưu trữ sẵn có?
Ramz

Xin lỗi vì sự không chính xác. Ý tôi là Android/data/${applicationId}/trong SDcard.
t0m

1
Cần thêm điều này vào mục đích: aim.addFlags (Intent.FLAG_GRANT_READ_URI_PERMISSION);
s-hunter

26

Giải pháp của tôi là 'Uri.parse' Đường dẫn tệp dưới dạng Chuỗi, thay vì sử dụng Uri.fromFile ().

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, doesn't work in Android 10.
}
Intent viewFile = new Intent(Intent.ACTION_VIEW);
viewFile.setDataAndType(uri, "text/plain");
startActivity(viewFile);

Có vẻ như fromFile () sử dụng Con trỏ tệp, mà tôi cho rằng có thể không an toàn khi địa chỉ bộ nhớ được hiển thị cho tất cả các ứng dụng. Nhưng Chuỗi đường dẫn tệp không bao giờ làm tổn thương bất kỳ ai, vì vậy nó hoạt động mà không cần ném FileUriExposedException.

Đã thử nghiệm trên các cấp độ API 9 đến 27! Mở thành công tệp văn bản để chỉnh sửa trong một ứng dụng khác. Không yêu cầu FileProvider, cũng như Thư viện hỗ trợ Android.


Tôi ước tôi đã nhìn thấy điều này đầu tiên. Tôi đã không chứng minh rằng nó hoạt động với tôi, nhưng nó ít cồng kềnh hơn so với FileProvider.
Dale

Một lưu ý về lý do tại sao điều này thực sự hoạt động: Không phải là con trỏ Tệp gây ra sự cố, nhưng thực tế là ngoại lệ chỉ xảy ra nếu bạn có đường dẫn với 'file: //', được tự động thêm vào từFile, nhưng không được phân tích cú pháp .
Xmister

3
Điều này không có ngoại lệ, nhưng nó cũng không thể gửi tệp đến ứng dụng liên quan. Vì vậy, không làm việc cho tôi.
Serdar Samancıoğlu

1
Điều này sẽ thất bại trên Android 10 trở lên, vì bạn không thể cho rằng ứng dụng kia có quyền truy cập vào bộ nhớ ngoài thông qua hệ thống tệp.
CommonsWare

24

Chỉ cần dán đoạn mã dưới đây vào hoạt động onCreate ()

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build());

Nó sẽ bỏ qua tiếp xúc với URI


1
đây là một trong những giải pháp nhưng không phải là tiêu chuẩn. Stil những người đã đánh giá thấp các câu trả lời là sai vì đây cũng là mã làm việc với giải pháp làm việc.
saksham

23

Chỉ cần dán mã dưới đây trong hoạt động onCreate().

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); 
StrictMode.setVmPolicy(builder.build());

Nó sẽ bỏ qua tiếp xúc với URI.

Chúc mừng mã hóa :-)


1
Nhược điểm này là gì?
James F

1
Điều này sẽ thất bại trên Android 10 trở lên, vì bạn không thể cho rằng ứng dụng kia có quyền truy cập vào bộ nhớ ngoài thông qua hệ thống tệp.
CommonsWare

18

Sử dụng fileProvider là cách để đi. Nhưng bạn có thể sử dụng cách giải quyết đơn giản này:

CẢNH BÁO : Nó sẽ được sửa trong bản phát hành Android tiếp theo - https://issuetracker.google.com/issues/37122890#comment4

thay thế:

startActivity(intent);

bởi

startActivity(Intent.createChooser(intent, "Your title"));

7
Trình chọn sẽ được Google vá sớm để chứa cùng một kiểm tra. Đây không phải là một giải pháp.
Con trỏ Null

Cái này hoạt động nhưng sẽ không hoạt động trong các phiên bản Android trong tương lai.
Diljeet

13

Tôi đã sử dụng câu trả lời của Palash ở trên nhưng nó chưa hoàn chỉnh, tôi phải cung cấp quyền như thế này

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);

11

Chỉ cần dán đoạn mã dưới đây vào hoạt động onCreate ()

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder (); StrictMode.setVmPolicy (builder.build ());

Nó sẽ bỏ qua tiếp xúc với URI


Điều này, điều này sẽ loại bỏ các chính sách nghiêm ngặt. và sẽ bỏ qua cảnh báo an ninh. Không phải là một giải pháp tốt.
Inspire_coding

Nó cũng sẽ thất bại trên Android 10 trở lên, vì bạn không thể cho rằng ứng dụng kia có quyền truy cập vào bộ nhớ ngoài thông qua hệ thống tệp.
CommonsWare

7

thêm hai dòng này vào onCreate

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
    StrictMode.setVmPolicy(builder.build());

Phương pháp chia sẻ

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..."));

Điều này sẽ thất bại trên Android 10 trở lên, vì bạn không thể cho rằng ứng dụng kia có quyền truy cập vào bộ nhớ ngoài thông qua hệ thống tệp.
CommonsWare

7

Đây là giải pháp của tôi:

trong 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>

trong res / xml / Carrier_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>

trong đoạn của tôi, tôi có mã tiếp theo:

 Uri myPhotoFileUri = FileProvider.getUriForFile(getActivity(), getActivity().getApplicationContext().getPackageName() + ".provider", myPhotoFile);               
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, myPhotoFileUri);

Tất cả những gì bạn cần.

Cũng không cần tạo

public class GenericFileProvider extends FileProvider {}

Tôi đã thử nghiệm trên Android 5.0, 6.0 và Android 9.0 và nó thành công.


Tôi đã thử nghiệm giải pháp này và nó hoạt động tốt với một thay đổi nhỏ: aim.flags = Intent.FLAG_ACTIVITY_NEW_TASK aim.putExtra (Intent.EXTRA_STREAM, myPhotoFileUri) aim.type = "image / png" startAct Chia sẻ hình ảnh qua ")) Nó hoạt động tốt trên Android 7 và 8.
Inspire_coding 16/10/19

4

Để tải xuống pdf từ máy chủ, hãy thêm mã dưới đây vào lớp dịch vụ của bạn. Hy vọng điều này là hữu ích cho bạn.

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");
    }

Và có, đừng quên thêm quyền và nhà cung cấp trong bảng kê khai của bạn.

<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>

1
@xml/provider_paths
adityasnl

1
@Heisenberg vui lòng tham khảo bài viết của Rahul Upadhyay từ url: stackoverflow.com/questions/38555301/ Tiết
Bhoomika Chauhan

3

Tôi không biết tại sao, tôi đã làm mọi thứ giống hệt như LOLosta ( https://stackoverflow.com/a/38858040 ) nhưng vẫn bị lỗi:

java.lang.SecurityException: Permission Denial: opening provider redacted from ProcessRecord{redacted} (redacted) that is not exported from uid redacted

Tôi đã lãng phí hàng giờ cho vấn đề này. Thủ phạm? Kotlin.

val playIntent = Intent(Intent.ACTION_VIEW, uri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

intentđã thực sự thiết lập getIntent().addFlagsthay vì hoạt động trên playIntent mới khai báo của tôi.


2

tôi đặt phương thức này để đường dẫn imageuri dễ dàng đi vào nội dung.

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);
}

2
As of Android N, in order to work around this issue, you need to use the FileProvider API

Có 3 bước chính ở đây như được đề cập dưới đây

Bước 1: Nhập đơn

<manifest ...>
    <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>
</manifest>

Bước 2: Tạo tệp XML res / xml / Carrier_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>

Bước 3: Thay đổi mã

File file = ...;
Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
// Old Approach
    install.setDataAndType(Uri.fromFile(file), mimeType);
// End Old approach
// New Approach
    Uri apkURI = FileProvider.getUriForFile(
                             context, 
                             context.getApplicationContext()
                             .getPackageName() + ".provider", file);
    install.setDataAndType(apkURI, mimeType);
    install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// End New Approach
    context.startActivity(install);

1

Tôi biết đây là một câu hỏi khá cũ nhưng câu trả lời này dành cho người xem trong tương lai. Vì vậy, tôi đã gặp phải một vấn đề tương tự và sau khi nghiên cứu, tôi đã tìm thấy một giải pháp thay thế cho phương pháp này.

Ý định của bạn ở đây ví dụ: Để xem hình ảnh của bạn từ đường dẫn của bạn trong 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)

Chức năng chính dưới đây

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
        }
    }

Tương tự như vậy, thay vì một hình ảnh, bạn có thể sử dụng bất kỳ định dạng tệp nào khác như pdf và trong trường hợp của tôi, nó hoạt động tốt


0

Tôi đã dành gần một ngày để tìm hiểu tại sao tôi lại nhận được ngoại lệ này. Sau rất nhiều cuộc đấu tranh, cấu hình này đã hoạt động hoàn hảo ( Kotlin ):

AndroidManifest.xml

<provider
  android:name="androidx.core.content.FileProvider"
  android:authorities="com.lomza.moviesroom.fileprovider"
  android:exported="false"
  android:grantUriPermissions="true">
  <meta-data
    android:name="android.support.FILE_PROVIDER_PATHS"
    android:resource="@xml/file_paths" />
</provider>

file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>
  <files-path name="movies_csv_files" path="."/>
</paths>

Ý định

fun goToFileIntent(context: Context, file: File): Intent {
    val intent = Intent(Intent.ACTION_VIEW)
    val contentUri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file)
    val mimeType = context.contentResolver.getType(contentUri)
    intent.setDataAndType(contentUri, mimeType)
    intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION

    return intent
}

Tôi giải thích toàn bộ quá trình ở đây .


-1

https://stackoverflow.com/a/38858040/395097 câu trả lời này đã hoàn tất.

Câu trả lời này dành cho - bạn đã có một ứng dụng được nhắm mục tiêu dưới 24 và hiện bạn đang nâng cấp lên targetSDKVersion> = 24.

Trong Android N, chỉ có tệp uri tiếp xúc với ứng dụng bên thứ 3 được thay đổi. (Không phải cách chúng tôi đã sử dụng nó trước đây). Vì vậy, chỉ thay đổi những nơi bạn đang chia sẻ đường dẫn với ứng dụng bên thứ 3 (Camera trong trường hợp của tôi)

Trong ứng dụng của chúng tôi, chúng tôi đã gửi uri đến ứng dụng Camera, tại vị trí đó, chúng tôi đang mong đợi ứng dụng camera sẽ lưu trữ hình ảnh được chụp.

  1. Đối với Android N, chúng tôi tạo Nội dung mới: // url dựa trên url chỉ vào tệp.
  2. Chúng tôi tạo đường dẫn dựa trên tệp api thông thường cho cùng (sử dụng phương thức cũ hơn).

Bây giờ chúng tôi có 2 uri khác nhau cho cùng một tập tin. # 1 được chia sẻ với ứng dụng Camera. Nếu mục đích của máy ảnh là thành công, chúng ta có thể truy cập hình ảnh từ # 2.

Hi vọng điêu nay co ich.


1
Bạn đang tham khảo một câu trả lời đã được đăng ở đây, nếu bạn cần hoàn thành nó, hãy bình luận trong câu trả lời.
IgniteCoders

1
@IgniteCoders Như tôi đã đề cập rõ ràng trong tin nhắn, câu trả lời của tôi bao gồm các trường hợp sử dụng liên quan.
Aram

-1

Xamarin.Android

Lưu ý: Không thể giải quyết đường dẫn xml / Carrier_paths.xml (.axml), ngay cả sau khi tạo thư mục xml trong phần Tài nguyên (có thể đặt ở vị trí hiện tại như Giá trị , không thử), vì vậy tôi đã dùng đến cái này hoạt động bây giờ Thử nghiệm cho thấy rằng nó chỉ cần được gọi một lần cho mỗi lần chạy ứng dụng (điều này có nghĩa là nó thay đổi trạng thái hoạt động của máy chủ VM).

Lưu ý: xml cần được viết hoa, vì vậy Tài nguyên / Xml / nhà cung cấp_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);

-1

Câu trả lời của @Pkosta là một cách để làm điều này.

Bên cạnh việc sử dụng FileProvider, bạn cũng có thể chèn tệp vào MediaStore(đặc biệt đối với tệp hình ảnh và video), vì các tệp trong MediaStore có thể truy cập được cho mọi ứng dụng:

MediaStore chủ yếu nhắm vào các loại MIME video, âm thanh và hình ảnh, tuy nhiên bắt đầu với Android 3.0 (API cấp 11), nó cũng có thể lưu trữ các loại phi phương tiện (xem MediaStore.Files để biết thêm). Các tệp có thể được chèn vào MediaStore bằng scanFile () sau đó nội dung: // style Uri phù hợp để chia sẻ được chuyển đến cuộc gọi lại onScanCompleted () được cung cấp. Lưu ý rằng một khi được thêm vào hệ thống MediaStore, nội dung có thể truy cập được vào bất kỳ ứng dụng nào trên thiết bị.

Ví dụ: bạn có thể chèn tệp video vào MediaStore như thế này:

ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.DATA, videoFilePath);
Uri contentUri = context.getContentResolver().insert(
      MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);

contentUrigiống như content://media/external/video/media/183473, có thể được chuyển trực tiếp đến Intent.putExtra:

intent.setType("video/*");
intent.putExtra(Intent.EXTRA_STREAM, contentUri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
activity.startActivity(intent);

Điều này làm việc cho tôi, và tiết kiệm những rắc rối khi sử dụng FileProvider.


-1

Đơn giản chỉ cần để nó bỏ qua Phơi sáng URI ... Thêm nó sau khi tạo

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build()); 

Điều này sẽ thất bại trên Android 10 trở lên, vì bạn không thể cho rằng ứng dụng kia có quyền truy cập vào bộ nhớ ngoài thông qua hệ thống tệp.
CommonsWare

Dont này phải được sử dụng trong một ứng dụng producction.
Jorgesys

-1

Hãy thử giải pháp này

ĐƯA RA NHỮNG GIẤY PHÉP NÀY TRONG MANIFEST

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

Ý định hình ảnh

Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
                    startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
                }

NHẬN HÌNH ẢNH NGAY LẬP TỨC TRONG ONACTIVITYRESULT

@Override
            protected void onActivityResult(int requestCode, int resultCode, Intent data) {
                super.onActivityResult(requestCode, resultCode, data);
                if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
                    Bundle extras = data.getExtras();
                    Bitmap imageBitmap = (Bitmap) extras.get("data");
                    // CALL THIS METHOD TO GET THE URI FROM THE BITMAP
                    Uri tempUri = getImageUri(getApplicationContext(), imageBitmap);
                    //DO SOMETHING WITH URI
                }
            } 

PHƯƠNG PHÁP NHẬN URI IMAGE

public Uri getImageUri(Context inContext, Bitmap inImage) {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        inImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
        String path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(), inImage, "Title", null);
        return Uri.parse(path);
    }

bất cứ ai có thể cho tôi biết tại sao xuống bỏ phiếu. Đây là giải pháp làm việc 100%.
Abdul Basit Rishi

Điều này chỉ giúp bạn có hình thu nhỏ chứ không phải hình ảnh đầy đủ.
Build3r

-2

Trong trường hợp của tôi, tôi đã loại bỏ ngoại lệ bằng cách thay thế SetDataAndTypebằng chỉ SetData.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.