ฉันจะรับเส้นทางการ์ด SD ภายนอกสำหรับ Android 4.0+ ได้อย่างไร


94

Samsung Galaxy S3มีช่องเสียบการ์ด SD ภายนอกซึ่งติดตั้งกับ/mnt/extSdCard.

ฉันจะไปเส้นทางนี้ได้Environment.getExternalStorageDirectory()อย่างไร?

สิ่งนี้จะกลับมาmnt/sdcardและฉันไม่พบ API สำหรับการ์ด SD ภายนอก (หรือที่เก็บข้อมูล USB แบบถอดได้ในแท็บเล็ตบางรุ่น)


ทำไมคุณถึงต้องการสิ่งนี้? เป็นเรื่องที่หลีกเลี่ยงไม่ได้บน Android ที่จะทำสิ่งนี้ บางทีถ้าคุณแบ่งปันแรงจูงใจของคุณเราสามารถชี้ให้คุณเห็นทิศทางของแนวปฏิบัติที่ดีที่สุดสำหรับสิ่งประเภทนี้
rharter

2
เมื่อผู้ใช้ของคุณเปลี่ยนโทรศัพท์ให้เสียบการ์ด SD กับโทรศัพท์เครื่องใหม่และในโทรศัพท์เครื่องแรกคือ / sdcard บนโทรศัพท์เครื่องที่สองคือ / mnt / extSdCard สิ่งใดก็ตามที่ใช้เส้นทางไฟล์จะขัดข้อง ฉันต้องการสร้างเส้นทางจริงจากเส้นทางสัมพัทธ์
Romulus Urakagi Ts'ai

ตอนนี้หัวข้อนี้เก่าแล้ว แต่อาจช่วยได้ คุณควรใช้วิธีนี้ System.getenv (); ดูโครงการ Environment3 เพื่อเข้าถึงที่เก็บข้อมูลทั้งหมดที่เชื่อมต่อกับอุปกรณ์ของคุณ github.com/omidfaraji/Environment3
Omid Faraji


นี่คือวิธีแก้ปัญหาของฉันที่ใช้ได้จนถึง Nougat: stackoverflow.com/a/40205116/5002496
Gokul NC

คำตอบ:


58

ฉันมีรูปแบบของโซลูชันที่พบที่นี่

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

วิธีการเดิมได้รับการทดสอบและใช้ได้ผล

  • Huawei X3 (สต็อก)
  • Galaxy S2 (สต็อก)
  • Galaxy S3 (สต็อก)

ฉันไม่แน่ใจว่าแอนดรอยด์รุ่นใดเปิดอยู่เมื่อทดสอบ

ฉันได้ทดสอบเวอร์ชันที่แก้ไขด้วยไฟล์

  • Moto Xoom 4.1.2 (หุ้น)
  • Galaxy Nexus (cyanogenmod 10) โดยใช้สาย otg
  • HTC Incredible (cyanogenmod 7.2) สิ่งนี้ส่งคืนทั้งภายในและภายนอก อุปกรณ์นี้ค่อนข้างเป็น oddball เนื่องจากภายในส่วนใหญ่ไม่ได้ใช้งานเนื่องจาก getExternalStorage () ส่งคืนเส้นทางไปยัง sdcard แทน

และอุปกรณ์จัดเก็บข้อมูลเดียวที่ใช้ sdcard เป็นที่เก็บข้อมูลหลัก

  • HTC G1 (cyanogenmod 6.1)
  • HTC G1 (หุ้น)
  • HTC Vision / G2 (สต็อก)

ยกเว้นอุปกรณ์ Incredible ทั้งหมดเหล่านี้เท่านั้นที่ส่งคืนพื้นที่เก็บข้อมูลแบบถอดได้ อาจมีการตรวจสอบเพิ่มเติมบางอย่างที่ฉันควรทำ แต่อย่างน้อยก็ดีกว่าวิธีแก้ปัญหาใด ๆ ที่ฉันพบ


3
ฉันคิดว่าโซลูชันของคุณจะให้ชื่อโฟลเดอร์ที่ไม่ถูกต้องหากต้นฉบับมีตัวพิมพ์ใหญ่เช่น / mnt / SdCard
Eugene Popovich

1
ฉันได้ทดสอบกับอุปกรณ์ Motorola, Samsung และ LG บางรุ่นแล้วและทำงานได้อย่างสมบูรณ์แบบ
pepedeutico

2
@KhawarRaza วิธีนี้หยุดทำงานเมื่อ kitkat ใช้วิธีของ Dmitriy Lozenko โดยใช้วิธีนี้เป็นทางเลือกหากคุณรองรับอุปกรณ์ pre-ics
Gnathonic

1
ทำงานบน HUAWEI Honor 3c ขอบคุณ.
li2

2
สิ่งนี้กำลังกลับมา / mnt แทนที่จะเป็น / storage บน 4.0+ สำหรับฉันใน Galaxy Note 3
ono

54

ฉันพบวิธีที่เชื่อถือได้มากขึ้นในการรับเส้นทางไปยัง SD-CARD ทั้งหมดในระบบ สิ่งนี้ใช้ได้กับ Android ทุกเวอร์ชันและส่งคืนเส้นทางไปยังพื้นที่เก็บข้อมูลทั้งหมด (รวมถึงการจำลอง)

ทำงานได้อย่างถูกต้องบนอุปกรณ์ทั้งหมดของฉัน

PS: อ้างอิงจากซอร์สโค้ดของคลาส Environment

private static final Pattern DIR_SEPORATOR = Pattern.compile("/");

/**
 * Raturns all available SD-Cards in the system (include emulated)
 *
 * Warning: Hack! Based on Android source code of version 4.3 (API 18)
 * Because there is no standart way to get it.
 * TODO: Test on future Android versions 4.4+
 *
 * @return paths to all available SD-Cards in the system (include emulated)
 */
