ContentProvider再探——Pembekal Dokumen


Pengenalan kepada bahagian ini:

Selepas mengkaji bahagian sebelumnya, saya percaya anda sudah tahu cara menggunakan ContentProvider yang disediakan oleh sistem atau menyesuaikan ContentProvider. Ia pada asasnya telah memenuhi keperluan pembangunan harian Menariknya, saya melihat Penyedia lain ini dalam dokumentasi rasmi:

1.png

Penyedia Kalendar: Pembekal kalendar ialah. perpustakaan sumber untuk acara berkaitan kalendar Melalui API yang disediakannya, kami Anda boleh menambah, memadam, mengubah suai dan menyemak kalendar, masa, mesyuarat, peringatan dan kandungan lain!
Pembekal Kenalan: Hubungi pembekal, tidak perlu dikatakan, ini adalah yang paling banyak digunakan~ Saya akan kembali dan menterjemah artikel ini kemudian apabila saya mempunyai masa!
Rangka Kerja Akses Storan (SAF): Rangka kerja akses storan, perkara baharu yang diperkenalkan selepas 4.4, membolehkan pengguna menyemak imbas fail pada telefon mudah alih mereka. Kandungan storan menyediakan kemudahan, dan kandungan yang boleh diakses bukan sahaja termasuk: dokumen, gambar, video, audio, muat turun, tetapi juga termasuk semua Kandungan yang disediakan oleh ContentProvider tertentu (yang mesti mempunyai API yang dipersetujui). Tidak kira dari mana kandungan itu datang, tidak kira aplikasi mana Dengan memanggil arahan untuk menyemak imbas kandungan fail sistem, sistem akan menggunakan antara muka bersatu untuk anda semak imbas.
Malah, ia adalah aplikasi terbina dalam yang dipanggil DocumentsUI, kerana IntentFilternya tidak mempunyai PELANCUR, jadi kami tidak Cari bahan ini pada desktop! Hei, cuba kod berikut Di sini kami telah memilih dua telefon mudah alih untuk perbandingan. Sebagai perbandingan, Lenovo S898T daripada 4.2 dan Nexus 5 daripada 5.0.1 dibandingkan dengan kod berikut:

Niat niat = Niat baharu(Intent.ACTION_OPEN_DOCUMENT);
niat. addCategory (Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
startActivity(intent);
Berikut ialah hasil yang sedang dijalankan:

2.png3.png

Yang di sebelah kanan ialah perkara baharu yang dibawakan kepada kami oleh 4.4 Secara amnya, kami boleh menggunakannya apabila kami mendapat fail Url~ Seterusnya, cuma turun dokumen~


2. Cuma turun dokumen:

1) Komposisi rangka kerja SAF:

  • Pembekal dokumen: Penyedia Kandungan khas yang membenarkan perkhidmatan storan (seperti Google Drive) untuk Paparkan fail yang anda uruskan kepada dunia luar. Ia adalah subkelas Penyedia Dokumen Selain itu, format storan pembekal dokumen Ia selaras dengan format storan fail tradisional Bagi cara menyimpan kandungan anda, terpulang kepada anda Sistem Android mempunyai beberapa terbina dalam Pembekal Dokumen sedemikian, seperti pembekal Dokumen untuk muat turun, gambar dan video!
  • Apl pelanggan: Perisian klien biasa yang boleh menerima pulangan daripada pembekal Dokumen dengan mencetuskan ACTION_OPEN_DOCUMENT dan/atau ACTION_CREATE_DOCUMENT kandungan, seperti memilih gambar, Kemudian pulangkan Uri.
  • Pemilih: Antara muka yang serupa dengan pengurus fail dan antara muka peringkat sistem yang menyediakan akses kepada syarat penapisan pelanggan Saluran kandungan penyedia Dokumen ialah program DocumentsUI yang disebutkan pada mulanya!

Sesetengah ciri:

  • Pengguna boleh menyemak imbas kandungan yang disediakan oleh semua pembekal dokumen, bukan hanya satu Program aplikasi
  • menyediakan akses jangka panjang dan berterusan kepada fail dalam pembekal dokumen dan data yang berterusan. Pengguna boleh menambah, memadam, mengedit dan menyimpan kandungan yang diselenggara oleh pembekal dokumen
  • Menyokong perkhidmatan kandungan berbilang pengguna dan sementara, seperti pembekal storan USB yang hanya akan muncul apabila pemacu berjaya dipasang

2) Gambaran Keseluruhan:

Inti SAF adalah untuk melaksanakan subkelas Pembekal Dokumen atau Pembekal Kandungan. dalam pembekal dokumen disusun dengan pepohon direktori fail tradisional:

4.png

3) Carta alir:

Seperti yang dinyatakan di atas, data pembekal dokumen adalah berdasarkan struktur hierarki Fail tradisional , tetapi itu hanya perwakilan luaran. Cara menyimpan data anda terpulang kepada anda, asalkan antara muka luar negara anda boleh diakses melalui API DocumentsProvider. Carta alir di bawah menunjukkan kemungkinan struktur aplikasi foto menggunakan SAF:

5.png

Analisis:

Daripada gambar di atas, kita dapat melihat bahawa Picker ialah jambatan antara pemanggil dan penyedia kandungan! Ia menyediakan dan memberitahu pemanggil bahawa ia boleh memilih Pembekal kandungan yang mana, seperti DriveDocProvider, UsbDocProvider, CloundDocProvider di sini.
Apabila pelanggan mencetuskan Niat ACTION_OPEN_DOCUMENT atau ACTION_CREATE_DOCUMENT, interaksi di atas akan berlaku. Sudah tentu, kami juga boleh menambah syarat penapis pada Niat, seperti mengehadkan jenis MIME kepada "imej"

6.png

Ia adalah perkara di atas, jika anda turut dipasang Jika anda menggunakan perisian lain untuk melihat gambar, anda juga akan melihatnya di sini! Ringkasnya: selepas klien menghantar Niat dua Tindakan di atas, UI Pemilih akan dibuka, di mana pilihan tersedia yang berkaitan akan dipaparkan. Pembekal Dokumen adalah untuk pengguna memilih Selepas pengguna memilih, mereka boleh mendapatkan maklumat yang berkaitan tentang fail tersebut!


4) Pelanggan memanggil dan dapatkan Uri yang dikembalikan

Kod pelaksanaan adalah seperti berikut:

MainActivity Kelas Awam Public Extends AppCompatactivity mengimplementasikan view.onclickListener {
final static swasta int read_request_code = 42; .onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Butang O btn_show = (Button) findViewById(R.id.btn_show);  Listen (R.id.btn_show);  Listen (R.id.btn_show) s);
    }

    @Override
    public void onClick(Lihat v) {
        Niat niat = Niat baharu(Intent.ACTION_OPPEN_DOCUMENT);
     ENABLE .  dd. 🎜>        niat. setType("imej/*");
        startActivityForResult(niat, READ_REQUEST_CODE);
    }

    @Override
     protected resultCode data) {
Jika (requestCode == read_request_code && resultCode == activity.result_ok) { uri uri;
if (data! = Null) { uri = data.getdata (); ("HeHe", "Uri: " + uri.toString());
            }
         }
    }
}

Hasil berjalan: Sebagai contoh, jika kita memilih anjing, UI Picker akan dimatikan dengan sendirinya, dan kemudian anda boleh melihat uri sedemikian pada Logcat:

7.png


5) Dapatkan parameter fail berdasarkan uri

Kod teras adalah seperti berikut:

public void dumpImageMetaData( Uri uri) {
Kursor kursor = getContentResolver()
.query(uri, null, null, null, null, null);
cuba {
if (cursor != null && cursor.moveToFirst ()) {
        String displayName = cursor.getString(
                    cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); > jika (!cursor.isNull(sizeIndex)) {
size = cursor.getString(sizeIndex);
                                                                                                                                            ";
}
Log.e("HeHe", "Saiz: " + saiz) ;
}
}akhirnya {
cursor.close();
}
}

Hasil jalankan: Ia masih anjing yang sama Selepas memanggil kaedah, nama fail dan saiz fail akan dimasukkan, dalam bait

8.png


6) Dapatkan Bitmap

berdasarkan Uri Kod teras adalah seperti berikut:

Bitmap peribadi getBitmapFromUri(Uri uri) membuang IOException {<. . .close();
kembalikan imej;
}



Hasil jalankan
:

7) Dapatkan aliran input berdasarkan Uri

9.gifKod teras adalah seperti berikut:

Rentetan peribadi readTextFromUri(Uri uri) membuang IOException { InputStream inputStream = getContentResolver().openInputStream(uri); Pembaca BufferedReader = BufferedReader baharu(InputStreamReader baharu(

inputStream));
StringBuilder stringBuilder = StringBuilder baharu();
Baris rentetan;
Baris rentetan;<🎜 ((line = reader.readL ine()) != null ) {
       stringBuilder.append(line);

Kandungan di atas hanya memberitahu anda perkara yang anda boleh ketahui melalui Uri, dan Uri diperoleh melalui SAF!


8) Cipta fail baharu dan padam fail:

Cipta fail:

private void createFile(String mimeType, String Nama fail) {
Niat niat = Niat baharu(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(mimeType);
intent.putExtra(Intent.TITLE. FileName);
startActivityForResult(intent, WRITE_REQUEST_CODE);
}

Uri fail yang dibuat boleh diperolehi dalam onActivityResult()

Padam fail:

Dengan syarat Document.COLUMN_FLAGS mengandungi SUPPORTS_DELETE

DocumentsContract.deleteDocument(getContentResolver(), uri);
<🎜

9) Tulis Pembekal Dokumen tersuai

Jika anda mahu data permohonan anda dibuka dalam documentsui, anda perlu menulis pembekal dokumen anda sendiri. Berikut menerangkan langkah-langkah untuk menyesuaikan DocumentsProvider:

  • Versi API ialah 19 atau lebih tinggi
  • Daftarkan Penyedia dalam manifes.xml
  • Nama pembekal Tambah nama pakej kepada nama kelas, contohnya: com.example.android.storageprovider.MyCloudProvider
  • Authority ialah nama pakej + nama jenis penyedia, contohnya: com.example.android .storageprovider.documents
  • Nilai atribut android:exported adalah benar

Berikut ialah contoh penulisan Pembekal:

<manifest... >
...
<uses-sdk
android:minSdkVersion="19"
android : TargetSdkversion = "19" /& gt; didayakan ="@bool/atLeastKitKat">
                                                                                                                                                                                    </provider>
</application>

</manifest>

10) Subkelas DocumentsProvider

melaksanakan sekurang-kurangnya kaedah berikut:

  • queryRoots()
  • queryChildDocuments()
  • queryDocument()
  • openDocument()

Terdapat kaedah lain, tetapi ia tidak diperlukan. Berikut menunjukkan pelaksanaan mengakses sistem fail Cara kasar untuk menulis DocumentsProvider.

Laksanakan queryRoots

@Override
public Cursor queryRoots(String[] projection) melemparkan FileNotFoundException {

    // Buat kursor dengan sama                  medan default atau proction "Unjuran" adalah NULL.
Hasil MatrixCursor Akhir =
MatrixCursor baru (ResolVerootProjection (unjuran));  Ini mengalih keluar pembekal
    // kami dari  senarai sepenuhnya.
    jika (!isUserLoggedIn()) {
        hasil pemulangan;
     }
    jika (!isUserLoggedIn()) {
        hasil pemulangan;
     }
                                                                             >< akar ( cth. untuk berbilang akaun dalam 
    // apl yang sama) -- hanya tambah berbilang baris kursor.
    // Bina satu baris untuk akar dipanggil "MyCloud .                                                                                                 . baris ();
    row.add(Root.COLUMN_ROOT_ID, ROOT);
    row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary));

    / FLAG_SUPPORTS_CREATE bermaksud sekurang-kurangnya satu direktori di bawah akar menyokong
    // membuat dokumen. FLAG_SUPPORTS_RECENTS bermaksud yang permohonan anda paling
    // dokumen yang digunakan baru-baru akan dipaparkan  dalam                                         //         /          /           /             /                                                                            . 🎜>    // perkongsian.
    baris.tambah (Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE |
            Root. FLAG_SUPPORTS_RECENTS |
            Root. FLAG_SUPPORTS<🎎 >< TICLE); ialah tajuk akar (cth. Galeri, Drive).
    baris.tambah (Root.COLUMN_TITLE, getContext().getString(R.string.title));

    // ID dokumen ini tidak boleh tukar sebaik sahaja dikongsi.
    row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir));

     //                                                   kanak-kanak hanya diperkenalkan pada kami jenis 
    //  root pengguna yang mengandungi jenis yang diingini di suatu tempat dalam hierarki fail mereka.
    row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(m Base Dirror)>(mBaseDir) _BYTES, mBaseDir.getFreeSpace ());
    row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);

    return hasil;
}

Pertanyaan pelaksanaan

cursor public QueryChildDocuments (String parentDocumentId, string [] unjuran,
sortorder rentetan) melemparkan fileNotFoundException {
unjuran));
    fail akhir induk = getFileForDocId(parentDocumentId);
    untuk (Fail fail : parent.listFiles()) {
        // Menambahkan nama fail dan memaparkan ME,  nama fail dan MI .
         includeFail(hasil, null, fail);
    }
    hasil pulangan;
}



Laksanakan pertanyaanDokumen

<🎜 @Override QueryDocument Kursor Awam (String DocumentID, String [] Projection) melemparkan

