Cách phổ biến để ghi vào thẻ SD bên ngoài trên Android


76

Trong ứng dụng của tôi, tôi cần lưu trữ nhiều hình ảnh trong bộ nhớ của thiết bị. Các tệp như vậy có xu hướng đáp ứng đầy đủ bộ nhớ của thiết bị và tôi muốn cho phép người dùng có thể chọn thẻ SD bên ngoài làm thư mục đích.

Tôi đã đọc ở khắp mọi nơi rằng Android không cho phép người dùng ghi vào thẻ SD bên ngoài, bằng thẻ SD, ý tôi là thẻ SD bên ngoài và có thể gắn được chứ không phải bộ nhớ ngoài , nhưng các ứng dụng quản lý tệp quản lý để ghi vào SD bên ngoài trên tất cả các phiên bản Android.

Cách tốt hơn để cấp quyền truy cập đọc / ghi vào thẻ SD bên ngoài trên các cấp API khác nhau (Pre-KitKat, KitKat, Lollipop +) là gì?

Cập nhật 1

Tôi đã thử Phương pháp 1 từ câu trả lời của Doomknight nhưng không có kết quả: Như bạn có thể thấy, tôi đang kiểm tra các quyền trong thời gian chạy trước khi cố gắng viết trên SD:

HashSet<String> extDirs = getStorageDirectories();
for(String dir: extDirs) {
    Log.e("SD",dir);
    File f = new File(new File(dir),"TEST.TXT");
    try {
        if(ActivityCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE)==PackageManager.PERMISSION_GRANTED) {
            f.createNewFile();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Nhưng tôi gặp lỗi truy cập, đã thử trên hai thiết bị khác nhau: HTC10 và Shield K1.

10-22 14:52:57.329 30280-30280/? E/SD: /mnt/media_rw/F38E-14F8
10-22 14:52:57.329 30280-30280/? W/System.err: java.io.IOException: open failed: EACCES (Permission denied)
10-22 14:52:57.329 30280-30280/? W/System.err:     at java.io.File.createNewFile(File.java:939)
10-22 14:52:57.329 30280-30280/? W/System.err:     at com.myapp.activities.TestActivity.onResume(TestActivity.java:167)
10-22 14:52:57.329 30280-30280/? W/System.err:     at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1326)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.Activity.performResume(Activity.java:6338)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3336)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3384)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2574)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread.access$900(ActivityThread.java:150)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1399)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:102)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.os.Looper.loop(Looper.java:168)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:5885)
10-22 14:52:57.330 30280-30280/? W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
10-22 14:52:57.330 30280-30280/? W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:819)
10-22 14:52:57.330 30280-30280/? W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:709)
10-22 14:52:57.330 30280-30280/? W/System.err: Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied)
10-22 14:52:57.330 30280-30280/? W/System.err:     at libcore.io.Posix.open(Native Method)
10-22 14:52:57.330 30280-30280/? W/System.err:     at libcore.io.BlockGuardOs.open(BlockGuardOs.java:186)
10-22 14:52:57.330 30280-30280/? W/System.err:     at java.io.File.createNewFile(File.java:932)
10-22 14:52:57.330 30280-30280/? W/System.err:  ... 14 more

ứng dụng hệ thống có thể truy cập từ bên ngoài lưu trữ thẻ SD hoàn toàn nhưng các ứng dụng khác không thể trừ khi hệ điều hành và ứng dụng có quyền truy cập root
Pavneet_Singh

4
@PavneetSingh điều này không đúng, tất cả các ứng dụng khám phá tệp đều có quyền truy cập vào thẻ sd bên ngoài, ngay cả khi không có quyền root.
Vektor88,

bạn đang nói về trình khám phá tệp nào? bởi vì một số trong những nổi tiếng, sử dụng các kịch bản rễ vào thẻ truy cập sd
Pavneet_Singh

1
họ sử dụng phương pháp tôi đã nói với bạn, để kiểm tra, chỉ cần cài đặt hệ điều hành kitkat (chưa được root) và cài đặt ES và cố gắng xóa một tệp khỏi nó, bạn sẽ nhận được một cảnh báo (điều này có thể khiến điện thoại của bạn trở thành cục gạch) yêu cầu áp dụng root xử lý rủi ro của riêng bạn
Pavneet_Singh

1
từ liên kết Tôi thực sự khuyên bạn KHÔNG BAO GIỜ dựa vào mã này , giống như tôi đã nói ứng dụng của bạn không thể làm được điều đó nhưng nhà cung cấp phương tiện truyền thông là một ứng dụng hệ thống nên bạn có thể khai thác tính năng của nó để làm những gì bạn có thể
Pavneet_Singh

Câu trả lời:


121

Tóm lược

Bạn có thể cấp quyền truy cập đọc / ghi cho thẻ SD bên ngoài ở các cấp api khác nhau ( API23 + tại thời điểm chạy ).

Kể từ KitKat, các quyền là không cần thiết nếu bạn sử dụng các thư mục dành riêng cho ứng dụng, nếu không thì bắt buộc .

Cách phổ biến:

Các lịch sử nói rằng không có cách phổ biến để viết vào thẻ SD bên ngoài nhưng vẫn tiếp tục ...

Thực tế này được phân tích bằng những ví dụ về cấu hình bộ nhớ ngoài cho thiết bị .

Cách dựa trên API:

Trước KitKat, hãy thử sử dụng Doomsknight phương pháp 1, phương pháp 2 nếu không.

Yêu cầu quyền trong tệp kê khai (Api <23) và tại thời điểm chạy (Api> = 23).

Cách đề xuất:

ContextCompat.getExternalFilesDirs giải quyết lỗi truy cập khi bạn không cần chia sẻ tệp.

Cách chia sẻ an toàn là sử dụng nhà cung cấp nội dung hoặc Khung truy cập lưu trữ mới .

Cách nhận biết quyền riêng tư:

Kể từ Android Q Beta 4 , theo mặc định, các ứng dụng nhắm mục tiêu đến Android 9 (API cấp 28) trở xuống không có thay đổi.