public static String[] getStorageDirectories()
{
    // Final set of paths
    final Set<String> rv = new HashSet<String>();
    // Primary physical SD-CARD (not emulated)
    final String rawExternalStorage = System.getenv("EXTERNAL_STORAGE");
    // All Secondary SD-CARDs (all exclude primary) separated by ":"
    final String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE");
    // Primary emulated SD-CARD
    final String rawEmulatedStorageTarget = System.getenv("EMULATED_STORAGE_TARGET");
    if(TextUtils.isEmpty(rawEmulatedStorageTarget))
    {
        // Device has physical external storage; use plain paths.
        if(TextUtils.isEmpty(rawExternalStorage))
        {
            // EXTERNAL_STORAGE undefined; falling back to default.
            rv.add("/storage/sdcard0");
        }
        else
        {
            rv.add(rawExternalStorage);
        }
    }
    else
    {
        // Device has emulated storage; external storage paths should have
        // userId burned into them.
        final String rawUserId;
        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
        {
            rawUserId = "";
        }
        else
        {
            final String path = Environment.getExternalStorageDirectory().getAbsolutePath();
            final String[] folders = DIR_SEPORATOR.split(path);
            final String lastFolder = folders[folders.length - 1];
            boolean isDigit = false;
            try
            {
                Integer.valueOf(lastFolder);
                isDigit = true;
            }
            catch(NumberFormatException ignored)
            {
            }
            rawUserId = isDigit ? lastFolder : "";
        }
        // /storage/emulated/0[1,2,...]
        if(TextUtils.isEmpty(rawUserId))
        {
            rv.add(rawEmulatedStorageTarget);
        }
        else
        {
            rv.add(rawEmulatedStorageTarget + File.separator + rawUserId);
        }
    }
    // Add all secondary storages
    if(!TextUtils.isEmpty(rawSecondaryStoragesStr))
    {
        // All Secondary SD-CARDs splited into array
        final String[] rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator);
        Collections.addAll(rv, rawSecondaryStorages);
    }
    return rv.toArray(new String[rv.size()]);
}

5
DIR_SEPORATOR สมมติว่าเป็นอะไร
cYrixmorten

1
@cYrixmorten ที่ระบุไว้ด้านบนของรหัส .. Patternสำหรับ/
Ankit Bansal

ใครทดสอบวิธีนี้กับ Lolipop บ้าง?
lenrok258

ฉันลองใช้วิธีนี้กับ lenovo P780 แล้วมันใช้ไม่ได้
Satheesh Kumar

2
ทำงานได้ถึง Lollipop; ไม่ได้ทำงานกับขนมหวาน (กาแล็กซี่แท็บ w / 6.0.1) ซึ่งผลตอบแทนSystem.getenv("SECONDARY_STORAGE") /storage/sdcard1ตำแหน่งนั้นไม่มีอยู่จริง แต่ตำแหน่งจริงคือ/storage/42CE-FD0Aโดยที่ 42CE-FD0A คือหมายเลขซีเรียลโวลุ่มของพาร์ติชันที่จัดรูปแบบ
DaveAlden

32

ฉันเดาว่าจะใช้ sdcard ภายนอกคุณต้องใช้สิ่งนี้:

new File("/mnt/external_sd/")

หรือ

new File("/mnt/extSdCard/")

ในกรณีของคุณ ...

แทนที่ Environment.getExternalStorageDirectory()

ใช้ได้ผลสำหรับฉัน คุณควรตรวจสอบสิ่งที่อยู่ในไดเรกทอรี mnt ก่อนและทำงานจากที่นั่น ..


คุณควรใช้วิธีการเลือกบางประเภทเพื่อเลือก sdcard ที่จะใช้:

File storageDir = new File("/mnt/");
if(storageDir.isDirectory()){
    String[] dirList = storageDir.list();
    //TODO some type of selecton method?
}

1
อันที่จริงฉันต้องการวิธีการมากกว่าพา ธ ฮาร์ดโค้ด แต่ / mnt / list ควรจะใช้ได้ ขอขอบคุณ.
Romulus Urakagi Ts'ai

ไม่มีปัญหา. มีตัวเลือกในการตั้งค่าที่คุณเลือกเส้นทางจากนั้นใช้วิธีการดึงข้อมูลนี้
FabianCook

10
ขออภัยการ์ด SD ภายนอกอาจอยู่ในโฟลเดอร์อื่นที่ไม่ใช่ / mnt เช่นบน Samsung GT-I9305 เป็น / storage / extSdCard ในขณะที่การ์ด SD ในตัวมี path / storage / sdcard0
iseeall

2
ถ้าอย่างนั้นคุณจะได้รับตำแหน่ง sdcard จากนั้นรับไดเร็กทอรีหลัก
FabianCook

เส้นทางของการ์ด SD ภายนอกจะเป็นอย่างไร (สำหรับเพนไดรฟ์ที่เชื่อมต่อกับแท็บเล็ตผ่านสาย OTG) สำหรับ Android 4.0+
osimer pothe

15

ในการดึงข้อมูลที่เก็บภายนอกทั้งหมด(ไม่ว่าจะเป็นการ์ด SDหรือที่เก็บข้อมูลภายในแบบถอดไม่ได้ ) คุณสามารถใช้รหัสต่อไปนี้:

final String state = Environment.getExternalStorageState();

if ( Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state) ) {  // we can read the External Storage...           
    //Retrieve the primary External Storage:
    final File primaryExternalStorage = Environment.getExternalStorageDirectory();

    //Retrieve the External Storages root directory:
    final String externalStorageRootDir;
    if ( (externalStorageRootDir = primaryExternalStorage.getParent()) == null ) {  // no parent...
        Log.d(TAG, "External Storage: " + primaryExternalStorage + "\n");
    }
    else {
        final File externalStorageRoot = new File( externalStorageRootDir );
        final File[] files = externalStorageRoot.listFiles();

        for ( final File file : files ) {
            if ( file.isDirectory() && file.canRead() && (file.listFiles().length > 0) ) {  // it is a real directory (not a USB drive)...
                Log.d(TAG, "External Storage: " + file.getAbsolutePath() + "\n");
            }
        }
    }
}

