blob: 30844d7c277e2639b6107ff5b2f3af0ebbd2e2f5 [file] [log] [blame]
page.title=Khuôn kh Truy cp Kho lưu tr
@jd:body
<div id="qv-wrapper">
<div id="qv">
<h2>Trong tài liu này
<a href="#" onclick="hideNestedItems('#toc44',this);return false;" class="header-toggle">
<span class="more">hin nhiu hơn</span>
<span class="less" style="display:none">hin ít hơn</span></a></h2>
<ol id="toc44" class="hide-nested">
<li>
<a href="#overview">Tng quan</a>
</li>
<li>
<a href="#flow">Dòng Điu khin</a>
</li>
<li>
<a href="#client">Ghi mt ng dng Máy khách</a>
<ol>
<li><a href="#search">Tìm kiếm tài liu</a></li>
<li><a href="#process">Kết qu tiến trình</a></li>
<li><a href="#metadata">Kim tra siêu d liu tài liu</a></li>
<li><a href="#open">M mt tài liu</a></li>
<li><a href="#create">To mt tài liu mi</a></li>
<li><a href="#delete">Xóa mt tài liu</a></li>
<li><a href="#edit">Chnh sa mt tài liu</a></li>
<li><a href="#permissions">C định các quyn</a></li>
</ol>
</li>
<li><a href="#custom">Ghi mt Trình cung cp Tài liu Tùy chnh</a>
<ol>
<li><a href="#manifest">Bn kê khai</a></li>
<li><a href="#contract">Hp đồng</a></li>
<li><a href="#subclass">Phân lp con DocumentsProvider</a></li>
<li><a href="#security">Bo mt</a></li>
</ol>
</li>
</ol>
<h2>Lp khóa</h2>
<ol>
<li>{@link android.provider.DocumentsProvider}</li>
<li>{@link android.provider.DocumentsContract}</li>
</ol>
<h2>Video</h2>
<ol>
<li><a href="http://www.youtube.com/watch?v=zxHVeXbK1P4">
DevBytes: Khuôn kh Truy cp Kho lưu tr Android 4.4: Trình cung cp</a></li>
<li><a href="http://www.youtube.com/watch?v=UFj9AEz0DHQ">
DevBytes: Khuôn kh Truy cp Kho lưu tr Android 4.4: Máy khách</a></li>
</ol>
<h2>Mã Ví dụ</h2>
<ol>
<li><a href="{@docRoot}samples/StorageProvider/index.html">
Trình cung cp Lưu trữ</a></li>
<li><a href="{@docRoot}samples/StorageClient/index.html">
StorageClient</a></li>
</ol>
<h2>Xem thêm</h2>
<ol>
<li>
<a href="{@docRoot}guide/topics/providers/content-provider-basics.html">
Ni dung Cơ bn v Trình cung cp Ni dung
</a>
</li>
</ol>
</div>
</div>
<p>Android 4.4 (API mc 19) gii thiu Khuôn kh Truy cp Kho lưu tr (SAF). SAF
giúp người dùng đơn gin hóa vic duyt và m tài liu, hình nh và các tp khác
gia tt c trình cung cp lưu tr tài liu mà h thích. UI tiêu chun, d s dng
cho phép người dùng duyt tp và truy cp hot động gn đây mt cách nht quán gia các ng dng và trình cung cp.</p>
<p>Dch v lưu tr đám mây hoc cc b có th tham gia vào h sinh thái này bng cách trin khai mt
{@link android.provider.DocumentsProvider} để gói gn các dch v ca mình. Nhng ng dng
máy khách cn truy cp vào tài liu ca mt trình cung cp có th tích hp vi SAF ch bng mt vài
dòng mã.</p>
<p>SAF bao gm:</p>
<ul>
<li><strong>Trình cung cp tài liu</strong>&mdash;Mt trình cung cp ni dung cho phép mt
dch v lưu tr (chng hn như Google Drive) phát hin các tp mà nó qun lý. Trình cung cp tài liu được
trin khai thành mt lp con ca lp {@link android.provider.DocumentsProvider}.
Sơ đồ tài liu-trình cung cp s được da trên mt phân cp tp truyn thng,
cho dù cách thc trình cung cp tài liu ca bn trc tiếp lưu tr d liu là hoàn toàn do bn.
Nn tng Android bao gm mt vài trình cung cp tài liu tích hp, chng hn như
Downloads, Images, và Videos.</li>
<li><strong>ng dng máy khách</strong>&mdash;Mt ng dng tùy chnh có chc năng gi ra ý định
{@link android.content.Intent#ACTION_OPEN_DOCUMENT} và/hoặc
{@link android.content.Intent#ACTION_CREATE_DOCUMENT} và nhận các tệp
được tr v bi trình cung cp tài liu.</li>
<li><strong>B chn</strong>&mdash;Mt UI h thng cho phép người dùng truy cp tài liu t tt c
trình cung cp tài liu mà tha mãn các tiêu chí tìm kiếm ca ng dng máy khách.</li>
</ul>
<p>Mt s tính năng được SAF cung cp bao gm:</p>
<ul>
<li>Cho phép người dùng duyt ni dung t tt c trình cung cp tài liu, không ch mt ng dng duy nht.</li>
<li>Giúp ng dng ca bn có th có quyn truy cp lâu dài, c định vào
các tài liu được s hu bi mt trình cung cp tài liu. Thông qua truy cp này, người dùng có th thêm, chnh sa,
lưu và xóa tp trên trình cung cp.</li>
<li>H tr nhiu tài khon người dùng và các phn gc tm thi chng hn như trình cung cp
b nh USB, nó ch xut hin nếu ổ đĩa được cm vào. </li>
</ul>
<h2 id ="overview">Tng quan</h2>
<p>SAF tp trung xoay quanh mt trình cung cp ni dung là mt lp con
ca lp {@link android.provider.DocumentsProvider}. Trong mt <em>trình cung cp tài liu</em>, d liu được
cu trúc thành mt phân cp tp truyn thng:</p>
<p><img src="{@docRoot}images/providers/storage_datamodel.png" alt="data model" /></p>
<p class="img-caption"><strong>Hình 1.</strong> Mô hình d liu ca trình cung cp tài liu. Mt Phn gc ch đến mt Tài liu duy nht,
sau đó nó bt đầu xòe ra toàn b cây.</p>
<p>Lưu ý điu sau đây:</p>
<ul>
<li>Mi mt trình cung cp tài liu s báo cáo mt hoc nhiu
"phần gốc" là đim bt đầu khám phá cây tài liu.
Mi phn gc có mt {@link android.provider.DocumentsContract.Root#COLUMN_ROOT_ID} duy nhất,
và nó tr đến mt tài liu (thư mc)
biu din ni dung bên dưới phn gc đó.
Phn gc có th linh hot theo thiết kế để h tr các trường hp s dng như nhiu tài khon,
thiết b lưu tr USB tm thi, hoc đăng nhp/đăng xut người dùng.</li>
<li>Dưới mi phn gc là mt tài liu đơn lẻ. Tài liu đó s tr ti 1 đến <em>N</em> tài liu,
mi tài liu li có th tr ti 1 đến <em>N</em> tài liệu khác. </li>
<li>Mi b nh ph tr ph b mt
các tp và thư mc riêng l bng cách tham chiếu chúng bng mt
{@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} duy nhất.
ID ca tài liu phi là duy nht và không thay đổi sau khi được phát hành, do chúng được s dng để cp URI
không thay đổi gia các ln khi động li thiết bị.</li>
<li>Tài liu có th là mt tp m được (có mt kiu MIME c thể), hoc mt
thư mc cha các tài liu b sung (có kiu MIME
{@link android.provider.DocumentsContract.Document#MIME_TYPE_DIR}).</li>
<li>Mi tài liu có th có các kh năng khác nhau như được mô t bi
{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS COLUMN_FLAGS}.
Ví dụ, {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE},
{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE}, và
{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_THUMBNAIL}.
{@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} cũng có thể
có trong nhiu thư mc.</li>
</ul>
<h2 id="flow">Dòng Điu khin</h2>
<p>Như nêu trên, mô hình d liu ca trình cung cp tài liu được da trên mt phân cp
tp truyn thng. Tuy nhiên, bn có th thc tế lưu tr d liu ca mình bng bt k cách nào mà mình thích, min
là nó có th được truy cp thông qua API {@link android.provider.DocumentsProvider}. Ví dụ, bn
có th s dng kho lưu tr đám mây da trên tag cho d liu ca mình.</p>
<p>Hình 2 minh ha mt ví d v cách mà mt ng dng nh có th s dng SAF
để truy cp d liu được lưu trữ:</p>
<p><img src="{@docRoot}images/providers/storage_dataflow.png" alt="app" /></p>
<p class="img-caption"><strong>Hình 2.</strong> Dòng Khuôn khổ Truy cập Kho lưu trữ</p>
<p>Lưu ý điu sau đây:</p>
<ul>
<li>Trong SAF, trình cung cp và máy khách không tương tác
trc tiếp vi nhau. Mt máy khách yêu cu quyn để tương tác
vi tp (c th là quyn đọc, chnh sa, to hoc xóa tp).</li>
<li>Tương tác bt đầu khi mt ng dng (trong ví d này này mt ng dng nh) th hin ý định
{@link android.content.Intent#ACTION_OPEN_DOCUMENT} hoặc {@link android.content.Intent#ACTION_CREATE_DOCUMENT}. Ý định có thể bao gồm các bộ lọc
để c th hơn các tiêu chí&mdash;ví dụ, "cấp cho tôi tất cả tệp mở được
có kiểu MIME là 'image'."</li>
<li>Sau khi ý định th hin, b chn ca h thng s đi đến tng trình cung cp được đăng ký
và hin th cho người dùng xem các phn gc ni dung khp vi tiêu chí.</li>
<li>B chn cp cho người dùng mt giao din tiêu chun để truy cp tài liu, mc
dù các trình cung cp tài liu liên quan có th rt khác nhau. Ví dụ, hình 2
minh ha mt trình cung cp Google Drive, mt trình cung cp USB, và mt trình cung cp đám mây.</li>
</ul>
<p>Hình 3 minh ha mt b chn mà trong đó mt người dùng đang tìm kiếm hình nh đã chn mt
tài khon Google Drive:</p>
<p><img src="{@docRoot}images/providers/storage_picker.png" width="340" alt="picker" style="border:2px solid #ddd" /></p>
<p class="img-caption"><strong>Hình 3.</strong> Bộ chọn</p>
<p>Khi người dùng chn Google Drive, hình nh được hin th như minh ha trong
hình 4. T đim đó tr đi, người dùng có th tương tác vi chúng theo bt k cách nào
được h tr bi trình cung cp và ng dng máy khách.
<p><img src="{@docRoot}images/providers/storage_photos.png" width="340" alt="picker" style="border:2px solid #ddd" /></p>
<p class="img-caption"><strong>Hình 4.</strong> Hình ảnh</p>
<h2 id="client">Ghi mt ng dng Máy khách</h2>
<p>Trên phiên bn Android 4.3 và thp hơn, nếu bn mun ng dng ca mình truy xut mt tp t mt ng dng
khác, nó phi gi ra mt ý định chng hn như {@link android.content.Intent#ACTION_PICK}
hay {@link android.content.Intent#ACTION_GET_CONTENT}. Khi đó, người dùng phải chọn
mt ng dng duy nht mà t đó h chn mt tp và ng dng được chn phi cung cp mt
giao din người dùng để người dùng duyt và chn t các tp có sn. </p>
<p>Trên phiên bn Android 4.4 tr lên, bn có thêm mt tùy chn là s dng ý định
{@link android.content.Intent#ACTION_OPEN_DOCUMENT},
nó hin th mt UI b chn được điu khin bi h thng, cho phép người dùng
duyt tt c tp mà các ng dng khác đã cung cp. T UI duy nht này, người dùng
có th chn mt tp t bt k ng dng nào được h trợ.</p>
<p>{@link android.content.Intent#ACTION_OPEN_DOCUMENT} không
nhm mc đích thay thế cho {@link android.content.Intent#ACTION_GET_CONTENT}.
Bn nên s dng cái nào s ph thuc vào nhu cu ca ng dng ca bn:</p>
<ul>
<li>S dng {@link android.content.Intent#ACTION_GET_CONTENT} nếu bạn muốn ứng dụng của mình
ch đơn thun đọc/nhp d liu. Bng cách này, ng dng nhp mt bn sao d liu,
chng hn như mt tp hình nh.</li>
<li>S dng {@link android.content.Intent#ACTION_OPEN_DOCUMENT} nếu bạn muốn ứng dụng
ca mình có quyn truy cp lâu dài, c định vào các tài liu được s hu bi mt
trình cung cp tài liu. Ví d như trường hp mt ng dng chnh sa nh cho phép người dùng chnh sa
các hình nh được lưu tr trong mt trình cung cp tài liu. </li>
</ul>
<p>Phn này mô t cách ghi các ng dng máy khách da trên
{@link android.content.Intent#ACTION_OPEN_DOCUMENT} và
các ý định {@link android.content.Intent#ACTION_CREATE_DOCUMENT}.</p>
<h3 id="search">Tìm kiếm tài liu</h3>
<p>
Đon mã HTML sau s dng {@link android.content.Intent#ACTION_OPEN_DOCUMENT}
để tìm kiếm các trình cung cp tài liu mà
cha tp hình nh:</p>
<pre>private static final int READ_REQUEST_CODE = 42;
...
/**
* Fires an intent to spin up the &quot;file chooser&quot; UI and select an image.
*/
public void performFileSearch() {
// ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
// browser.
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
// Filter to only show results that can be &quot;opened&quot;, such as a
// file (as opposed to a list of contacts or timezones)
intent.addCategory(Intent.CATEGORY_OPENABLE);
// Filter to show only images, using the image MIME data type.
// If one wanted to search for ogg vorbis files, the type would be &quot;audio/ogg&quot;.
// To search for all documents available via installed storage providers,
// it would be &quot;*/*&quot;.
intent.setType(&quot;image/*&quot;);
startActivityForResult(intent, READ_REQUEST_CODE);
}</pre>
<p>Lưu ý điều sau đây:</p>
<ul>
<li>Khi ứng dụng thể hiện ý định {@link android.content.Intent#ACTION_OPEN_DOCUMENT}
, nó sẽ khởi chạy một bộ chọn để hiển thị tất cả trình cung cấp tài liệu khớp với tiêu chí.</li>
<li>Thêm thể loại {@link android.content.Intent#CATEGORY_OPENABLE} vào
ý định sẽ lọc kết quả để chỉ hiển thị những tài liệu có thể mở được, chẳng hạn như tệp hình ảnh.</li>
<li>Câu lệnh {@code intent.setType("image/*")} sẽ lọc thêm để
chỉ hiển thị những tài liệu có kiểu dữ liệu MIME hình ảnh.</li>
</ul>
<h3 id="results">Kết quả Tiến trình</h3>
<p>Sau khi người dùng chọn một tài liệu trong bộ chọn,
{@link android.app.Activity#onActivityResult onActivityResult()} sẽ được gọi.
URI tro tới tài liệu được chọn sẽ nằm trong tham số {@code resultData}
. Trích xuất UI bằng cách sử dụng {@link android.content.Intent#getData getData()}.
Sau khi có nó, bạn có thể sử dụng nó để truy xuất tài liệu mà người dùng muốn. Ví
dụ:</p>
<pre>&#64;Override
public void onActivityResult(int requestCode, int resultCode,
Intent resultData) {
// The ACTION_OPEN_DOCUMENT intent was sent with the request code
// READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
// response to some other intent, and the code below shouldn't run at all.
if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
// The document selected by the user won't be returned in the intent.
// Instead, a URI to that document will be contained in the return intent
// provided to this method as a parameter.
// Pull that URI using resultData.getData().
Uri uri = null;
if (resultData != null) {
uri = resultData.getData();
Log.i(TAG, "Uri: " + uri.toString());
showImage(uri);
}
}
}
</pre>
<h3 id="metadata">Kiểm tra siêu dữ liệu tài liệu</h3>
<p>Sau khi có URI cho một tài liệu, bạn có quyền truy cập siêu dữ liệu của nó. Đoạn mã HTML
này bắt siêu dữ liệu cho một tài liệu được quy định bởi URI, và ghi lại nó:</p>
<pre>public void dumpImageMetaData(Uri uri) {
// The query, since it only applies to a single document, will only return
// one row. There's no need to filter, sort, or select fields, since we want
// all fields for one document.
Cursor cursor = getActivity().getContentResolver()
.query(uri, null, null, null, null, null);
try {
// moveToFirst() returns false if the cursor has 0 rows. Very handy for
// &quot;if there's anything to look at, look at it&quot; conditionals.
if (cursor != null &amp;&amp; cursor.moveToFirst()) {
// Note it's called &quot;Display Name&quot;. This is
// provider-specific, and might not necessarily be the file name.
String displayName = cursor.getString(
cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
Log.i(TAG, &quot;Display Name: &quot; + displayName);
int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
// If the size is unknown, the value stored is null. But since an
// int can't be null in Java, the behavior is implementation-specific,
// which is just a fancy term for &quot;unpredictable&quot;. So as
// a rule, check if it's null before assigning to an int. This will
// happen often: The storage API allows for remote files, whose
// size might not be locally known.
String size = null;
if (!cursor.isNull(sizeIndex)) {
// Technically the column stores an int, but cursor.getString()
// will do the conversion automatically.
size = cursor.getString(sizeIndex);
} else {
size = &quot;Unknown&quot;;
}
Log.i(TAG, &quot;Size: &quot; + size);
}
} finally {
cursor.close();
}
}
</pre>
<h3 id="open-client">Mở một tài liệu</h3>
<p>Sau khi có URI cho một tài liệu, bạn có thể mở nó hoặc làm bất kỳ điều gì
mà bạn muốn.</p>
<h4>Bitmap</h4>
<p>Sau đây là một ví dụ về cách bạn có thể mở một {@link android.graphics.Bitmap}:</p>
<pre>private Bitmap getBitmapFromUri(Uri uri) throws IOException {
ParcelFileDescriptor parcelFileDescriptor =
getContentResolver().openFileDescriptor(uri, "r");
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
parcelFileDescriptor.close();
return image;
}
</pre>
<p>Lưu ý rằng bạn không nên thực hiện thao tác này trên luồng UI. Thực hiện điều này dưới
nền bằng cách sử dụng {@link android.os.AsyncTask}. Sau khi mở bitmap, bạn có thể
hiển thị nó trong một {@link android.widget.ImageView}.
</p>
<h4>Nhận một InputStream</h4>
<p>Sau đây là một ví dụ về cách mà bạn có thể nhận một {@link java.io.InputStream} từ URI. Trong đoạn mã HTML
này, các dòng tệp đang được đọc thành một xâu:</p>
<pre>private String readTextFromUri(Uri uri) throws IOException {
InputStream inputStream = getContentResolver().openInputStream(uri);
BufferedReader reader = new BufferedReader(new InputStreamReader(
inputStream));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
fileInputStream.close();
parcelFileDescriptor.close();
return stringBuilder.toString();
}
</pre>
<h3 id="create">Tạo một tài liệu mới</h3>
<p>Ứng dụng của bạn có thể tạo một tài liệu mới trong một trình cung cấp tài liệu bằng cách sử dụng ý định
{@link android.content.Intent#ACTION_CREATE_DOCUMENT}
. Để tạo một tệp, bạn cấp cho ý định của mình một kiểu MIME và tên tệp, và
khởi chạy nó bằng một mã yêu cầu duy nhất. Phần còn lại sẽ được làm hộ bạn:</p>
<pre>
// Here are some examples of how you might call this method.
// The first parameter is the MIME type, and the second parameter is the name
// of the file you are creating:
//
// createFile("text/plain", "foobar.txt");
// createFile("image/png", "mypicture.png");
// Unique request code.
private static final int WRITE_REQUEST_CODE = 43;
...
private void createFile(String mimeType, String fileName) {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
// Filter to only show results that can be &quot;opened&quot;, such as
// a file (as opposed to a list of contacts or timezones).
intent.addCategory(Intent.CATEGORY_OPENABLE);
// Create a file with the requested MIME type.
intent.setType(mimeType);
intent.putExtra(Intent.EXTRA_TITLE, fileName);
startActivityForResult(intent, WRITE_REQUEST_CODE);
}
</pre>
<p>Sau khi tạo một tài liệu mới, bạn có thể nhận URI của tài liệu trong
{@link android.app.Activity#onActivityResult onActivityResult()}, sao cho bạn
có thể tiếp tục ghi nó.</p>
<h3 id="delete">Xóa một tài liệu</h3>
<p>Nếu bạn có URI cho một tài liệu và
{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS}
của tài liệu chứa
{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE},
bạn có thể xóa tài liệu đó. Ví dụ:</p>
<pre>
DocumentsContract.deleteDocument(getContentResolver(), uri);
</pre>
<h3 id="edit">Chỉnh sửa một tài liệu</h3>
<p>Bạn có thể sử dụng SAF để chỉnh sửa một tài liệu văn bản ngay tại chỗ.
Đoạn mã HTML này thể hiện
ý định {@link android.content.Intent#ACTION_OPEN_DOCUMENT} và sử dụng
thể loại {@link android.content.Intent#CATEGORY_OPENABLE} để chỉ hiển thị
những tài liệu có thể mở được. Nó lọc thêm để chỉ hiển thị những tệp văn bản:</p>
<pre>
private static final int EDIT_REQUEST_CODE = 44;
/**
* Open a file for writing and append some text to it.
*/
private void editDocument() {
// ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's
// file browser.
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
// Filter to only show results that can be &quot;opened&quot;, such as a
// file (as opposed to a list of contacts or timezones).
intent.addCategory(Intent.CATEGORY_OPENABLE);
// Filter to show only text files.
intent.setType(&quot;text/plain&quot;);
startActivityForResult(intent, EDIT_REQUEST_CODE);
}
</pre>
<p>Tiếp theo, t {@link android.app.Activity#onActivityResult onActivityResult()}
(xem <a href="#results">Kết qu tiến trình</a>) bn có th gi mã để thc hin chnh sa.
Đon mã HTML sau nhn được mt {@link java.io.FileOutputStream}
t {@link android.content.ContentResolver}. Theo mc định, nó s dng chế độ ghi”.
Cách tt nht là yêu cu lượng quyn truy cp bn cn mc ít nht, vì thế đừng yêu cu
quyn đọc/ghi nếu bn ch cn quyn ghi:</p>
<pre>private void alterDocument(Uri uri) {
try {
ParcelFileDescriptor pfd = getActivity().getContentResolver().
openFileDescriptor(uri, "w");
FileOutputStream fileOutputStream =
new FileOutputStream(pfd.getFileDescriptor());
fileOutputStream.write(("Overwritten by MyCloud at " +
System.currentTimeMillis() + "\n").getBytes());
// Let the document provider know you're done by closing the stream.
fileOutputStream.close();
pfd.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}</pre>
<h3 id="permissions">C định các quyn</h3>
<p>Khi ng dng ca bn m mt tp để đọc hoc ghi, h thng s cp cho
ng dng ca bn mt quyn URI được cp cho tp đó. Quyn này s kéo dài ti khi thiết b ca bn khi động li.
Nhưng gi s ng dng ca bn là mt ng dng chnh sa hình nh, và bn mun người dùng có th
truy cp 5 hình nh cui cùng mà h đã chnh sa, trc tiếp t ng dng ca bn. Nếu thiết b ca người dùng
đã khi động li, bn s phi gi người dùng tr li b chn h thng để tìm
các tp đó, đây rõ ràng không phi là cách lý tưởng.</p>
<p>Để tránh điu này xy ra, bn có th c định các quyn mà h thng
cp cho ng dng ca bn. ng dng ca bn s "nhận" cp quyn URI có th c định
mà h thng cung cp mt cách hiu quả. Điu này cho phép người dùng có quyn liên tc truy cp các tp đó
thông qua ng dng ca bn, ngay c khi thiết b đã b khi động li:</p>
<pre>final int takeFlags = intent.getFlags()
&amp; (Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Check for the freshest data.
getContentResolver().takePersistableUriPermission(uri, takeFlags);</pre>
<p>Còn mt bước cui cùng. Bn có th đã lưu các
URI gn đây nht mà ng dng ca bn đã truy cp, nhưng chúng còn th không còn hp lệ&mdash;mt ng dng khác
có th đã xóa hoc sa đổi tài liu. Vì thế, bn luôn nên gi
{@code getContentResolver().takePersistableUriPermission()} để kim tra
d liu mi nht.</p>
<h2 id="custom">Ghi mt Trình cung cp Tài liu Tùy chnh</h2>
<p>
Nếu bn đang phát trin mt ng dng cung cp dch v lưu tr cho tp (chng hn như
mt dch v lưu tr đám mây), bn có th cung cp các tp ca mình thông qua
SAF bng cách ghi mt trình cung cp tài liu tùy chnh. Phn này mô t cách làm điu
này.</p>
<h3 id="manifest">Bn kê khai</h3>
<p>Để trin khai mt trình cung cp tài liu tùy chnh, hãy thêm ni dung sau vào bn kê khai
ca ng dng ca bn:</p>
<ul>
<li>Mt mc tiêu API mc 19 hoc cao hơn.</li>
<li>Mt phn t <code>&lt;provider&gt;</code> khai báo trình cung cp lưu tr
tùy chnh ca bn. </li>
<li>Tên ca trình cung cp ca bn, là tên lp ca nó, bao gm tên gói.
Ví dụ: <code>com.example.android.storageprovider.MyCloudProvider</code>.</li>
<li>Tên thm quyn ca bn, tc là tên gói ca bn (trong ví d này là
<code>com.example.android.storageprovider</code>) cng vi kiu ca trình cung cp ni dung
(<code>documents</code>). Ví dụ, {@code com.example.android.storageprovider.documents}.</li>
<li>Thuc tính <code>android:exported</code> được đặt thành <code>&quot;true&quot;</code>.
Bn phi xut trình cung cp ca mình để các ng dng khác có th thy nó.</li>
<li>Thuc tính <code>android:grantUriPermissions</code> được đặt thành
<code>&quot;true&quot;</code>. Thiết đặt này cho phép h thng cp cho các ng dng khác quyn truy cp
vào ni dung trong trình cung cp ca bn. Để tho lun v cách c định quyn được cp cho
mt tài liu c thể, hãy xem phn<a href="#permissions">C định các quyn</a>.</li>
<li>Quyn {@code MANAGE_DOCUMENTS}. Theo mc định, mt trình cung cp s có sn
đối vi mi người. Vic thêm quyn này s hn chế trình cung cp ca bn vào h thng.
Hn chế này có ý nghĩa quan trng đối vi vn đề bo mt.</li>
<li>Thuc tính {@code android:enabled} được đặt thành mt giá tr boolean được định nghĩa trong mt tp
tài nguyên. Mc đích ca thuc tính này là để vô hiu hóa trình cung cp trên các thiết b chy phiên bn Android 4.3 hoc thp hơn.
Ví dụ, {@code android:enabled="@bool/atLeastKitKat"}. Bên
cnh vic nêu thuc tính này trong bn kê khai, bn cn làm như sau:
<ul>
<li>Trong tp tài nguyên {@code bool.xml} ca bn bên dưới {@code res/values/}, hãy thêm
dòng sau: <pre>&lt;bool name=&quot;atLeastKitKat&quot;&gt;false&lt;/bool&gt;</pre></li>
<li>Trong tp tài nguyên {@code bool.xml} ca bn bên dưới {@code res/values-v19/}, hãy thêm
dòng sau: <pre>&lt;bool name=&quot;atLeastKitKat&quot;&gt;true&lt;/bool&gt;</pre></li>
</ul></li>
<li>Mt b lc ý định cha hành động
{@code android.content.action.DOCUMENTS_PROVIDER}, sao cho trình cung cp ca bn
xut hin trong b chn khi h thng tìm kiếm trình cung cp.</li>
</ul>
<p>Sau đây là các đon trích t mt bn kê khai mu cha mt trình cung cp:</p>
<pre>&lt;manifest... &gt;
...
&lt;uses-sdk
android:minSdkVersion=&quot;19&quot;
android:targetSdkVersion=&quot;19&quot; /&gt;
....
&lt;provider
android:name=&quot;com.example.android.storageprovider.MyCloudProvider&quot;
android:authorities=&quot;com.example.android.storageprovider.documents&quot;
android:grantUriPermissions=&quot;true&quot;
android:exported=&quot;true&quot;
android:permission=&quot;android.permission.MANAGE_DOCUMENTS&quot;
android:enabled=&quot;&#64;bool/atLeastKitKat&quot;&gt;
&lt;intent-filter&gt;
&lt;action android:name=&quot;android.content.action.DOCUMENTS_PROVIDER&quot; /&gt;
&lt;/intent-filter&gt;
&lt;/provider&gt;
&lt;/application&gt;
&lt;/manifest&gt;</pre>
<h4 id="43">H tr các thiết b chy phiên bn Android 4.3 và thp hơn</h4>
<p>Ý định
{@link android.content.Intent#ACTION_OPEN_DOCUMENT} chỉ có sẵn
trên các thiết b chy phiên bn Android 4.4 tr lên.
Nếu bn mun ng dng ca mình h tr {@link android.content.Intent#ACTION_GET_CONTENT}
để to điu kin cho các thiết b đang chy phiên bn Android 4.3 và thp hơn, bn nên
vô hiu hóa b lc ý định {@link android.content.Intent#ACTION_GET_CONTENT} trong
bn kê khai ca bn cho các thiết b chy phiên bn Android 4.4 tr lên. Mt
trình cung cp tài liu và {@link android.content.Intent#ACTION_GET_CONTENT} nên được xem xét
loi tr ln nhau. Nếu bn h tr c hai đồng thi, ng dng ca bn s
xut hin hai ln trong UI ca b chn h thng, đưa ra hai cách khác nhau để truy cp
d liu đã lưu ca bn. Điu này có th khiến người dùng b nhm ln.</p>
<p>Sau đây là cách được khuyến cáo để vô hiu hóa b lc ý định
{@link android.content.Intent#ACTION_GET_CONTENT} đối với các thiết bị
chy phiên bn Android 4.4 hoc cao hơn:</p>
<ol>
<li>Trong tp tài nguyên {@code bool.xml} ca bn bên dưới {@code res/values/}, hãy thêm
dòng sau: <pre>&lt;bool name=&quot;atMostJellyBeanMR2&quot;&gt;true&lt;/bool&gt;</pre></li>
<li>Trong tp tài nguyên {@code bool.xml} ca bn bên dưới {@code res/values-v19/}, hãy thêm
dòng sau: <pre>&lt;bool name=&quot;atMostJellyBeanMR2&quot;&gt;false&lt;/bool&gt;</pre></li>
<li>Thêm mt
<a href="{@docRoot}guide/topics/manifest/activity-alias-element.html">bí danh
hot động</a> để vô hiu hóa b lc ý định {@link android.content.Intent#ACTION_GET_CONTENT}
đối vi các phiên bn 4.4 (API mc 19) tr lên. Ví dụ:
<pre>
&lt;!-- This activity alias is added so that GET_CONTENT intent-filter
can be disabled for builds on API level 19 and higher. --&gt;
&lt;activity-alias android:name=&quot;com.android.example.app.MyPicker&quot;
android:targetActivity=&quot;com.android.example.app.MyActivity&quot;
...
android:enabled=&quot;@bool/atMostJellyBeanMR2&quot;&gt;
&lt;intent-filter&gt;
&lt;action android:name=&quot;android.intent.action.GET_CONTENT&quot; /&gt;
&lt;category android:name=&quot;android.intent.category.OPENABLE&quot; /&gt;
&lt;category android:name=&quot;android.intent.category.DEFAULT&quot; /&gt;
&lt;data android:mimeType=&quot;image/*&quot; /&gt;
&lt;data android:mimeType=&quot;video/*&quot; /&gt;
&lt;/intent-filter&gt;
&lt;/activity-alias&gt;
</pre>
</li>
</ol>
<h3 id="contract">Hợp đồng</h3>
<p>Thường khi bạn ghi một trình cung cấp nội dung tùy chỉnh, một trong những tác vụ đó là
triển khai các lớp hợp đồng như được mô tả trong hướng dẫn cho nhà phát triển
<a href="{@docRoot}guide/topics/providers/content-provider-creating.html#ContractClass">
Trình cung cấp Nội dung</a>. Lớp hợp đồng là một lớp {@code public final} mà
chứa các định nghĩa hằng số cho URI, tên cột, kiểu MIME và
siêu dữ liệu khác liên quan tới trình cung cấp. SAF
cung cấp những lớp hợp đồng này cho bạn, vì thế bạn không cần tự
ghi:</p>
<ul>
<li>{@link android.provider.DocumentsContract.Document}</li>
<li>{@link android.provider.DocumentsContract.Root}</li>
</ul>
<p>Ví dụ, sau đây là các cột bạn có thể trả về trong một con chạy khi
trình cung cấp tài liệu của bạn được truy vấn về tài liệu hoặc phần gốc:</p>
<pre>private static final String[] DEFAULT_ROOT_PROJECTION =
new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES,
Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
Root.COLUMN_AVAILABLE_BYTES,};
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new
String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
Document.COLUMN_FLAGS, Document.COLUMN_SIZE,};
</pre>
<h3 id="subclass">Phân lớp con DocumentsProvider</h3>
<p>Bước tiếp theo trong khi ghi một trình cung cấp tài liệu tùy chỉnh đó là phân lớp con
cho lớp tóm tắt {@link android.provider.DocumentsProvider}. Tối thiểu, bạn cần triển khai
các phương pháp sau:</p>
<ul>
<li>{@link android.provider.DocumentsProvider#queryRoots queryRoots()}</li>
<li>{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}</li>
<li>{@link android.provider.DocumentsProvider#queryDocument queryDocument()}</li>
<li>{@link android.provider.DocumentsProvider#openDocument openDocument()}</li>
</ul>
<p>Đây là những phương pháp duy nhất mà bạn được yêu cầu phải triển khai, nhưng còn
nhiều phương pháp nữa mà bạn có thể muốn triển khai. Xem {@link android.provider.DocumentsProvider}
để biết chi tiết.</p>
<h4 id="queryRoots">Triển khai queryRoots</h4>
<p>Việc bạn triển khai {@link android.provider.DocumentsProvider#queryRoots
queryRoots()} phải trả về một {@link android.database.Cursor} trỏ về tất cả
thư mục gốc trong trình cung cấp tài liệu của bạn, bằng cách sử dụng các cột được định nghĩa trong
{@link android.provider.DocumentsContract.Root}.</p>
<p>Trong đoạn mã HTML sau, tham số {@code projection} biểu diễn các trường cụ thể
mà hàm gọi muốn nhận về. Đoạn mã HTML tạo một con chạy mới
và thêm một hàng vào nó&mdash;một thư mục gốc, mức cao nhất, như
Downloads hoặc Images. Hầu hết các trình cung cấp chỉ có một phần gốc. Bạn có thể có nhiều hơn một,
ví dụ, trong trường hợp nhiều tài khoản người dùng. Trong trường hợp đó, chỉ cần thêm một
hàng thứ hai vào con chạy.</p>
<pre>
&#64;Override
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
// Create a cursor with either the requested fields, or the default
// projection if "projection" is null.
final MatrixCursor result =
new MatrixCursor(resolveRootProjection(projection));
// If user is not logged in, return an empty root cursor. This removes our
// provider from the list entirely.
if (!isUserLoggedIn()) {
return result;
}
// It's possible to have multiple roots (e.g. for multiple accounts in the
// same app) -- just add multiple cursor rows.
// Construct one row for a root called &quot;MyCloud&quot;.
final MatrixCursor.RowBuilder row = result.newRow();
row.add(Root.COLUMN_ROOT_ID, ROOT);
row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary));
// FLAG_SUPPORTS_CREATE means at least one directory under the root supports
// creating documents. FLAG_SUPPORTS_RECENTS means your application's most
// recently used documents will show up in the &quot;Recents&quot; category.
// FLAG_SUPPORTS_SEARCH allows users to search all documents the application
// shares.
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE |
Root.FLAG_SUPPORTS_RECENTS |
Root.FLAG_SUPPORTS_SEARCH);
// COLUMN_TITLE is the root title (e.g. Gallery, Drive).
row.add(Root.COLUMN_TITLE, getContext().getString(R.string.title));
// This document id cannot change once it's shared.
row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir));
// The child MIME types are used to filter the roots and only present to the
// user roots that contain the desired type somewhere in their file hierarchy.
row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir));
row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace());
row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
return result;
}</pre>
<h4 id="queryChildDocuments">Triển khai queryChildDocuments</h4>
<p>Việc bạn triển khai
{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}
phải trả về một {@link android.database.Cursor} mà chỉ đến tất cả tệp trong
thư mục được chỉ định, bằng cách sử dụng các cột được định nghĩa trong
{@link android.provider.DocumentsContract.Document}.</p>
<p>Phương pháp này được gọi khi bạn chọn một thư mục gốc ứng dụng trong UI bộ chọn.
Nó nhận được tài liệu con của một thư mục nằm dưới phần gốc. Nó có thể được gọi ở bất kỳ mức nào trong phân cấp tệp
, không chỉ phần gốc. Đoạn mã HTML
này tạo một con chạy mới bằng các cột được yêu cầu, sau đó thêm thông tin về
mọi tệp con trực tiếp trong thư mục mẹ vào con chạy.
Tệp con có thể là một hình ảnh, một thư mục khác&mdash;bất kỳ tệp nào:</p>
<pre>&#64;Override
public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
String sortOrder) throws FileNotFoundException {
final MatrixCursor result = new
MatrixCursor(resolveDocumentProjection(projection));
final File parent = getFileForDocId(parentDocumentId);
for (File file : parent.listFiles()) {
// Adds the file's display name, MIME type, size, and so on.
includeFile(result, null, file);
}
return result;
}
</pre>
<h4 id="queryDocument">Triển khai queryDocument</h4>
<p>Việc bạn triển khai
{@link android.provider.DocumentsProvider#queryDocument queryDocument()}
phải trả về một {@link android.database.Cursor} mà chỉ đến tệp được chỉ định,
bằng cách sử dụng các cột được định nghĩa trong {@link android.provider.DocumentsContract.Document}.
</p>
<p>Phương pháp {@link android.provider.DocumentsProvider#queryDocument queryDocument()}
trả về cùng thông tin đã được chuyển trong
{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()},
nhưng là đối với một tệp cụ thể:</p>
<pre>&#64;Override
public Cursor queryDocument(String documentId, String[] projection) throws
FileNotFoundException {
// Create a cursor with the requested projection, or the default projection.
final MatrixCursor result = new
MatrixCursor(resolveDocumentProjection(projection));
includeFile(result, documentId, null);
return result;
}
</pre>
<h4 id="openDocument">Triển khai openDocument</h4>
<p>Bạn phải triển khai {@link android.provider.DocumentsProvider#openDocument
openDocument()} để trả về một {@link android.os.ParcelFileDescriptor} biểu diễn
tệp được chỉ định. Các ứng dụng khác có thể sử dụng {@link android.os.ParcelFileDescriptor}
được trả về để truyền phát dữ liệu. Hệ thống gọi phương pháp này sau khi người dùng chọn một tệp
và ứng dụng máy khách yêu cầu truy cập nó bằng cách gọi
{@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()}.
Ví dụ:</p>
<pre>&#64;Override
public ParcelFileDescriptor openDocument(final String documentId,
final String mode,
CancellationSignal signal) throws
FileNotFoundException {
Log.v(TAG, &quot;openDocument, mode: &quot; + mode);
// It's OK to do network operations in this method to download the document,
// as long as you periodically check the CancellationSignal. If you have an
// extremely large file to transfer from the network, a better solution may
// be pipes or sockets (see ParcelFileDescriptor for helper methods).
final File file = getFileForDocId(documentId);
final boolean isWrite = (mode.indexOf('w') != -1);
if(isWrite) {
// Attach a close listener if the document is opened in write mode.
try {
Handler handler = new Handler(getContext().getMainLooper());
return ParcelFileDescriptor.open(file, accessMode, handler,
new ParcelFileDescriptor.OnCloseListener() {
&#64;Override
public void onClose(IOException e) {
// Update the file with the cloud server. The client is done
// writing.
Log.i(TAG, &quot;A file with id &quot; +
documentId + &quot; has been closed!
Time to &quot; +
&quot;update the server.&quot;);
}
});
} catch (IOException e) {
throw new FileNotFoundException(&quot;Failed to open document with id &quot;
+ documentId + &quot; and mode &quot; + mode);
}
} else {
return ParcelFileDescriptor.open(file, accessMode);
}
}
</pre>
<h3 id="security">Bảo mật</h3>
<p>Giả sử trình cung cấp tài liệu của bạn là một dịch vụ lưu trữ đám mây được bảo vệ bằng mật khẩu
và bạn muốn đảm bảo rằng người dùng được đăng nhập trước khi bạn bắt đầu chia sẻ tệp của họ.
Ứng dụng của bạn nên làm gì nếu người dùng không đăng nhập? Giải pháp là trả về
phần gốc 0 trong triển khai {@link android.provider.DocumentsProvider#queryRoots
queryRoots()} của bạn. Cụ thể là một con chạy gốc trống:</p>
<pre>
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
...
// If user is not logged in, return an empty root cursor. This removes our
// provider from the list entirely.
if (!isUserLoggedIn()) {
return result;
}
</pre>
<p>Bước còn lại là gọi {@code getContentResolver().notifyChange()}.
Bạn còn nhớ {@link android.provider.DocumentsContract} chứ? Chúng ta đang sử dụng nó để tạo
URI này. Đoạn mã HTML sau báo cho hệ thống truy vấn các phần gốc trong
trình cung cấp tài liệu của bạn bất cứ khi nào trạng thái đăng nhập của người dùng thay đổi. Nếu người dùng không được
đăng nhập, lệnh gọi tới {@link android.provider.DocumentsProvider#queryRoots queryRoots()} sẽ trả về một
con chạy trống như minh họa bên trên. Điều này đảm bảo rằng tài liệu của một trình cung cấp chỉ
có sẵn nếu người dùng đăng nhập vào trình cung cấp đó.</p>
<pre>private void onLoginButtonClick() {
loginOrLogout();
getContentResolver().notifyChange(DocumentsContract
.buildRootsUri(AUTHORITY), null);
}
</pre>