Các ứng dụng nhắm mục tiêu Android Q theo mặc định (hoặc chọn tham gia) được cung cấp chế độ xem đã lọc vào bộ nhớ ngoài.


1. Câu trả lời ban đầu.

Cách phổ biến để ghi vào thẻ SD bên ngoài trên Android

Không có cách phổ biến nào để ghi vào thẻ SD bên ngoài trên Android do những thay đổi liên tục :

  • Pre-KitKat: nền tảng Android chính thức chưa hỗ trợ thẻ SD trừ một số ngoại lệ.

  • KitKat: API được giới thiệu cho phép ứng dụng truy cập tệp trong thư mục dành riêng cho ứng dụng trên thẻ SD.

  • Lollipop: đã thêm các API để cho phép các ứng dụng yêu cầu quyền truy cập vào các thư mục do các nhà cung cấp khác sở hữu.

  • Nougat: cung cấp một API đơn giản hóa để truy cập các thư mục lưu trữ bên ngoài phổ biến.

  • ... Thay đổi quyền riêng tư của Android Q: Bộ nhớ trong phạm vi ứng dụng và phạm vi phương tiện

Cách tốt hơn để cấp quyền truy cập đọc / ghi cho thẻ SD bên ngoài ở các cấp API khác nhau là gì

Dựa trên câu trả lời của Doomsknightcủa tôi , và các bài đăng trên blog của Dave SmithMark Murphy: 1 , 2 , 3 :


2. Cập nhật câu trả lời.

Cập nhật 1 . Tôi đã thử Phương pháp 1 từ câu trả lời của Doomknight nhưng không có kết quả:

Như bạn có thể thấy, tôi đang kiểm tra các quyền trong thời gian chạy trước khi cố gắng ghi trên SD ...

Tôi sẽ sử dụng các thư mục dành riêng cho ứng dụng để tránh vấn đề về câu hỏi đã cập nhật của bạn và ContextCompat.getExternalFilesDirs()sử dụng tài liệu getExternalFilesDir làm tài liệu tham khảo.

Cải thiện phương pháp heuristics để xác định những gì đại diện cho phương tiện di động dựa trên các cấp api khác nhau nhưandroid.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT

... Nhưng tôi gặp lỗi truy cập, đã thử trên hai thiết bị khác nhau: HTC10 và Shield K1.

Hãy nhớ rằng Android 6.0 hỗ trợ thiết bị lưu trữ di động và các ứng dụng của bên thứ ba phải thông qua Khung truy cập bộ nhớ . Các thiết bị HTC10Shield K1 của bạn có thể là API 23.

Nhật ký của bạn hiển thị quyền bị từ chối truy cập ngoại lệ/mnt/media_rw , chẳng hạn như bản sửa lỗi này cho API 19+:

<permission name="android.permission.WRITE_EXTERNAL_STORAGE" >
<group gid="sdcard_r" />
<group gid="sdcard_rw" />
<group gid="media_rw" /> // this line is added via root in the link to fix it.
</permission>

Tôi chưa bao giờ thử nó vì vậy tôi không thể chia sẻ mã nhưng tôi sẽ tránh forcố gắng viết trên tất cả các thư mục trả về và tìm kiếm thư mục lưu trữ có sẵn tốt nhất để ghi vào dựa trên dung lượng còn lại .

Có lẽ sự thay thế của Gizm0 cho getStorageDirectories()phương pháp của bạn đó là một điểm khởi đầu tốt.

ContextCompat.getExternalFilesDirsgiải quyết vấn đề nếu bạn không cần quyền truy cập vào các thư mục khác .


3. Android 1.0 .. Pre-KitKat.

Trước KitKat, hãy thử sử dụng Doomsknight method 1 hoặc đọc phản hồi này của Gnathonic.

public static HashSet<String> getExternalMounts() {
    final HashSet<String> out = new HashSet<String>();
    String reg = "(?i).*vold.*(vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*";
    String s = "";
    try {
        final Process process = new ProcessBuilder().command("mount")
                .redirectErrorStream(true).start();
        process.waitFor();
        final InputStream is = process.getInputStream();
        final byte[] buffer = new byte[1024];
        while (is.read(buffer) != -1) {
            s = s + new String(buffer);
        }
        is.close();
    } catch (final Exception e) {
        e.printStackTrace();
    }

    // parse output
    final String[] lines = s.split("\n");
    for (String line : lines) {
        if (!line.toLowerCase(Locale.US).contains("asec")) {
            if (line.matches(reg)) {
                String[] parts = line.split(" ");
                for (String part : parts) {
                    if (part.startsWith("/"))
                        if (!part.toLowerCase(Locale.US).contains("vold"))
                            out.add(part);
                }
            }
        }
    }
    return out;
}

Thêm mã tiếp theo vào của bạn AndroidManifest.xmlvà đọc Nhận quyền truy cập vào bộ nhớ ngoài

Quyền truy cập vào bộ nhớ ngoài được bảo vệ bởi các quyền khác nhau của Android.

Bắt đầu từ Android 1.0, quyền ghi được bảo vệ với WRITE_EXTERNAL_STORAGEquyền .

Bắt đầu từ Android 4.1, quyền truy cập đọc được bảo vệ với READ_EXTERNAL_STORAGEquyền.

Để ... ghi tệp trên bộ nhớ ngoài, ứng dụng của bạn phải có ... quyền hệ thống:

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

Nếu bạn cần cả hai ..., bạn chỉ cần yêu cầu sự WRITE_EXTERNAL_STORAGEcho phép.

Đọc lời giải thích Mark Murphy củađề nghị Dianne HackbornDave Smith bài viết

  • Cho đến Android 4.4, không có hỗ trợ chính thức cho phương tiện di động trong Android, Bắt đầu từ KitKat, khái niệm bộ nhớ ngoài "chính" và "phụ" xuất hiện trong API FMW.
  • Các ứng dụng trước đây chỉ dựa vào lập chỉ mục MediaStore, vận chuyển cùng với phần cứng hoặc kiểm tra các điểm gắn kết và áp dụng một số phương pháp phỏng đoán để xác định những gì đại diện cho phương tiện di động.