หรือคุณอาจใช้System.getenv ("EXTERNAL_STORAGE")เพื่อดึงข้อมูลไดเร็กทอรี External Storage หลัก (เช่น"/ storage / sdcard0" ) และSystem.getenv ("SECONDARY_STORAGE")เพื่อดึงรายการไดเร็กทอรีรองทั้งหมด (เช่น" / storage / extSdCard: / storage / UsbDriveA: / storage / UsbDriveB " ) โปรดจำไว้ว่าในกรณีนี้คุณอาจต้องการกรองรายการไดเร็กทอรีรองเพื่อแยกไดรฟ์ USB ออก

ไม่ว่าในกรณีใดโปรดทราบว่าการใช้เส้นทางแบบฮาร์ดโค้ดมักเป็นแนวทางที่ไม่ดี (โดยเฉพาะอย่างยิ่งเมื่อผู้ผลิตทุกรายอาจเปลี่ยนแปลงตามความพอใจ)


System.getenv ("SECONDARY_STORAGE") ไม่ทำงานกับอุปกรณ์ USB ที่เชื่อมต่อผ่านสาย OTG บน Nexus 5 และ Nexus 7
Khawar Raza

นี่คือวิธีแก้ปัญหาของฉันที่ใช้ได้จนถึง Nougat: stackoverflow.com/a/40205116/5002496
Gokul NC

14

ฉันใช้โซลูชันของ Dmitriy Lozenkoจนกระทั่งฉันตรวจสอบAsus Zenfone2 , Marshmallow 6.0.1และโซลูชันไม่ทำงาน วิธีการแก้ปัญหาล้มเหลวเมื่อได้รับEMULATED_STORAGE_TARGETเฉพาะสำหรับเส้นทาง microSD คือ: การจัดเก็บ / / F99C-10F4 / ฉันแก้ไขโค้ดเพื่อรับเส้นทางรูทจำลองโดยตรงจากเส้นทางแอปพลิเคชันที่จำลองด้วยcontext.getExternalFilesDirs(null);และเพิ่มเส้นทางทางกายภาพเฉพาะรุ่นโทรศัพท์ที่รู้จักมากขึ้น

ที่จะทำให้ชีวิตของเราง่ายขึ้นผมทำห้องสมุดที่นี่ คุณสามารถใช้งานได้ผ่านระบบสร้าง gradle, maven, sbt และ leiningen

หากคุณชอบวิธีที่ล้าสมัยคุณสามารถคัดลอกวางไฟล์ได้โดยตรงจากที่นี่แต่คุณจะไม่รู้ว่าจะมีการอัปเดตในอนาคตโดยไม่ต้องตรวจสอบด้วยตนเอง

หากคุณมีคำถามหรือข้อเสนอแนะโปรดแจ้งให้เราทราบ


1
ฉันไม่ได้ใช้สิ่งนี้อย่างกว้างขวาง แต่ก็ใช้ได้ดีกับปัญหาของฉัน
เดวิด

1
ทางออกที่ดี! อย่างไรก็ตามหมายเหตุหนึ่ง - หากถอดการ์ด SD ออกเมื่อแอปพลิเคชันทำงานอยู่context.getExternalFilesDirs(null)จะส่งคืนnullไฟล์ใดไฟล์หนึ่งและจะมีข้อยกเว้นในลูปต่อไปนี้ ฉันขอแนะนำให้เพิ่มif (file == null) continue;เป็นบรรทัดแรกในลูป
Koger

1
@Koger ขอบคุณสำหรับข้อเสนอแนะฉันได้เพิ่มและแก้ไขคำตอบของฉันผิด
HendraWD

13

ข่าวดี! ใน KitKat ตอนนี้มี API สาธารณะสำหรับการโต้ตอบกับอุปกรณ์จัดเก็บข้อมูลที่แชร์สำรองเหล่านี้

ใหม่Context.getExternalFilesDirs()และContext.getExternalCacheDirs()วิธีการสามารถส่งคืนหลายเส้นทางรวมทั้งอุปกรณ์หลักและรอง จากนั้นคุณสามารถทำซ้ำและตรวจสอบEnvironment.getStorageState()และFile.getFreeSpace()กำหนดตำแหน่งที่ดีที่สุดในการจัดเก็บไฟล์ของคุณ วิธีการเหล่านี้ยังมีอยู่ContextCompatในไลบรารี support-v4

โปรดทราบว่าหากคุณสนใจใช้เฉพาะไดเรกทอรีที่ส่งคืนContextคุณก็ไม่จำเป็นต้องมีสิทธิ์READ_หรือWRITE_EXTERNAL_STORAGEสิทธิ์อีกต่อไป นับจากนี้ไปคุณจะสามารถอ่าน / เขียนไดเร็กทอรีเหล่านี้ได้ตลอดเวลาโดยไม่ต้องมีการอนุญาตเพิ่มเติม

แอปยังสามารถทำงานบนอุปกรณ์รุ่นเก่าต่อไปได้โดยการขออนุญาตสิ้นสุดอายุการใช้งานดังนี้:

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

ขอขอบคุณ. ฉันได้สร้างโค้ดที่เป็นไปตามสิ่งที่คุณเขียนและฉันคิดว่ามันทำงานได้ดีในการค้นหาเส้นทางการ์ด SD ทั้งหมด ลองดูหน่อยได้ไหม? ที่นี่: stackoverflow.com/a/27197248/878126 BTW คุณไม่ควรใช้ "getFreeSpace" เนื่องจากในทางทฤษฎีพื้นที่ว่างสามารถเปลี่ยนแปลงได้ระหว่างรันไทม์
นักพัฒนา Android


11

ฉันทำสิ่งต่อไปนี้เพื่อเข้าถึงการ์ด sd ภายนอกทั้งหมด

ด้วย:

File primaryExtSd=Environment.getExternalStorageDirectory();

คุณจะได้รับเส้นทางไปยัง SD ภายนอกหลักจากนั้นด้วย:

File parentDir=new File(primaryExtSd.getParent());

คุณได้รับ dir หลักของที่เก็บข้อมูลภายนอกหลักและยังเป็นพาเรนต์ของ sd ภายนอกทั้งหมด ตอนนี้คุณสามารถแสดงรายการพื้นที่เก็บข้อมูลทั้งหมดและเลือกที่เก็บข้อมูลที่คุณต้องการ

หวังว่าจะเป็นประโยชน์


นี่ควรเป็นคำตอบที่ได้รับการยอมรับและขอขอบคุณที่เป็นประโยชน์
Meanman

sdcard ของฉันอยู่ที่ / storage / FSAB-2345 / primaryExtSd มาเป็น "/ storage / emulated / 0" แต่นั่นคือที่เก็บข้อมูลภายในของฉันจริงๆ! ดังนั้นฉันต้องทำการ primaryExtSd.getParentFile (). getParentFile () เพื่อไปที่ "/ storage" และนั่นคือไดเร็กทอรีหลักสำหรับไดเร็กทอรีหน่วยเก็บข้อมูลภายนอกของฉัน ดูเหมือนว่าต้องเดินเต็มต้นไม้หลังจากไปถึงรากเพื่อค้นหาที่เก็บข้อมูลทั้งหมดจริงๆ
safe_malloc

9

นี่คือวิธีรับรายการเส้นทางการ์ด SD (ไม่รวมที่จัดเก็บข้อมูลภายนอกหลัก):

  /**
   * returns a list of all available sd cards paths, or null if not found.
   * 
   * @param includePrimaryExternalStorage set to true if you wish to also include the path of the primary external storage
   */
  @TargetApi(Build.VERSION_CODES.HONEYCOMB)
  public static List<String> getSdCardPaths(final Context context,final boolean includePrimaryExternalStorage)
    {
    final File[] externalCacheDirs=ContextCompat.getExternalCacheDirs(context);
    if(externalCacheDirs==null||externalCacheDirs.length==0)
      return null;
    if(externalCacheDirs.length==1)
      {
      if(externalCacheDirs[0]==null)
        return null;
      final String storageState=EnvironmentCompat.getStorageState(externalCacheDirs[0]);
      if(!Environment.MEDIA_MOUNTED.equals(storageState))
        return null;
      if(!includePrimaryExternalStorage&&VERSION.SDK_INT>=VERSION_CODES.HONEYCOMB&&Environment.isExternalStorageEmulated())
        return null;
      }
    final List<String> result=new ArrayList<>();
    if(includePrimaryExternalStorage||externalCacheDirs.length==1)
      result.add(getRootOfInnerSdCardFolder(externalCacheDirs[0]));
    for(int i=1;i<externalCacheDirs.length;++i)
      {
      final File file=externalCacheDirs[i];
      if(file==null)
        continue;
      final String storageState=EnvironmentCompat.getStorageState(file);
      if(Environment.MEDIA_MOUNTED.equals(storageState))
        result.add(getRootOfInnerSdCardFolder(externalCacheDirs[i]));
      }
    if(result.isEmpty())
      return null;
    return result;
    }

  /** Given any file/folder inside an sd card, this will return the path of the sd card */
  private static String getRootOfInnerSdCardFolder(File file)
    {
    if(file==null)
      return null;
    final long totalSpace=file.getTotalSpace();
    while(true)
      {
      final File parentFile=file.getParentFile();
      if(parentFile==null||parentFile.getTotalSpace()!=totalSpace||!parentFile.canRead())
        return file.getAbsolutePath();
      file=parentFile;
      }
    }

externalCacheDirs[0].getParentFile().getParentFile().getParentFile().getParentFile().getAbsolutePath()นี่คือสูตรสำหรับภัยพิบัติ หากคุณกำลังทำสิ่งนี้คุณอาจจะฮาร์ดโค้ดเส้นทางที่คุณต้องการได้เช่นกันหากคุณได้รับแคช dir กับสิ่งอื่นที่ไม่ใช่ผู้ปกครอง 4 คนคุณจะได้รับ NullPointerException หรือเส้นทางที่ไม่ถูกต้อง
rharter

@rharter ถูกต้อง. แก้ไขปัญหานี้ด้วย โปรดดู
นักพัฒนา Android

เอกสาร ( developer.android.com/reference/android/content/… ) กล่าวว่า "อุปกรณ์จัดเก็บข้อมูลภายนอกที่ส่งคืนที่นี่ถือเป็นส่วนถาวรของอุปกรณ์ซึ่งรวมถึงพื้นที่จัดเก็บข้อมูลภายนอกที่จำลองและช่องเสียบสื่อจริงเช่นการ์ด SD ในแบตเตอรี่ ช่องทางที่ส่งกลับไม่รวมอุปกรณ์ชั่วคราวเช่นแฟลชไดรฟ์ USB " ดูเหมือนว่าจะไม่มีช่องเสียบการ์ด SD เช่นสถานที่ที่คุณสามารถถอดการ์ด SD ได้โดยไม่ต้องใช้แบตเตอรี่ ยากที่จะบอกว่า
LarsH

1
@LarsH ฉันได้ทดสอบด้วยตัวเองใน SGS3 (และการ์ด SD จริง) ดังนั้นฉันคิดว่ามันควรจะทำงานบนอุปกรณ์อื่นด้วย
นักพัฒนา Android

5

ขอบคุณสำหรับเบาะแสจากพวกคุณโดยเฉพาะ @SmartLemon ฉันได้วิธีแก้ปัญหาแล้ว ในกรณีที่มีคนอื่นต้องการฉันใส่วิธีแก้ไขขั้นสุดท้ายที่นี่ (เพื่อค้นหาการ์ด SD ภายนอกที่อยู่ในรายการแรก):

public File getExternalSDCardDirectory()
{
    File innerDir = Environment.getExternalStorageDirectory();
    File rootDir = innerDir.getParentFile();
    File firstExtSdCard = innerDir ;
    File[] files = rootDir.listFiles();
    for (File file : files) {
        if (file.compareTo(innerDir) != 0) {
            firstExtSdCard = file;
            break;
        }
    }
    //Log.i("2", firstExtSdCard.getAbsolutePath().toString());
    return firstExtSdCard;
}