FileNotFoundException { // Buat kursor dengan unjuran yang diminta, atau unjuran lalai.
keputusan MatrixCursor = baharu
            MatrixCursor(resolveDocumentProjection(unjuran));
    includeFile(result, documentId, null);
   ; 
  ;<🎜

Baiklah, itu sahaja dalam dokumen: Pada mulanya saya ingin menterjemahkannya sendiri, tetapi kemudian saya menemui terjemahan bahasa Cina bagi dokumen ini semasa menghabiskan masa dalam talian, jadi saya terlalu malas~

pautan terjemahan bahasa Cina:rangka kerja akses storan android Rangka Kerja Akses Storan


3. Android 4.4 Mendapatkan masalah laluan sumber:

Malah, tempat kami menggunakan SAF ini tidak lebih daripada mendapatkan Uri imej, dan dari Contoh di atas kami juga dapati: Pautan yang kami perolehi adalah seperti ini:

content://com.android.providers.media.documents/document/image%3A69983

Untuk pautan sebegini, kami boleh terus mendapatkan uri melalui kaedah di atas!

Sudah tentu, ini versi 4.4 ke atas~!

Jika ia adalah versi sebelumnya: uri mungkin seperti ini:

content://media/external/images/media/image%3A69983

Berikut ialah penyelesaian komprehensif yang saya lihat di tempat lain, pautan asal: Dalam Android 4.4 Masalah mendapatkan laluan sumber

String statik awam getPath(konteks konteks akhir, Uri akhir uri) {
    boolean akhir isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT>  Do cu CODES.KITKAT>                     akhir if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
         // ExternalStorageProvider   
         jika (isExternalStorageDocument(uri))  =       akhir cumentsContract.getDocumentId(uri);
            String akhir [] split = docId.split(":");
             final String type = split[0];

              jika ("primary". equals Ignore Case (type re ) {                                                 < nvironment.getExternalStorageDirectory () + "/" + split[1];
             }

                    // TODO menangani bukan utama volum  
                                                                                                                                                 lain jika (isDownloadsDocument(uri) ) {

String akhir id = documentsContract.getDocumentId (uri);
uri final contenturi = contenturis.withAppendedId ( uri.parse ("content: // muat turun/public_downloads"), panjang. valueOf(id));

            return getDataColumn(context, contentUri, null, null);
        }
          //  Media Provider     <🎜 uri)) {
            akhir String docId = DocumentsContract.getDocumentId(uri);
            Rentetan akhir[] split = docId.split(":");
             jenis String akhir = split[0];
             Uri kandunganUri = null;<  >                 (                                                 akhir akhir
                 contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            } lain jika ("video".sama dengan(jenis)) {<🎜 >                        Media dia.LUARAN_KANDUNGAN_URI;
            } lain jika ( "audio".sama dengan(jenis)) {
                contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
            }
       = String       =    akhir akhir?          akhir String[] selectionArgs = baharu Rentetan[] {
                    berpecah[1]
            };
            kembali getDataColumn(konteks, kandunganUri,    Arsen pilihanraya,               🎜>    }
    // MediaStore (dan umum)  
    lain jika ("kandungan".equalsIgnoreCase(uri.getScheme())) {
        kembali getDataColumn(konteks, uri, null, null);
                                                              "file".equalsIgnoreCase(uri.getScheme())) {
        return uri.getPath();
    }
    return null;
}

/**<🎜 * Dapatkan nilai lajur data untuk Uri ini. Ini berguna untuk 
 * MediaStore Uris, dan penyedia Kandungan berasaskan fail lain. 
 *
 * @param konteks Konteks . 
 * @param uri Uri untuk pertanyaan. 
 * @param selection (Pilihan) Penapis digunakan dalam pertanyaan. 
 * @param selectionArgs (Pilihan) Argumen Pilihan yang digunakan dalam pertanyaan. 
 * @return Nilai lajur _data , yang biasanya laluan fail. 
 */
String statik awam getDataColumn(Konteks konteks, Uri uri, Pemilihan rentetan,
                                                                                                                                                                 kursor = null;
    lajur String akhir = " _data";
    final String[] unjuran = {
            lajur
    };

    cuba {
                   column       context , selectionArgs ,
                null);
         jika (kursor != null && cursor.moveToFirst()) {
             akhir int column_index. 🎜>            kembali kursor.getString(column_index);< . >
/**
 * @param uri Uri untuk semak. 
 * @return Sama ada pihak Uri adalah ExternalStorageProvider. 
 */
public static boolean isExternalStorageDocument(Uri uri) {
    return "com.android.externalstorage.documents".equals(uri.getAuthority());
}

/**
* @param uri Uri untuk menyemak.
* @kembali Walau apa pun kuasa Uri ialah Penyedia Muat Turun
*/
boolean statik awam ialahDownloadsDocument(Uri uri) {
kembalikan "com.android.providers. downloads.documents".equals(uri.getAuthority());
}

/**
 * @param uri Uri untuk semak. 
 * @return Sama ada pihak berkuasa Uri adalah MediaProvider. 
 */
public static boolean isMediaDocument(Uri uri) {
return "com .android.providers.media.documents".equals(uri.getAuthority());
}

Ringkasan bahagian ini:

Baiklah, tentang perkara ini Itu sahaja untuk Rangka Kerja Akses Storan Android SAF. Kami akan menggunakannya kemudian dan mengkajinya secara mendalam. Ketahui sahaja. Ia akan menjadi lebih mudah untuk mendapatkan laluan fail selepas 4.4~