4. Android 4.4 KitKat giới thiệu Khung truy cập lưu trữ (SAF) .

Bỏ qua ghi chú tiếp theo do lỗi, nhưng hãy thử sử dụng ContextCompat.getExternalFilesDirs():

  • Kể từ Android 4.2, Google đã yêu cầu các nhà sản xuất thiết bị khóa phương tiện di động để bảo mật (hỗ trợ nhiều người dùng) và các thử nghiệm mới đã được thêm vào 4.4.
  • Vì KitKat getExternalFilesDirs()và các phương thức khác đã được thêm vào để trả về một đường dẫn có thể sử dụng trên tất cả các ổ lưu trữ có sẵn (Mục đầu tiên được trả về là ổ chính).
  • Bảng dưới đây cho biết nhà phát triển có thể cố gắng làm gì và KitKat sẽ phản hồi như thế nào: nhập mô tả hình ảnh ở đây

Lưu ý: Bắt đầu với Android 4.4, các quyền này không bắt buộc nếu bạn chỉ đọc hoặc ghi các tệp riêng tư cho ứng dụng của mình. Để biết thêm thông tin ..., hãy xem lưu các tệp ở chế độ riêng tư của ứng dụng .

<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                     android:maxSdkVersion="18" />
</manifest>

Cũng đọc lời giải thích của Paolo Rovelli và thử sử dụng giải pháp của Jeff Sharkey kể từ KitKat:

Trong KitKat hiện có một API công khai để tương tác với các thiết bị lưu trữ dùng chung thứ cấp này.

Phương thức Context.getExternalFilesDirs()và mới Context.getExternalCacheDirs()có thể trả về nhiều đường dẫn, bao gồm cả thiết bị chính và thiết bị phụ.

Sau đó, bạn có thể lặp lại chúng và kiểm tra Environment.getStorageState()File.getFreeSpace()xác định nơi tốt nhất để lưu trữ tệp của mình.

Các phương pháp này cũng có sẵn ContextCompattrong thư viện support-v4.

Bắt đầu từ Android 4.4, chủ sở hữu, nhóm và chế độ của tệp trên thiết bị lưu trữ bên ngoài hiện được tổng hợp dựa trên cấu trúc thư mục. Điều này cho phép các ứng dụng quản lý các thư mục dành riêng cho gói của chúng trên bộ nhớ ngoài mà không yêu cầu chúng có WRITE_EXTERNAL_STORAGEquyền rộng rãi . Ví dụ: ứng dụng có tên gói com.example.foohiện có thể tự do truy cập Android/data/com.example.foo/trên các thiết bị lưu trữ bên ngoài mà không cần quyền. Các quyền tổng hợp này được thực hiện bằng cách gói các thiết bị lưu trữ thô trong một daemon FUSE.

Với KitKat, cơ hội của bạn cho một "giải pháp hoàn chỉnh" mà không cần root là khá nhiều:

Dự án Android chắc chắn đã hoàn thành ở đây. Không ứng dụng nào có quyền truy cập đầy đủ vào thẻ SD bên ngoài:

  • trình quản lý tệp: bạn không thể sử dụng chúng để quản lý thẻ SD bên ngoài của mình. Trong hầu hết các lĩnh vực, họ chỉ có thể đọc chứ không thể viết.
  • ứng dụng phương tiện: bạn không thể gắn thẻ lại / sắp xếp lại bộ sưu tập phương tiện của mình nữa, vì những ứng dụng đó không thể ghi vào đó.
  • ứng dụng văn phòng: khá giống nhau

Nơi duy nhất các ứng dụng của bên thứ 3 được phép ghi trên thẻ bên ngoài của bạn là "thư mục riêng của chúng" (tức là /sdcard/Android/data/<package_name_of_the_app>).

Các cách duy nhất để thực sự khắc phục yêu cầu nhà sản xuất (một số trong số họ đã sửa nó, ví dụ như Huawei với bản cập nhật Kitkat của họ cho P6) - hoặc root ... (Giải thích của Izzy tiếp tục ở đây)


5. Android 5.0 đã giới thiệu các thay đổi và lớp trợ giúp DocumentFile .

getStorageStateĐã thêm vào API 19, không dùng nữa trong API 21, sử dụnggetExternalStorageState(File)

Đây là hướng dẫn tuyệt vời để tương tác với Khung truy cập lưu trữ trong KitKat.

Tương tác với các API mới trong Lollipop rất giống nhau (theo giải thích của Jeff Sharkey) .


6. Android 6.0 Marshmallow giới thiệu một mô hình quyền thời gian chạy mới .

Quyền yêu cầu trong thời gian chạy nếu mức API 23+ và đọc Yêu cầu Quyền tại Run Time

Bắt đầu từ Android 6.0 (API cấp 23), người dùng cấp quyền cho ứng dụng trong khi ứng dụng đang chạy, không phải khi họ cài đặt ứng dụng ... hoặc cập nhật ứng dụng ... người dùng có thể thu hồi quyền.

// Assume thisActivity is the current activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
        Manifest.permission.WRITE_EXTERNAL_STORAGE);

Android 6.0 giới thiệu mô hình quyền thời gian chạy mới trong đó các ứng dụng yêu cầu các khả năng khi cần thiết trong thời gian chạy. Vì mô hình mới bao gồm các READ/WRITE_EXTERNAL_STORAGEquyền nên nền tảng cần tự động cấp quyền truy cập vào bộ nhớ mà không giết hoặc khởi động lại các ứng dụng đã chạy. Nó thực hiện điều này bằng cách duy trì ba chế độ xem riêng biệt của tất cả các thiết bị lưu trữ được gắn kết:

  • / mnt / runtime / default được hiển thị cho các ứng dụng không có quyền lưu trữ đặc biệt ...
  • / mnt / runtime / read được hiển thị cho các ứng dụng có READ_EXTERNAL_STORAGE
  • / mnt / runtime / write được hiển thị cho các ứng dụng có WRITE_EXTERNAL_STORAGE