หากไม่มีการ์ด SD ภายนอกจะส่งคืนพื้นที่จัดเก็บข้อมูลบนบอร์ด ฉันจะใช้มันถ้าไม่มี sdcard คุณอาจต้องเปลี่ยน


1
การขว้างนี้ null บน Android เวอร์ชัน 19 rootDir.listFiles () คืนค่า null ฉันได้ทดสอบกับ nexus 7, emulator และ galaxy note แล้ว
Manu Zi

3

อ้างถึงรหัสของฉันหวังว่าจะเป็นประโยชน์สำหรับคุณ:

    Runtime runtime = Runtime.getRuntime();
    Process proc = runtime.exec("mount");
    InputStream is = proc.getInputStream();
    InputStreamReader isr = new InputStreamReader(is);
    String line;
    String mount = new String();
    BufferedReader br = new BufferedReader(isr);
    while ((line = br.readLine()) != null) {
        if (line.contains("secure")) continue;
        if (line.contains("asec")) continue;

        if (line.contains("fat")) {//TF card
            String columns[] = line.split(" ");
            if (columns != null && columns.length > 1) {
                mount = mount.concat("*" + columns[1] + "\n");
            }
        } else if (line.contains("fuse")) {//internal storage
            String columns[] = line.split(" ");
            if (columns != null && columns.length > 1) {
                mount = mount.concat(columns[1] + "\n");
            }
        }
    }
    txtView.setText(mount);

2

อันที่จริงในอุปกรณ์บางชื่อเริ่มต้นภายนอก sdcard มีการแสดงเป็นextSdCardและอื่น ๆ sdcard1ก็คือ

ข้อมูลโค้ดนี้ช่วยในการค้นหาเส้นทางที่แน่นอนและช่วยดึงเส้นทางของอุปกรณ์ภายนอกให้คุณ

String sdpath,sd1path,usbdiskpath,sd0path;    
        if(new File("/storage/extSdCard/").exists())
            {
               sdpath="/storage/extSdCard/";
               Log.i("Sd Cardext Path",sdpath);
            }
        if(new File("/storage/sdcard1/").exists())
         {
              sd1path="/storage/sdcard1/";
              Log.i("Sd Card1 Path",sd1path);
         }
        if(new File("/storage/usbcard1/").exists())
         {
              usbdiskpath="/storage/usbcard1/";
              Log.i("USB Path",usbdiskpath);
         }
        if(new File("/storage/sdcard0/").exists())
         {
              sd0path="/storage/sdcard0/";
              Log.i("Sd Card0 Path",sd0path);
         }

สิ่งนี้ช่วยฉันได้อย่างมาก
Droid Chris

มีอุปกรณ์บางอย่างที่ใช้ "/ storage / external_sd" (LG G3 KitKat), Marshmallow มีรูปแบบที่แตกต่างกัน, Internal Storage บน Nexus 5X ที่ใช้ Android 6.0 คือ "/ mnt / sdcard" และพบ SDcard ภายนอกภายใต้ "/ storage / XXXX-XXXX "
AB

2

ใช่. ผู้ผลิตรายอื่นใช้ชื่อ SDcard ที่แตกต่างกันเช่นใน Samsung Tab 3 extsd และอุปกรณ์ samsung อื่น ๆ ใช้ sdcard เช่นเดียวกับผู้ผลิตรายอื่นใช้ชื่อที่แตกต่างกัน

ฉันมีความต้องการเช่นเดียวกับคุณ ดังนั้นฉันจึงได้สร้างตัวอย่างสำหรับคุณจากโครงการของฉันไปที่ลิงก์นี้ตัวอย่างตัวเลือกไดเรกทอรี Androidซึ่งใช้ไลบรารี androi-dirchooser ตัวอย่างนี้ตรวจจับ SDcard และแสดงรายการโฟลเดอร์ย่อยทั้งหมดและยังตรวจพบว่าอุปกรณ์มีมากกว่าหนึ่ง SDcard หรือไม่

ส่วนหนึ่งของโค้ดมีลักษณะดังนี้ตัวอย่างเต็มไปที่ลิงค์Android Directory Chooser

/**
* Returns the path to internal storage ex:- /storage/emulated/0
 *
* @return
 */
private String getInternalDirectoryPath() {
return Environment.getExternalStorageDirectory().getAbsolutePath();
 }

/**
 * Returns the SDcard storage path for samsung ex:- /storage/extSdCard
 *
 * @return
 */
    private String getSDcardDirectoryPath() {
    return System.getenv("SECONDARY_STORAGE");
}


 mSdcardLayout.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View view) {
        String sdCardPath;
        /***
         * Null check because user may click on already selected buton before selecting the folder
         * And mSelectedDir may contain some wrong path like when user confirm dialog and swith back again
         */

        if (mSelectedDir != null && !mSelectedDir.getAbsolutePath().contains(System.getenv("SECONDARY_STORAGE"))) {
            mCurrentInternalPath = mSelectedDir.getAbsolutePath();
        } else {
            mCurrentInternalPath = getInternalDirectoryPath();
        }
        if (mCurrentSDcardPath != null) {
            sdCardPath = mCurrentSDcardPath;
        } else {
            sdCardPath = getSDcardDirectoryPath();
        }
        //When there is only one SDcard
        if (sdCardPath != null) {
            if (!sdCardPath.contains(":")) {
                updateButtonColor(STORAGE_EXTERNAL);
                File dir = new File(sdCardPath);
                changeDirectory(dir);
            } else if (sdCardPath.contains(":")) {
                //Multiple Sdcards show root folder and remove the Internal storage from that.
                updateButtonColor(STORAGE_EXTERNAL);
                File dir = new File("/storage");
                changeDirectory(dir);
            }
        } else {
            //In some unknown scenario at least we can list the root folder
            updateButtonColor(STORAGE_EXTERNAL);
            File dir = new File("/storage");
            changeDirectory(dir);
        }


    }
});

2

โซลูชันนี้ (ประกอบจากคำตอบอื่น ๆ ของคำถามนี้) จัดการกับข้อเท็จจริง (ตามที่ @ono กล่าวไว้) ซึ่งSystem.getenv("SECONDARY_STORAGE")ไม่มีประโยชน์กับ Marshmallow

ผ่านการทดสอบและทำงานบน:

  • Samsung Galaxy Tab 2 (Android 4.1.1 - สต็อก)
  • Samsung Galaxy Note 8.0 (Android 4.2.2 - หุ้น)
  • Samsung Galaxy S4 (Android 4.4 - หุ้น)
  • Samsung Galaxy S4 (Android 5.1.1 - Cyanogenmod)
  • Samsung Galaxy Tab A (Android 6.0.1 - สต็อก)

    /**
     * Returns all available external SD-Card roots in the system.
     *
     * @return paths to all available external SD-Card roots in the system.
     */
    public static String[] getStorageDirectories() {
        String [] storageDirectories;
        String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE");
    
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            List<String> results = new ArrayList<String>();
            File[] externalDirs = applicationContext.getExternalFilesDirs(null);
            for (File file : externalDirs) {
                String path = file.getPath().split("/Android")[0];
                if((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Environment.isExternalStorageRemovable(file))
                        || rawSecondaryStoragesStr != null && rawSecondaryStoragesStr.contains(path)){
                    results.add(path);
                }
            }
            storageDirectories = results.toArray(new String[0]);
        }else{
            final Set<String> rv = new HashSet<String>();
    
            if (!TextUtils.isEmpty(rawSecondaryStoragesStr)) {
                final String[] rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator);
                Collections.addAll(rv, rawSecondaryStorages);
            }
            storageDirectories = rv.toArray(new String[rv.size()]);
        }
        return storageDirectories;
    }
    

เงื่อนไขrawSecondaryStoragesStr.contains(path)สามารถอนุญาตให้เพิ่มที่เก็บข้อมูลภายในได้ เราสามารถลบเงื่อนไขนั้นได้ดีขึ้น .. ฉันประสบปัญหาดังกล่าวเนื่องจากอุปกรณ์ของฉันส่งคืนไดเร็กทอรีที่เก็บข้อมูลภายในด้วยSystem.getenv("SECONDARY_STORAGE")แต่ส่งคืนเส้นทางการ์ดหน่วยความจำภายนอกด้วยSystem.getenv(EXTERNAL_STORAGE)KitKat; ดูเหมือนว่าผู้ค้ารายต่างๆได้ดำเนินการสิ่งนี้แตกต่างกันโดยไม่มีมาตรฐานใด ๆ ..
Gokul NC

นี่คือวิธีแก้ปัญหาของฉันที่ใช้ได้จนถึง Nougat: stackoverflow.com/a/40205116/5002496
Gokul NC

1

ในอุปกรณ์บางอย่าง (เช่น samsung galaxy sII) การ์ดหน่วยความจำภายใน mabe อยู่ใน vfat ในกรณีนี้ใช้อ้างอิงรหัสสุดท้ายเราจะได้รับการ์ดหน่วยความจำภายในเส้นทาง (/ mnt / sdcad) แต่ไม่มีการ์ดภายนอก รหัสดูด้านล่างเพื่อแก้ปัญหานี้

static String getExternalStorage(){
         String exts =  Environment.getExternalStorageDirectory().getPath();
         try {
            FileReader fr = new FileReader(new File("/proc/mounts"));       
            BufferedReader br = new BufferedReader(fr);
            String sdCard=null;
            String line;
            while((line = br.readLine())!=null){
                if(line.contains("secure") || line.contains("asec")) continue;
            if(line.contains("fat")){
                String[] pars = line.split("\\s");
                if(pars.length<2) continue;
                if(pars[1].equals(exts)) continue;
                sdCard =pars[1]; 
                break;
            }
        }
        fr.close();
        br.close();
        return sdCard;  

     } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return null;
}

1
       File[] files = null;
    File file = new File("/storage");// /storage/emulated
if (file.exists()) {
        files = file.listFiles();
            }
            if (null != files)
                for (int j = 0; j < files.length; j++) {
                    Log.e(TAG, "" + files[j]);
                    Log.e(TAG, "//--//--// " +             files[j].exists());

                    if (files[j].toString().replaceAll("_", "")
                            .toLowerCase().contains("extsdcard")) {
                        external_path = files[j].toString();
                        break;
                    } else if (files[j].toString().replaceAll("_", "")
                            .toLowerCase()
                            .contains("sdcard".concat(Integer.toString(j)))) {
                        // external_path = files[j].toString();
                    }
                    Log.e(TAG, "--///--///--  " + external_path);
                }

0

ฉันมั่นใจว่ารหัสนี้จะช่วยแก้ปัญหาของคุณได้อย่างแน่นอน ... นี่ใช้งานได้ดีสำหรับฉัน ... \

try {
            File mountFile = new File("/proc/mounts");
            usbFoundCount=0;
            sdcardFoundCount=0;
            if(mountFile.exists())
             {
                Scanner usbscanner = new Scanner(mountFile);
                while (usbscanner.hasNext()) {
                    String line = usbscanner.nextLine();
                    if (line.startsWith("/dev/fuse /storage/usbcard1")) {
                        usbFoundCount=1;
                        Log.i("-----USB--------","USB Connected and properly mounted---/dev/fuse /storage/usbcard1" );
                    }
            }
         }
            if(mountFile.exists()){
                Scanner sdcardscanner = new Scanner(mountFile);
                while (sdcardscanner.hasNext()) {
                    String line = sdcardscanner.nextLine();
                    if (line.startsWith("/dev/fuse /storage/sdcard1")) {
                        sdcardFoundCount=1;
                        Log.i("-----USB--------","USB Connected and properly mounted---/dev/fuse /storage/sdcard1" );
                    }
            }
         }
            if(usbFoundCount==1)
            {
                Toast.makeText(context,"USB Connected and properly mounted", 7000).show();
                Log.i("-----USB--------","USB Connected and properly mounted" );
            }
            else
            {
                Toast.makeText(context,"USB not found!!!!", 7000).show();
                Log.i("-----USB--------","USB not found!!!!" );

            }
            if(sdcardFoundCount==1)
            {
                Toast.makeText(context,"SDCard Connected and properly mounted", 7000).show();
                Log.i("-----SDCard--------","SDCard Connected and properly mounted" );
            }
            else
            {
                Toast.makeText(context,"SDCard not found!!!!", 7000).show();
                Log.i("-----SDCard--------","SDCard not found!!!!" );

            }
        }catch (Exception e) {
            e.printStackTrace();
        } 