7. Android 7.0 cung cấp một API đơn giản hóa để truy cập các bộ nhớ ngoài.

Quyền truy cập thư mục theo phạm vi Trong Android 7.0, các ứng dụng có thể sử dụng các API mới để yêu cầu quyền truy cập vào các thư mục bộ nhớ ngoài cụ thể , bao gồm các thư mục trên phương tiện di động như thẻ SD ...

Để biết thêm thông tin, hãy xem đào tạo về Quyền truy cập thư mục theo phạm vi .

Đọc các bài đăng của Mark Murphy: Hãy cẩn thận với quyền truy cập thư mục theo phạm vi . Nó không được dùng nữa trong Android Q :

Lưu ý rằng quyền truy cập thư mục theo phạm vi được thêm vào trong 7.0 không được dùng trong Android Q.

Cụ thể, createAccessIntent()phương thức trên StorageVolume không được dùng nữa.

Họ đã thêm một createOpenDocumentTreeIntent()có thể được sử dụng như một sự thay thế.


8. Android 8.0 Oreo .. Android Q Beta thay đổi.

Bắt đầu từ Android O , Khung truy cập bộ nhớ cho phép các nhà cung cấp tài liệu tùy chỉnh tạo các bộ mô tả tệp có thể tìm kiếm cho các tệp nằm trong nguồn dữ liệu từ xa ...

Quyền , trước Android O , nếu một ứng dụng yêu cầu quyền trong thời gian chạy và quyền đã được cấp, hệ thống cũng cấp sai cho ứng dụng phần còn lại của các quyền thuộc cùng một nhóm quyền và đã được đăng ký trong tệp kê khai.

Đối với các ứng dụng nhắm mục tiêu Android O , hành vi này đã được sửa chữa. Ứng dụng chỉ được cấp các quyền mà ứng dụng đã yêu cầu rõ ràng. Tuy nhiên, khi người dùng cấp quyền cho ứng dụng, tất cả các yêu cầu cấp quyền tiếp theo trong nhóm quyền đó sẽ tự động được cấp.

Ví dụ, READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE...

Cập nhật: Bản phát hành Android Q beta trước đó tạm thời thay thế quyền READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGEbằng các quyền chi tiết hơn, dành riêng cho phương tiện.

Lưu ý: Google đã giới thiệu các vai trò trên Beta 1 và xóa chúng khỏi tài liệu trước Beta 2 ...

Lưu ý: Các điều khoản cụ thể để các bộ sưu tập phương tiện truyền thông đã được giới thiệu trong releases- beta trước đó READ_MEDIA_IMAGES, READ_MEDIA_AUDIOREAD_MEDIA_VIDEO- bây giờ là lỗi thời . Thêm thông tin:

Bài đánh giá Q Beta 4 (API cuối cùng) của Mark Murphy: Cái chết của bộ nhớ ngoài: Sự kết thúc của Saga (?)

"Cái chết phổ biến hơn sự sống. Mọi người đều chết, nhưng không phải ai cũng sống." - Andrew Sachs


9. Câu hỏi liên quan và câu trả lời được đề xuất.

Làm cách nào để nhận đường dẫn thẻ SD bên ngoài cho Android 4.0+?

mkdir () hoạt động khi bên trong bộ nhớ flash nội bộ, nhưng không hoạt động với thẻ SD?

Sự khác biệt giữa getExternalFilesDir và getExternalStorageDirectory ()

Tại sao getExternalFilesDirs () không hoạt động trên một số thiết bị?

Cách sử dụng API truy cập thẻ SD mới được trình bày cho Android 5.0 (Lollipop)

Ghi vào thẻ SD bên ngoài trong Android 5.0 trở lên

Quyền ghi trên thẻ SD của Android sử dụng SAF (Khung truy cập bộ nhớ)

SAFFAQ: Câu hỏi thường gặp về Khung truy cập lưu trữ


10. Các lỗi và vấn đề liên quan.

Lỗi: Trên Android 6, khi sử dụng getExternalFilesDirs, nó sẽ không cho phép bạn tạo tệp mới trong kết quả của nó

Ghi vào thư mục được trả về bởi getExternalCacheDir () trên Lollipop không thành công mà không có quyền ghi


2
Một phân tích rất sâu sắc. +1. Mặc dù tôi phải lưu ý, tôi đã không viết phương thức đó. Nó là từ một nguồn khác một vài năm trước đây :) Hy vọng nó sẽ giúp anh ta.
IAmGroot

Cảm ơn bạn đã trả lời đầy đủ. Có vẻ như vấn đề ở đây là tôi nên sử dụng SAF để viết trong thư mục gốc của thẻ SD thay vì sử dụng @Doomsknight phương pháp 1, vì nó sẽ không hoạt động trên các cấp API cao hơn, trong khi dưới KitKat tôi có thể sử dụng nó.
Vektor88

Np, nếu bạn cần viết bên ngoài dir ứng dụng của mình, có vẻ là cách chính xác để sử dụng API chính thức . Có vẻ như ở đây đã giải quyết được vấn đề tương tự, tôi sẽ kiểm tra vào ngày khác.
albodelu

Trình khám phá tệp ES trong điện thoại 4.4.2 tạo Thư mục như thế nào? @albodelu Bạn có thể vui lòng giải thích không
karanatwal.github.io

Ở điểm 3, có một liên kết giải thích cách thực hiện đối với các thiết bị đã root. Ứng dụng này có một tùy chọn để vô hiệu hóa các hạn chế bổ sung trong KitKat: Công cụ> Root Explorer> Mount R / W
albodelu

10

Tôi tin rằng có hai phương pháp để đạt được điều này:

PHƯƠNG PHÁP 1: ( KHÔNG hoạt động trên 6.0 trở lên, do thay đổi quyền)