Environment.getExternalStorageDirectory () ไม่ควรให้ตำแหน่งที่แน่นอนของ sdcard ภายนอกหรือตำแหน่ง usb ของคุณเพียงแค่แยกวิเคราะห์ไปยังตำแหน่ง "/ proc / mounts" จากนั้น / dev / fuse / storage / sdcard1 "จะให้ตำแหน่งว่า sdcard ของคุณถูกต้องหรือไม่ ติดหรือไม่เหมือนกันสำหรับ usb ด้วยวิธีนี้คุณสามารถรับได้อย่างง่ายดาย .. ไชโย ..
แซม

0

ที่ไม่เป็นความจริง. / mnt / sdcard / external_sd ได้แม้ว่าจะไม่ได้ต่อการ์ด SD ก็ตาม แอปพลิเคชันของคุณจะหยุดทำงานเมื่อคุณพยายามเขียนถึง / mnt / sdcard / external_sd เมื่อไม่ได้ต่อเชื่อม

คุณต้องตรวจสอบว่าติดตั้งการ์ด SD ก่อนโดยใช้:

boolean isSDPresent = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);

7
getExternalStorageState()ฉันคิดว่าสิ่งนี้ไม่มีความหมายเมื่อส่งคืนที่จัดเก็บข้อมูลภายในแทนที่จะเป็นการ์ด SD ภายนอก
Romulus Urakagi Ts'ai

ตอนนี้พวกเขามีฟังก์ชั่นรับการ์ด SD ภายนอกหรือไม่ ฉันไม่พบเบาะแสใด ๆ หลังจากค้นหาเป็นเวลาหนึ่งชั่วโมง หากเป็นเช่นนั้นสิ่งนี้ก็แปลกมากหลังจากมีการอัปเดตหลายเวอร์ชัน
rml

เมื่อคุณพูดว่า "ไม่จริง" คุณหมายถึงอะไร ฉันรู้ว่านั่นคือ 2.5 ปีที่แล้ว ...
LarsH

0
 String path = Environment.getExternalStorageDirectory()
                        + File.separator + Environment.DIRECTORY_PICTURES;
                File dir = new File(path);

4
มันใช้ไม่ได้ ส่งคืนเส้นทางของ Internal Storage ie / storage / emulated / 0

0

คุณสามารถใช้บางอย่างเช่น - Context.getExternalCacheDirs () หรือ Context.getExternalFilesDirs () หรือ Context.getObbDirs () พวกเขาให้ไดเรกทอรีเฉพาะของแอปพลิเคชันในอุปกรณ์จัดเก็บข้อมูลภายนอกทั้งหมดที่แอปพลิเคชันสามารถจัดเก็บไฟล์ได้

ดังนั้นสิ่งนี้ - Context.getExternalCacheDirs () [i] .getParentFile (). getParentFile (). getParentFile (). getParent () สามารถรับพา ธ รูทของอุปกรณ์จัดเก็บข้อมูลภายนอกได้

ฉันรู้ว่าคำสั่งเหล่านี้มีวัตถุประสงค์อื่น แต่คำตอบอื่นไม่ได้ผลสำหรับฉัน

ลิงก์นี้ให้คำแนะนำที่ดีแก่ฉัน - https://possiblemobile.com/2014/03/android-external-storage/


0

System.getenv("SECONDARY_STORAGE")ส่งคืนค่าว่างสำหรับ Marshmallow นี่เป็นอีกวิธีหนึ่งในการค้นหาไดร์ภายนอกทั้งหมด คุณสามารถตรวจสอบได้ว่าถอดออกได้หรือไม่ซึ่งจะกำหนดว่าภายใน / ภายนอก

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    File[] externalCacheDirs = context.getExternalCacheDirs();
    for (File file : externalCacheDirs) {
        if (Environment.isExternalStorageRemovable(file)) {
            // It's a removable storage
        }
    }
}

0

ฉันได้ลองใช้วิธีแก้ปัญหาโดยDmitriy LozenkoและGnathonicบนSamsung Galaxy Tab S2 (รุ่น: T819Y)แต่ไม่มีวิธีใดช่วยให้ฉันดึงเส้นทางไปยังไดเร็กทอรี SD Card ภายนอกได้ mountการดำเนินการคำสั่งมีเส้นทางที่ต้องการไปยังไดเร็กทอรี SD Card ภายนอก(เช่น / Storage / A5F9-15F4)แต่ไม่ตรงกับนิพจน์ทั่วไปจึงไม่ถูกส่งกลับ ฉันไม่ได้รับกลไกการตั้งชื่อไดเรกทอรีตามด้วย Samsung ทำไมพวกเขาเบี่ยงเบนจากมาตรฐาน(เช่น extsdcard)และมากับบางสิ่งบางอย่างมันคาวเช่นในกรณีของฉัน(เช่น / จัดเก็บ / A5F9-15F4) มีอะไรที่ฉันขาดหายไป? อย่างไรก็ตามหลังจากการเปลี่ยนแปลงในนิพจน์ทั่วไปของGnathonic โซลูชันช่วยให้ฉันได้รับไดเร็กทอรี sdcard ที่ถูกต้อง:

final HashSet<String> out = new HashSet<String>();
        String reg = "(?i).*(vold|media_rw).*(sdcard|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;

ฉันไม่แน่ใจว่านี่เป็นวิธีแก้ปัญหาที่ถูกต้องหรือไม่และจะให้ผลลัพธ์กับแท็บเล็ต Samsung รุ่นอื่น ๆ หรือไม่ แต่ตอนนี้ได้แก้ไขปัญหาของฉันแล้ว ต่อไปนี้เป็นอีกวิธีในการดึงเส้นทาง SD Card แบบถอดได้ใน Android (v6.0) ฉันได้ทดสอบวิธีการกับ android marshmallow แล้วและใช้งานได้ แนวทางที่ใช้นั้นเป็นวิธีพื้นฐานมากและจะใช้ได้กับเวอร์ชันอื่น ๆ ด้วยเช่นกัน แต่จำเป็นต้องทำการทดสอบ ข้อมูลเชิงลึกบางอย่างจะเป็นประโยชน์:

public static String getSDCardDirPathForAndroidMarshmallow() {

    File rootDir = null;

    try {
        // Getting external storage directory file
        File innerDir = Environment.getExternalStorageDirectory();

        // Temporarily saving retrieved external storage directory as root
        // directory
        rootDir = innerDir;

        // Splitting path for external storage directory to get its root
        // directory

        String externalStorageDirPath = innerDir.getAbsolutePath();

        if (externalStorageDirPath != null
                && externalStorageDirPath.length() > 1
                && externalStorageDirPath.startsWith("/")) {

            externalStorageDirPath = externalStorageDirPath.substring(1,
                    externalStorageDirPath.length());
        }

        if (externalStorageDirPath != null
                && externalStorageDirPath.endsWith("/")) {

            externalStorageDirPath = externalStorageDirPath.substring(0,
                    externalStorageDirPath.length() - 1);
        }

        String[] pathElements = externalStorageDirPath.split("/");

        for (int i = 0; i < pathElements.length - 1; i++) {

            rootDir = rootDir.getParentFile();
        }

        File[] files = rootDir.listFiles();

        for (File file : files) {
            if (file.exists() && file.compareTo(innerDir) != 0) {

                // Try-catch is implemented to prevent from any IO exception
                try {

                    if (Environment.isExternalStorageRemovable(file)) {
                        return file.getAbsolutePath();

                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    return null;
}

โปรดแบ่งปันหากคุณมีแนวทางอื่นในการจัดการปัญหานี้ ขอบคุณ


นี่คือวิธีแก้ปัญหาของฉันที่ใช้ได้จนถึง Nougat: stackoverflow.com/a/40205116/5002496
Gokul NC

ฉันมีปัญหาเดียวกันกับคุณใน Asus Zenfone 2 แต่ฉันเปลี่ยนรหัสจาก Dmitriy Lozenko แทนเพื่อแก้ปัญหาstackoverflow.com/a/40582634/3940133
HendraWD

@HendraWD โซลูชันที่คุณแนะนำอาจใช้ไม่ได้กับ Android Marshmallow (6.0) เนื่องจากการสนับสนุนตัวแปรสภาพแวดล้อมจะถูกลบออก ฉันจำไม่ได้อย่างแน่นอน แต่ฉันคิดว่าฉันได้ลองแล้ว
อับดุลเรห์มาน

@GokulNC ฉันจะลองดู ดูเหมือนว่า Legit :)
Abdul Rehman

@GokulNC ใช่นั่นคือเหตุผลที่ฉันใช้ context.getExternalFilesDirs (null); แทนที่จะใช้เส้นทางจริงโดยตรงเมื่อเวอร์ชันคือ> Marshmallow
HendraWD

0
String secStore = System.getenv("SECONDARY_STORAGE");

File externalsdpath = new File(secStore);

สิ่งนี้จะได้รับเส้นทางของที่เก็บข้อมูลสำรองภายนอก sd


0
//manifest file outside the application tag
//please give permission write this 
//<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
        File file = new File("/mnt");
        String[] fileNameList = file.list(); //file names list inside the mnr folder
        String all_names = ""; //for the log information
        String foundedFullNameOfExtCard = ""; // full name of ext card will come here
        boolean isExtCardFounded = false;
        for (String name : fileNameList) {
            if (!isExtCardFounded) {
                isExtCardFounded = name.contains("ext");
                foundedFullNameOfExtCard = name;
            }
            all_names += name + "\n"; // for log
        }
        Log.d("dialog", all_names + foundedFullNameOfExtCard);

นี่เป็นทางเลือกสำหรับผู้เริ่มต้นอย่างน้อยการรับ stringList ของไดรเวอร์ทั้งหมดที่ติดตั้งในอุปกรณ์
Emre Kilinc Arslan

0

ในการเข้าถึงไฟล์ในการ์ด SDของฉันบน HTC One X (Android) ฉันใช้เส้นทางนี้:

file:///storage/sdcard0/folder/filename.jpg

สังเกตุไตรป์ "/"!


-1

บน Galaxy S3 Android 4.3 เส้นทางที่ฉันใช้คือ. / storage/extSdCard/Card/และมันก็ทำงานได้ หวังว่ามันจะช่วย


-1

ขั้นตอนต่อไปนี้ใช้ได้ผลสำหรับฉัน คุณต้องเขียนบรรทัดนี้:

String sdf = new String(Environment.getExternalStorageDirectory().getName());
String sddir = new String(Environment.getExternalStorageDirectory().getPath().replace(sdf,""));

บรรทัดแรกจะให้ชื่อไดเร็กทอรี sd และคุณต้องใช้ในวิธีการแทนที่สำหรับสตริงที่สอง สตริงที่สองจะมีเส้นทางสำหรับภายในและถอดSD (การเก็บรักษา / / ในกรณีของฉัน) ฉันต้องการเส้นทางนี้สำหรับแอปของฉัน แต่คุณสามารถไปได้ไกลกว่านี้หากต้องการ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.