Tôi đã sử dụng phương pháp này trong nhiều năm trên nhiều phiên bản thiết bị mà không có vấn đề gì. Tín dụng là do nguồn gốc, vì không phải tôi là người viết nó.

Nó sẽ trả về tất cả các phương tiện được gắn kết (bao gồm cả Thẻ SD thực) trong danh sách các vị trí thư mục chuỗi. Với danh sách, bạn có thể hỏi người dùng nơi lưu, v.v.

Bạn có thể gọi nó như sau:

 HashSet<String> extDirs = getStorageDirectories();

Phương pháp:

/**
 * Returns all the possible SDCard directories
 */
public static HashSet<String> getStorageDirectories() {
    final HashSet<String> out = new HashSet<String>();
    String reg = "(?i).*vold.*(vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*";
    String s = "";
    try {
        final Process process = new ProcessBuilder().command("mount")
                .redirectErrorStream(true).start();
        process.waitFor();
        final InputStream is = process.getInputStream();
        final byte[] buffer = new byte[1024];
        while (is.read(buffer) != -1) {
            s = s + new String(buffer);
        }
        is.close();
    } catch (final Exception e) {
        e.printStackTrace();
    }

    // parse output
    final String[] lines = s.split("\n");
    for (String line : lines) {
        if (!line.toLowerCase().contains("asec")) {
            if (line.matches(reg)) {
                String[] parts = line.split(" ");
                for (String part : parts) {
                    if (part.startsWith("/"))
                        if (!part.toLowerCase().contains("vold"))
                            out.add(part);
                }
            }
        }
    }
    return out;
}

PHƯƠNG PHÁP 2:

Sử dụng thư viện hỗ trợ v4

import android.support.v4.content.ContextCompat;

Chỉ cần gọi sau đây để nhận danh sách các Filevị trí lưu trữ.

 File[] list = ContextCompat.getExternalFilesDirs(myContext, null);

Tuy nhiên, các địa điểm khác nhau về cách sử dụng.

Trả về các đường dẫn tuyệt đối đến các thư mục dành riêng cho ứng dụng trên tất cả các thiết bị lưu trữ bên ngoài nơi ứng dụng có thể đặt các tệp liên tục mà ứng dụng sở hữu. Các tệp này nằm trong ứng dụng và thường không hiển thị với người dùng dưới dạng phương tiện.

Các thiết bị lưu trữ bên ngoài được trả lại ở đây được coi là một phần vĩnh viễn của thiết bị, bao gồm cả bộ nhớ ngoài giả lập và các khe cắm phương tiện vật lý, chẳng hạn như thẻ SD trong ngăn chứa pin. Các đường dẫn trả về không bao gồm các thiết bị tạm thời, chẳng hạn như ổ đĩa flash USB.

Một ứng dụng có thể lưu trữ dữ liệu trên bất kỳ hoặc tất cả các thiết bị được trả lại. Ví dụ: một ứng dụng có thể chọn lưu trữ các tệp lớn trên thiết bị với dung lượng trống nhiều nhất

Thông tin thêm về ContextCompat

Chúng giống như các tệp dành riêng cho ứng dụng. Ẩn khỏi các ứng dụng khác.


1
Vui lòng chia sẻ liên kết mà bạn nhận được mã ở phương pháp 1. Ghi có khi đến hạn. Cảm ơn.
Eugen Pechanec

2
@EugenPechanec Tôi hoàn toàn đồng ý. tuy nhiên cách đây nhiều năm tôi lấy mã nguồn này từ một nguồn tuyệt vời. Tôi sẽ xem xét nó
IAmGroot

1
Tôi không rõ tôi nên làm gì với những đường dẫn này: Nếu thiết bị chưa được root, tôi sẽ không có quyền ghi vào sdcard bằng cách chỉ định đường dẫn tuyệt đối của nó. Liệu tôi có sai? Có lẽ tôi có thể lưu trữ một số dữ liệu trong thư mục ứng dụng dành riêng trong /external_sd/data/data/com.myapp ..., nhưng tôi sẽ không thể để viết thư cho / external_sd / MyApp / hình ảnh
Vektor88

1
@ vektor88 Bạn có thể truy cập tốt mà không cần root. Tôi và khách hàng của tôi chưa từng sử dụng thiết bị đã root. lỗi gì bạn nhận được? Bạn đã thêm quyền. Và như đã nêu bên dưới là bạn đang nhắm mục tiêu mới nhất và yêu cầu quyền tại thời điểm chạy. Thay đổi mục tiêu thành 20 và xem nó có hoạt động hay không, trước khi giải quyết yêu cầu cấp phép thời gian chạy. Xin vui lòng cung cấp lỗi khác.
IAmGroot

1
@ Vektor88 Khái niệm đó hoàn toàn là một thử nghiệm, để xem liệu bạn có đang gặp vấn đề về quyền hay không. Nếu bạn đang nhắm mục tiêu api 23, thì nó đặt giá thầu câu hỏi, bạn đã yêu cầu quyền trong thời gian chạy. Mặc dù đây là một vấn đề riêng biệt với mong muốn có được vị trí thẻ SDCard để lưu (như câu hỏi ban đầu). developer.android.com/training/permissions/requesting.html
IAmGroot

3

Chỉ là một câu trả lời khác. Câu trả lời này chỉ hiển thị 5.0+ vì tôi tin rằng câu trả lời của Doomknight được đăng ở đây là cách tốt nhất để thực hiện cho Android 4.4 trở xuống.

Điều này ban đầu được đăng ở đây ( Có cách nào để lấy kích thước Thẻ SD trong Android không? ) Bởi tôi để lấy kích thước Thẻ SD bên ngoài trên Android 5.0+

Để lấy thẻ SD bên ngoài làm File:

public File getExternalSdCard() {
    File externalStorage = null;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        File storage = new File("/storage");

        if(storage.exists()) {
            File[] files = storage.listFiles();

            for (File file : files) {
                if (file.exists()) {
                    try {
                        if (Environment.isExternalStorageRemovable(file)) {
                            externalStorage = file;
                            break;
                        }
                    } catch (Exception e) {
                        Log.e("TAG", e.toString());
                    }
                }
            }
        }
    } else {
        // do one of many old methods
        // I believe Doomsknight's method is the best option here
    }

    return externalStorage;
}

Lưu ý: Tôi chỉ nhận được thẻ sd bên ngoài "đầu tiên" tuy nhiên bạn có thể sửa đổi nó và quay lại ArrayList<File>thay vì Filevà để vòng lặp tiếp tục thay vì gọi breaksau khi tìm thấy thẻ đầu tiên.


Cảm ơn @ th3pat3l isExternalStorageRemovabletham khảo developer.android.com/reference/android/os/...
albodelu

1
Điều này chỉ hoạt động để truy cập vào thư mục con thẻ SD bên ngoài dựa trên ứng dụng của bạn: ví dụ: trên Điện thoại LG của tôi, nó cung cấp cho tôi: /storage/0403-0201/Android/data/com.your.application/files - nếu bạn muốn thêm một tệp vào thư mục DCIM - ngay cả khi bạn có quyền trong tệp kê khai của mình và bạn yêu cầu chúng trong thời gian chạy và bạn truy cập vào ứng dụng của bạn-> quyền đang được thiết lập và bật Lưu trữ BẠN VẪN CÓ ĐƯỢC (Không có tệp hoặc thư mục nào như vậy ) lỗi nếu bạn cố gắng tạo và mở tệp ở đó. Ít nhất thì đó là những gì đang xảy ra trên LG Aristo SDK 23. Điều duy nhất tôi thấy đó là SAF.
MarkJoel60

isExternalStorageRemovablechỉ dành cho> = 21, liệu có thể xác định xem bộ nhớ có thể tháo rời (thẻ microsd) trên <= 19 thiết bị API hay không, bằng cách nào đó thực hiện phương pháp tương tự?
user924

3

Ngoài tất cả các câu trả lời hay khác, tôi có thể thêm một chút nữa vào câu hỏi này để nó có thể đưa ra phạm vi rộng hơn cho người đọc. Trong câu trả lời của tôi ở đây, tôi sẽ sử dụng 2 tài nguyên có thể đếm được để trình bày Bộ nhớ ngoài.

Tài nguyên đầu tiên là từ Lập trình Android, The Big Nerd Ranch Guide phiên bản thứ 2 , chương 16, trang 294.

Cuốn sách mô tả các phương pháp tệp và thư mục cơ bản và bên ngoài. Tôi sẽ cố gắng tạo một bản lý lịch về những gì có thể liên quan đến câu hỏi của bạn.

Phần sau của cuốn sách:

Lưu trữ ngoài

Ảnh của bạn cần nhiều hơn một vị trí trên màn hình. Hình ảnh kích thước đầy đủ quá lớn để có thể nằm trong cơ sở dữ liệu SQLite, ít hơn nhiều Intent. Họ sẽ cần một nơi để sống trên hệ thống tệp của thiết bị của bạn. Thông thường, bạn sẽ đặt chúng vào bộ nhớ riêng của mình. Nhớ lại rằng bạn đã sử dụng bộ nhớ riêng để lưu cơ sở dữ liệu SQLite của mình. Với các phương pháp như Context.getFileStreamPath(String)Context.getFilesDir() , bạn cũng có thể làm điều tương tự với các tệp thông thường (tệp này sẽ nằm trong thư mục con liền kề với thư mục con của cơ sở dữ liệu mà cơ sở dữ liệu SQLite của bạn nằm trong đó)

Các phương thức tệp và thư mục cơ bản trong Ngữ cảnh

| Method                                                                                |
|---------------------------------------------------------------------------------------|
|File getFilesDir()                                                                      |
| - Returns a handle to the directory for private application files.                    |
|                                                                                       |
|FileInputStream openFileInput(String name)                                             |
| - Opens an existing file for input (relative to the files directory).                 |
|                                                                                       |
|FileOutputStream openFileOutput(String name, int mode)                                 |
| - Opens a file for output, possibly creating it (relative to the files directory).    |
|                                                                                       |
|File getDir(String name, int mode)                                                     |
| - Gets (and possibly creates) a subdirectory within the files directory.              |
|                                                                                       |
|String[] fileList()                                                                    |
| - Gets a list of file names in the main files directory, such as for use with         |
|   openFileInput(String).                                                              |
|                                                                                       |
|File getCacheDir()                                                                     |
| - Returns a handle to a directory you can use specifically for storing cache files.   |
|   You should take care to keep this directory tidy and use as little space as possible|

Nếu bạn đang lưu trữ các tệp mà chỉ ứng dụng hiện tại của bạn mới cần sử dụng, thì các phương pháp này chính là thứ bạn cần.

Mặt khác, nếu bạn cần một ứng dụng khác để ghi vào các tệp đó, bạn sẽ không gặp may: trong khi có một Context.MODE_WORLD_READABLElá cờ, bạn có thể chuyển vàoopenFileOutput(String, int) , nhưng nó không được dùng nữa và không hoàn toàn đáng tin cậy về tác dụng của nó trên các thiết bị mới hơn. Nếu bạn đang lưu trữ tệp để chia sẻ với các ứng dụng khác hoặc nhận tệp từ các ứng dụng khác (tệp như ảnh được lưu trữ), bạn cần lưu trữ chúng trên bộ nhớ ngoài.

Có hai loại bộ nhớ ngoài: chính và mọi thứ khác. Tất cả các thiết bị Android đều có ít nhất một vị trí cho bộ nhớ ngoài: vị trí chính, nằm trong thư mục được trả về Environment.getExternalStorageDirectory(). Đây có thể là thẻ SD, nhưng ngày nay nó thường được tích hợp vào chính thiết bị hơn. Một số thiết bị có thể có thêm bộ nhớ ngoài. Điều đó sẽ nằm trong “mọi thứ khác”.

Context cũng cung cấp khá nhiều phương pháp để sử dụng bộ nhớ ngoài. Các phương pháp này cung cấp các cách dễ dàng để truy cập bộ nhớ ngoài chính của bạn và các cách dễ dàng để có được mọi thứ khác. Tất cả các phương pháp này cũng lưu trữ tệp ở những nơi có sẵn công khai, vì vậy hãy cẩn thận với chúng.

Các phương thức tệp và thư mục bên ngoài trong Ngữ cảnh

| Method                                                                                |
| --------------------------------------------------------------------------------------|
|File getExternalCacheDir()                                                             |
| - Returns a handle to a cache folder in primary external storage. Treat it like you do|
|   getCacheDir(), except a little more carefully. Android is even less likely to clean |
|   up this folder than the private storage one.                                        |
|                                                                                       |
|File[] getExternalCacheDirs()                                                          |
| - Returns cache folders for multiple external storage locations.                      |
|                                                                                       |
|File getExternalFilesDir(String)                                                       |
| - Returns a handle to a folder on primary external storage in which to store regular  |
|   files. If you pass in a type String, you can access a specific subfolder dedicated  |
|   to a particular type of content. Type constants are defined in Environment, where   |
|   they are prefixed with DIRECTORY_.                                                  |
|   For example, pictures go in Environment.DIRECTORY_PICTURES.                         |
|                                                                                       |
|File[] getExternalFilesDirs(String)                                                    |
| - Same as getExternalFilesDir(String), but returns all possible file folders for the  |
|   given type.                                                                         |
|                                                                                       |
|File[] getExternalMediaDirs()                                                          |
| - Returns handles to all the external folders Android makes available for storing     |
|   media – pictures, movies, and music. What makes this different from calling         |
|   getExternalFilesDir(Environment.DIRECTORY_PICTURES) is that the media scanner       |
|   automatically scans this folder. The media scanner makes files available to         |
|   applications that play music, or browse movies and photos, so anything that you     |
|   put in a folder returned by getExternalMediaDirs() will automatically appear in     |
|   those apps.                                                                         |

Về mặt kỹ thuật, các thư mục bên ngoài được cung cấp ở trên có thể không khả dụng vì một số thiết bị sử dụng thẻ SD có thể tháo rời để lưu trữ bên ngoài. Trong thực tế, điều này hiếm khi là một vấn đề, vì hầu hết tất cả các thiết bị hiện đại đều có bộ nhớ trong không thể di chuyển cho bộ nhớ “bên ngoài” của chúng. Vì vậy, nó không đáng để đi đến độ dài cực đoan để giải thích nó. Nhưng chúng tôi khuyên bạn nên bao gồm mã đơn giản để đề phòng khả năng xảy ra, điều mà bạn sẽ thực hiện trong giây lát.

Quyền bộ nhớ ngoài

Nói chung, bạn cần có quyền ghi hoặc đọc từ bộ nhớ ngoài. Quyền là các giá trị chuỗi nổi tiếng mà bạn đặt trong tệp kê khai của mình bằng <uses-permission>thẻ. Họ nói với Android rằng bạn muốn làm điều gì đó mà Android muốn bạn xin phép.

Ở đây, Android mong bạn yêu cầu quyền vì nó muốn thực thi một số trách nhiệm. Bạn nói với Android rằng bạn cần truy cập bộ nhớ ngoài và sau đó Android sẽ nói với người dùng rằng đây là một trong những điều ứng dụng của bạn thực hiện khi họ cố gắng cài đặt nó. Bằng cách đó, không ai ngạc nhiên khi bạn bắt đầu lưu mọi thứ vào thẻ SD của họ.

Trong Android 4.4, KitKat, họ đã nới lỏng hạn chế này. TừContext.getExternalFilesDir(String) trả về một thư mục dành riêng cho ứng dụng của bạn, nên điều hợp lý là bạn muốn có thể đọc và ghi các tệp nằm ở đó. Vì vậy, trên Android 4.4 (API 19) trở lên, bạn không cần quyền này cho thư mục này. (Nhưng bạn vẫn cần nó cho các loại bộ nhớ ngoài khác.)

Thêm một dòng vào tệp kê khai của bạn yêu cầu quyền đọc bộ nhớ ngoài, nhưng chỉ tối đa API Liệt kê 16.5 Yêu cầu quyền bộ nhớ ngoài ( AndroidManifest.xml)

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.bignerdranch.android.criminalintent" >
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
         android:maxSdkVersion="18" />

Thuộc tính maxSdkVersion làm cho ứng dụng của bạn chỉ yêu cầu quyền này trên các phiên bản Android cũ hơn API 19, Android KitKat. Lưu ý rằng bạn chỉ yêu cầu đọc bộ nhớ ngoài. Cũng có một sự WRITE_EXTERNAL_STORAGEcho phép, nhưng bạn không cần nó. Bạn sẽ không ghi bất cứ thứ gì vào bộ nhớ ngoài: Ứng dụng máy ảnh sẽ làm điều đó cho bạn

Tài nguyên thứ hai là liên kết này đọc tất cả, nhưng bạn cũng có thể chuyển đến phần Sử dụng bộ nhớ ngoài .

Tài liệu tham khảo:

Nội dung đọc thêm:

Tuyên bố từ chối trách nhiệm: Thông tin này được lấy từ Lập trình Android: Hướng dẫn Nông trại Big Nerd với sự cho phép của các tác giả. Để biết thêm thông tin về cuốn sách này hoặc để mua một bản sao, vui lòng truy cập bignerdranch.com.


0

Chủ đề này hơi cũ nhưng tôi đang tìm giải pháp và sau một số nghiên cứu, tôi đã sử dụng đoạn mã dưới đây để lấy danh sách các điểm gắn kết "bên ngoài" có sẵn mà theo hiểu biết của tôi, hoạt động trên nhiều thiết bị khác nhau.

Về cơ bản, nó đọc các điểm gắn kết có sẵn, lọc ra những điểm không hợp lệ, kiểm tra phần còn lại nếu chúng có thể truy cập được và thêm chúng nếu tất cả các điều kiện được thỏa mãn.

Tất nhiên, các quyền bắt buộc phải được cấp trước khi mã được gọi.

// Notice: FileSystemDevice is just my own wrapper class. Feel free to replace it with your own. 

private List<FileSystemDevice> getDevices() {

    List<FileSystemDevice> devices = new ArrayList<>();

    // Add default external storage if available.
    File sdCardFromSystem = null;
    switch(Environment.getExternalStorageState()) {
        case Environment.MEDIA_MOUNTED:
        case Environment.MEDIA_MOUNTED_READ_ONLY:
        case Environment.MEDIA_SHARED:
            sdCardFromSystem = Environment.getExternalStorageDirectory();
            break;
    }

    if (sdCardFromSystem != null) {
        devices.add(new FileSystemDevice(sdCardFromSystem));
    }

    // Read /proc/mounts and add all mount points that are available
    // and are not "special". Also, check if the default external storage
    // is not contained inside the mount point. 
    try {
        FileInputStream fs = new FileInputStream("/proc/mounts");
        String mounts = IOUtils.toString(fs, "UTF-8");
        for(String line : mounts.split("\n")) {
            String[] parts = line.split(" ");

            // parts[0] - mount type
            // parts[1] - mount point
            if (parts.length > 1) {
                try {

                    // Skip "special" mount points and mount points that can be accessed
                    // directly by Android's functions. 
                    if (parts[0].equals("proc")) { continue; }
                    if (parts[0].equals("rootfs")) { continue; }
                    if (parts[0].equals("devpts")) { continue; }
                    if (parts[0].equals("none")) { continue; }
                    if (parts[0].equals("sysfs")) { continue; }
                    if (parts[0].equals("selinuxfs")) { continue; }
                    if (parts[0].equals("debugfs")) { continue; }
                    if (parts[0].equals("tmpfs")) { continue; }
                    if (parts[1].equals(Environment.getRootDirectory().getAbsolutePath())) { continue; }
                    if (parts[1].equals(Environment.getDataDirectory().getAbsolutePath())) { continue; }
                    if (parts[1].equals(Environment.getExternalStorageDirectory().getAbsolutePath())) { continue; }

                    // Verify that the mount point is accessible by listing its content. 
                    File file = new File(parts[1]);
                    if (file.listFiles() != null) {
                        try {

                            // Get canonical path for case it's just symlink to another mount point.
                            String devPath = file.getCanonicalPath();

                            for(FileSystemDevice device : devices) {

                                if (!devices.contains(devPath)) {                        
                                    devices.add(new FileSystemDevice(new File(devPath)));
                                }

                            }
                        } catch (Exception e) {
                            // Silently skip the exception as it can only occur if the mount point is not valid. 
                            e.printStackTrace();
                        }
                    }
                } catch (Exception e) {
                    // Silently skip the exception as it can only occur if the mount point is not valid. 
                    e.printStackTrace();
                }
            }
        }

        fs.close();
    } catch (FileNotFoundException e) {
        // Silently skip the exception as it can only occur if the /proc/mounts file is unavailable. 
        // Possibly, another detection method can be called here.
        e.printStackTrace();
    } catch (IOException e) {
        // Silently skip the exception as it can only occur if the /proc/mounts file is unavailable.
        // Possibly, another detection method can be called here.
        e.printStackTrace();            
    }

    return devices;
}

-1

Đây là cách tạo tệp mới trong Bộ nhớ ngoài (SDCard nếu có trong thiết bị hoặc Bộ nhớ ngoài của thiết bị nếu không có). Chỉ cần thay thế "tên thư mục" bằng tên của thư mục đích mong muốn của bạn và "tên tệp" bằng tên tệp bạn đang lưu. Tất nhiên ở đây bạn có thể xem cách lưu một tệp chung, bây giờ bạn có thể tìm kiếm cách lưu hình ảnh có thể ở đây hoặc bất cứ thứ gì trong tệp.

try {
            File dir =  new File(Environment.getExternalStorageDirectory() + "/foldername/");
            if (!dir.exists()){
                dir.mkdirs();
            }
            File sdCardFile = new File(Environment.getExternalStorageDirectory() + "/foldername/" + fileName );
            int num = 1;
            String fileNameAux = fileName;
            while (sdCardFile.exists()){
                fileNameAux = fileName+"_"+num;
                sdCardFile = new File(Environment.getExternalStorageDirectory() + "/foldername/" + fileNameAux);
                num++;
            }

Điều này cũng kiểm soát sự tồn tại của tệp đó và thêm một số vào cuối tên của tệp mới để lưu tệp đó.

Hy vọng nó giúp!

CHỈNH SỬA: Xin lỗi, tôi quên rằng bạn phải yêu cầu <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />trong tệp kê khai của mình (hoặc theo chương trình nếu bạn thích từ Marshmallow)


-3

Đối với các phiên bản bên dưới Marshmallow, bạn có thể trực tiếp cấp quyền trong tệp kê khai.

Nhưng đối với các thiết bị có Marshmallow trở lên, bạn cần cấp quyền trong thời gian chạy.

Bằng cách sử dụng

Environment.getExternalStorageDirectory();

bạn có thể truy cập trực tiếp vào thẻ SD bên ngoài (Đã gắn thẻ) Hy vọng điều này sẽ hữu ích.


Environment.getExternalStorageDirectory();sẽ cung cấp cho bạn đường dẫn đến thẻ SD bên trong thiết bị.
Vektor88,

Bây giờ từ Android Lollipop, bạn có hai tùy chọn, sử dụng Thẻ SD bên ngoài làm Bộ nhớ trong hoặc giống như thẻ sd thông thường, nó cũng phụ thuộc vào lựa chọn đó.
Geet Choubey 16/10/16


2
Đoạn mã đó cung cấp cho tôi đường dẫn nơi gắn thẻ SD bên ngoài. Sau đó là gì? Tôi vẫn không thể viết trên đó.
Vektor88

Bạn gặp phải lỗi gì khi cố gắng ghi vào thẻ sd bên ngoài?
Geet Choubey
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.