1 page.title=Khun kh Truy cp Kho lu tr 2 @jd:body 3 <div id="qv-wrapper"> 4 <div id="qv"> 5 6 <h2>Trong ti liu ny 7 <a href="#" onclick="hideNestedItems('#toc44',this);return false;" class="header-toggle"> 8 <span class="more">hin nhiu hn</span> 9 <span class="less" style="display:none">hin t hn</span></a></h2> 10 <ol id="toc44" class="hide-nested"> 11 <li> 12 <a href="#overview">Tng quan</a> 13 </li> 14 <li> 15 <a href="#flow">Dng iu khin</a> 16 </li> 17 <li> 18 <a href="#client">Ghi mt ng dng My khch</a> 19 <ol> 20 <li><a href="#search">Tm kim ti liu</a></li> 21 <li><a href="#process">Kt qu tin trnh</a></li> 22 <li><a href="#metadata">Kim tra siu d liu ti liu</a></li> 23 <li><a href="#open">M mt ti liu</a></li> 24 <li><a href="#create">To mt ti liu mi</a></li> 25 <li><a href="#delete">Xa mt ti liu</a></li> 26 <li><a href="#edit">Chnh sa mt ti liu</a></li> 27 <li><a href="#permissions">C nh cc quyn</a></li> 28 </ol> 29 </li> 30 <li><a href="#custom">Ghi mt Trnh cung cp Ti liu Ty chnh</a> 31 <ol> 32 <li><a href="#manifest">Bn k khai</a></li> 33 <li><a href="#contract">Hp ng</a></li> 34 <li><a href="#subclass">Phn lp con DocumentsProvider</a></li> 35 <li><a href="#security">Bo mt</a></li> 36 </ol> 37 </li> 38 39 </ol> 40 <h2>Lp kha</h2> 41 <ol> 42 <li>{@link android.provider.DocumentsProvider}</li> 43 <li>{@link android.provider.DocumentsContract}</li> 44 </ol> 45 46 <h2>Video</h2> 47 48 <ol> 49 <li><a href="http://www.youtube.com/watch?v=zxHVeXbK1P4"> 50 DevBytes: Khun kh Truy cp Kho lu tr Android 4.4: Trnh cung cp</a></li> 51 <li><a href="http://www.youtube.com/watch?v=UFj9AEz0DHQ"> 52 DevBytes: Khun kh Truy cp Kho lu tr Android 4.4: My khch</a></li> 53 </ol> 54 55 56 <h2>M V d</h2> 57 58 <ol> 59 <li><a href="{@docRoot}samples/StorageProvider/index.html"> 60 Trnh cung cp Lu tr</a></li> 61 <li><a href="{@docRoot}samples/StorageClient/index.html"> 62 StorageClient</a></li> 63 </ol> 64 65 <h2>Xem thm</h2> 66 <ol> 67 <li> 68 <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> 69 Ni dung C bn v Trnh cung cp Ni dung 70 </a> 71 </li> 72 </ol> 73 74 </div> 75 </div> 76 77 78 <p>Android 4.4 (API mc 19) gii thiu Khun kh Truy cp Kho lu tr (SAF). SAF 79 gip ngi dng n gin ha vic duyt v m ti liu, hnh nh v cc tp khc 80 gia tt c trnh cung cp lu tr ti liu m h thch. UI tiu chun, d s dng 81 cho php ngi dng duyt tp v truy cp hot ng gn y mt cch nht qun gia cc ng dng v trnh cung cp.</p> 82 83 <p>Dch v lu tr m my hoc cc b c th tham gia vo h sinh thi ny bng cch trin khai mt 84 {@link android.provider.DocumentsProvider} gi gn cc dch v ca mnh. Nhng ng dng 85 my khch cn truy cp vo ti liu ca mt trnh cung cp c th tch hp vi SAF ch bng mt vi 86 dng m.</p> 87 88 <p>SAF bao gm:</p> 89 90 <ul> 91 <li><strong>Trnh cung cp ti liu</strong>—Mt trnh cung cp ni dung cho php mt 92 dch v lu tr (chng hn nh Google Drive) pht hin cc tp m n qun l. Trnh cung cp ti liu c 93 trin khai thnh mt lp con ca lp {@link android.provider.DocumentsProvider}. 94 S ti liu-trnh cung cp s c da trn mt phn cp tp truyn thng, 95 cho d cch thc trnh cung cp ti liu ca bn trc tip lu tr d liu l hon ton do bn. 96 Nn tng Android bao gm mt vi trnh cung cp ti liu tch hp, chng hn nh 97 Downloads, Images, v Videos.</li> 98 99 <li><strong>ng dng my khch</strong>—Mt ng dng ty chnh c chc nng gi ra nh 100 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} v/hoc 101 {@link android.content.Intent#ACTION_CREATE_DOCUMENT} v nhn cc tp 102 c tr v bi trnh cung cp ti liu.</li> 103 104 <li><strong>B chn</strong>—Mt UI h thng cho php ngi dng truy cp ti liu t tt c 105 trnh cung cp ti liu m tha mn cc tiu ch tm kim ca ng dng my khch.</li> 106 </ul> 107 108 <p>Mt s tnh nng c SAF cung cp bao gm:</p> 109 <ul> 110 <li>Cho php ngi dng duyt ni dung t tt c trnh cung cp ti liu, khng ch mt ng dng duy nht.</li> 111 <li>Gip ng dng ca bn c th c quyn truy cp lu di, c nh vo 112 cc ti liu c s hu bi mt trnh cung cp ti liu. Thng qua truy cp ny, ngi dng c th thm, chnh sa, 113 lu v xa tp trn trnh cung cp.</li> 114 <li>H tr nhiu ti khon ngi dng v cc phn gc tm thi chng hn nh trnh cung cp 115 b nh USB, n ch xut hin nu a c cm vo. </li> 116 </ul> 117 118 <h2 id ="overview">Tng quan</h2> 119 120 <p>SAF tp trung xoay quanh mt trnh cung cp ni dung l mt lp con 121 ca lp {@link android.provider.DocumentsProvider}. Trong mt <em>trnh cung cp ti liu</em>, d liu c 122 cu trc thnh mt phn cp tp truyn thng:</p> 123 <p><img src="{@docRoot}images/providers/storage_datamodel.png" alt="data model" /></p> 124 <p class="img-caption"><strong>Hnh 1.</strong> M hnh d liu ca trnh cung cp ti liu. Mt Phn gc ch n mt Ti liu duy nht, 125 sau n bt u xe ra ton b cy.</p> 126 127 <p>Lu iu sau y:</p> 128 <ul> 129 130 <li>Mi mt trnh cung cp ti liu s bo co mt hoc nhiu 131 "phn gc" l im bt u khm ph cy ti liu. 132 Mi phn gc c mt {@link android.provider.DocumentsContract.Root#COLUMN_ROOT_ID} duy nht, 133 v n tr n mt ti liu (th mc) 134 biu din ni dung bn di phn gc . 135 Phn gc c th linh hot theo thit k h tr cc trng hp s dng nh nhiu ti khon, 136 thit b lu tr USB tm thi, hoc ng nhp/ng xut ngi dng.</li> 137 138 <li>Di mi phn gc l mt ti liu n l. Ti liu s tr ti 1 n <em>N</em> ti liu, 139 mi ti liu li c th tr ti 1 n <em>N</em> ti liu khc. </li> 140 141 <li>Mi b nh ph tr ph b mt 142 cc tp v th mc ring l bng cch tham chiu chng bng mt 143 {@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} duy nht. 144 ID ca ti liu phi l duy nht v khng thay i sau khi c pht hnh, do chng c s dng cp URI 145 khng thay i gia cc ln khi ng li thit b.</li> 146 147 148 <li>Ti liu c th l mt tp m c (c mt kiu MIME c th), hoc mt 149 th mc cha cc ti liu b sung (c kiu MIME 150 {@link android.provider.DocumentsContract.Document#MIME_TYPE_DIR}).</li> 151 152 <li>Mi ti liu c th c cc kh nng khc nhau nh c m t bi 153 {@link android.provider.DocumentsContract.Document#COLUMN_FLAGS COLUMN_FLAGS}. 154 V d, {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE}, 155 {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE}, v 156 {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_THUMBNAIL}. 157 {@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} cng c th 158 c trong nhiu th mc.</li> 159 </ul> 160 161 <h2 id="flow">Dng iu khin</h2> 162 <p>Nh nu trn, m hnh d liu ca trnh cung cp ti liu c da trn mt phn cp 163 tp truyn thng. Tuy nhin, bn c th thc t lu tr d liu ca mnh bng bt k cch no m mnh thch, min 164 l n c th c truy cp thng qua API {@link android.provider.DocumentsProvider}. V d, bn 165 c th s dng kho lu tr m my da trn tag cho d liu ca mnh.</p> 166 167 <p>Hnh 2 minh ha mt v d v cch m mt ng dng nh c th s dng SAF 168 truy cp d liu c lu tr:</p> 169 <p><img src="{@docRoot}images/providers/storage_dataflow.png" alt="app" /></p> 170 171 <p class="img-caption"><strong>Hnh 2.</strong> Dng Khun kh Truy cp Kho lu tr</p> 172 173 <p>Lu iu sau y:</p> 174 <ul> 175 176 <li>Trong SAF, trnh cung cp v my khch khng tng tc 177 trc tip vi nhau. Mt my khch yu cu quyn tng tc 178 vi tp (c th l quyn c, chnh sa, to hoc xa tp).</li> 179 180 <li>Tng tc bt u khi mt ng dng (trong v d ny ny mt ng dng nh) th hin nh 181 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} hoc {@link android.content.Intent#ACTION_CREATE_DOCUMENT}. nh c th bao gm cc b lc 182 c th hn cc tiu ch—v d, "cp cho ti tt c tp m c 183 c kiu MIME l 'image'."</li> 184 185 <li>Sau khi nh th hin, b chn ca h thng s i n tng trnh cung cp c ng k 186 v hin th cho ngi dng xem cc phn gc ni dung khp vi tiu ch.</li> 187 188 <li>B chn cp cho ngi dng mt giao din tiu chun truy cp ti liu, mc 189 d cc trnh cung cp ti liu lin quan c th rt khc nhau. V d, hnh 2 190 minh ha mt trnh cung cp Google Drive, mt trnh cung cp USB, v mt trnh cung cp m my.</li> 191 </ul> 192 193 <p>Hnh 3 minh ha mt b chn m trong mt ngi dng ang tm kim hnh nh chn mt 194 ti khon Google Drive:</p> 195 196 <p><img src="{@docRoot}images/providers/storage_picker.png" width="340" alt="picker" style="border:2px solid #ddd" /></p> 197 198 <p class="img-caption"><strong>Hnh 3.</strong> B chn</p> 199 200 <p>Khi ngi dng chn Google Drive, hnh nh c hin th nh minh ha trong 201 hnh 4. T im tr i, ngi dng c th tng tc vi chng theo bt k cch no 202 c h tr bi trnh cung cp v ng dng my khch. 203 204 <p><img src="{@docRoot}images/providers/storage_photos.png" width="340" alt="picker" style="border:2px solid #ddd" /></p> 205 206 <p class="img-caption"><strong>Hnh 4.</strong> Hnh nh</p> 207 208 <h2 id="client">Ghi mt ng dng My khch</h2> 209 210 <p>Trn phin bn Android 4.3 v thp hn, nu bn mun ng dng ca mnh truy xut mt tp t mt ng dng 211 khc, n phi gi ra mt nh chng hn nh {@link android.content.Intent#ACTION_PICK} 212 hay {@link android.content.Intent#ACTION_GET_CONTENT}. Khi , ngi dng phi chn 213 mt ng dng duy nht m t h chn mt tp v ng dng c chn phi cung cp mt 214 giao din ngi dng ngi dng duyt v chn t cc tp c sn. </p> 215 216 <p>Trn phin bn Android 4.4 tr ln, bn c thm mt ty chn l s dng nh 217 {@link android.content.Intent#ACTION_OPEN_DOCUMENT}, 218 n hin th mt UI b chn c iu khin bi h thng, cho php ngi dng 219 duyt tt c tp m cc ng dng khc cung cp. T UI duy nht ny, ngi dng 220 c th chn mt tp t bt k ng dng no c h tr.</p> 221 222 <p>{@link android.content.Intent#ACTION_OPEN_DOCUMENT} khng 223 nhm mc ch thay th cho {@link android.content.Intent#ACTION_GET_CONTENT}. 224 Bn nn s dng ci no s ph thuc vo nhu cu ca ng dng ca bn:</p> 225 226 <ul> 227 <li>S dng {@link android.content.Intent#ACTION_GET_CONTENT} nu bn mun ng dng ca mnh 228 ch n thun c/nhp d liu. Bng cch ny, ng dng nhp mt bn sao d liu, 229 chng hn nh mt tp hnh nh.</li> 230 231 <li>S dng {@link android.content.Intent#ACTION_OPEN_DOCUMENT} nu bn mun ng dng 232 ca mnh c quyn truy cp lu di, c nh vo cc ti liu c s hu bi mt 233 trnh cung cp ti liu. V d nh trng hp mt ng dng chnh sa nh cho php ngi dng chnh sa 234 cc hnh nh c lu tr trong mt trnh cung cp ti liu. </li> 235 236 </ul> 237 238 239 <p>Phn ny m t cch ghi cc ng dng my khch da trn 240 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} v 241 cc nh {@link android.content.Intent#ACTION_CREATE_DOCUMENT}.</p> 242 243 244 <h3 id="search">Tm kim ti liu</h3> 245 246 <p> 247 on m HTML sau s dng {@link android.content.Intent#ACTION_OPEN_DOCUMENT} 248 tm kim cc trnh cung cp ti liu m 249 cha tp hnh nh:</p> 250 251 <pre>private static final int READ_REQUEST_CODE = 42; 252 ... 253 /** 254 * Fires an intent to spin up the "file chooser" UI and select an image. 255 */ 256 public void performFileSearch() { 257 258 // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file 259 // browser. 260 Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); 261 262 // Filter to only show results that can be "opened", such as a 263 // file (as opposed to a list of contacts or timezones) 264 intent.addCategory(Intent.CATEGORY_OPENABLE); 265 266 // Filter to show only images, using the image MIME data type. 267 // If one wanted to search for ogg vorbis files, the type would be "audio/ogg". 268 // To search for all documents available via installed storage providers, 269 // it would be "*/*". 270 intent.setType("image/*"); 271 272 startActivityForResult(intent, READ_REQUEST_CODE); 273 }</pre> 274 275 <p>Lu iu sau y:</p> 276 <ul> 277 <li>Khi ng dng th hin nh {@link android.content.Intent#ACTION_OPEN_DOCUMENT} 278 , n s khi chy mt b chn hin th tt c trnh cung cp ti liu khp vi tiu ch.</li> 279 280 <li>Thm th loi {@link android.content.Intent#CATEGORY_OPENABLE} vo 281 nh s lc kt qu ch hin th nhng ti liu c th m c, chng hn nh tp hnh nh.</li> 282 283 <li>Cu lnh {@code intent.setType("image/*")} s lc thm 284 ch hin th nhng ti liu c kiu d liu MIME hnh nh.</li> 285 </ul> 286 287 <h3 id="results">Kt qu Tin trnh</h3> 288 289 <p>Sau khi ngi dng chn mt ti liu trong b chn, 290 {@link android.app.Activity#onActivityResult onActivityResult()} s c gi. 291 URI tro ti ti liu c chn s nm trong tham s {@code resultData} 292 . Trch xut UI bng cch s dng {@link android.content.Intent#getData getData()}. 293 Sau khi c n, bn c th s dng n truy xut ti liu m ngi dng mun. V 294 d:</p> 295 296 <pre>@Override 297 public void onActivityResult(int requestCode, int resultCode, 298 Intent resultData) { 299 300 // The ACTION_OPEN_DOCUMENT intent was sent with the request code 301 // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the 302 // response to some other intent, and the code below shouldn't run at all. 303 304 if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) { 305 // The document selected by the user won't be returned in the intent. 306 // Instead, a URI to that document will be contained in the return intent 307 // provided to this method as a parameter. 308 // Pull that URI using resultData.getData(). 309 Uri uri = null; 310 if (resultData != null) { 311 uri = resultData.getData(); 312 Log.i(TAG, "Uri: " + uri.toString()); 313 showImage(uri); 314 } 315 } 316 } 317 </pre> 318 319 <h3 id="metadata">Kim tra siu d liu ti liu</h3> 320 321 <p>Sau khi c URI cho mt ti liu, bn c quyn truy cp siu d liu ca n. on m HTML 322 ny bt siu d liu cho mt ti liu c quy nh bi URI, v ghi li n:</p> 323 324 <pre>public void dumpImageMetaData(Uri uri) { 325 326 // The query, since it only applies to a single document, will only return 327 // one row. There's no need to filter, sort, or select fields, since we want 328 // all fields for one document. 329 Cursor cursor = getActivity().getContentResolver() 330 .query(uri, null, null, null, null, null); 331 332 try { 333 // moveToFirst() returns false if the cursor has 0 rows. Very handy for 334 // "if there's anything to look at, look at it" conditionals. 335 if (cursor != null && cursor.moveToFirst()) { 336 337 // Note it's called "Display Name". This is 338 // provider-specific, and might not necessarily be the file name. 339 String displayName = cursor.getString( 340 cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); 341 Log.i(TAG, "Display Name: " + displayName); 342 343 int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE); 344 // If the size is unknown, the value stored is null. But since an 345 // int can't be null in Java, the behavior is implementation-specific, 346 // which is just a fancy term for "unpredictable". So as 347 // a rule, check if it's null before assigning to an int. This will 348 // happen often: The storage API allows for remote files, whose 349 // size might not be locally known. 350 String size = null; 351 if (!cursor.isNull(sizeIndex)) { 352 // Technically the column stores an int, but cursor.getString() 353 // will do the conversion automatically. 354 size = cursor.getString(sizeIndex); 355 } else { 356 size = "Unknown"; 357 } 358 Log.i(TAG, "Size: " + size); 359 } 360 } finally { 361 cursor.close(); 362 } 363 } 364 </pre> 365 366 <h3 id="open-client">M mt ti liu</h3> 367 368 <p>Sau khi c URI cho mt ti liu, bn c th m n hoc lm bt k iu g 369 m bn mun.</p> 370 371 <h4>Bitmap</h4> 372 373 <p>Sau y l mt v d v cch bn c th m mt {@link android.graphics.Bitmap}:</p> 374 375 <pre>private Bitmap getBitmapFromUri(Uri uri) throws IOException { 376 ParcelFileDescriptor parcelFileDescriptor = 377 getContentResolver().openFileDescriptor(uri, "r"); 378 FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); 379 Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor); 380 parcelFileDescriptor.close(); 381 return image; 382 } 383 </pre> 384 385 <p>Lu rng bn khng nn thc hin thao tc ny trn lung UI. Thc hin iu ny di 386 nn bng cch s dng {@link android.os.AsyncTask}. Sau khi m bitmap, bn c th 387 hin th n trong mt {@link android.widget.ImageView}. 388 </p> 389 390 <h4>Nhn mt InputStream</h4> 391 392 <p>Sau y l mt v d v cch m bn c th nhn mt {@link java.io.InputStream} t URI. Trong on m HTML 393 ny, cc dng tp ang c c thnh mt xu:</p> 394 395 <pre>private String readTextFromUri(Uri uri) throws IOException { 396 InputStream inputStream = getContentResolver().openInputStream(uri); 397 BufferedReader reader = new BufferedReader(new InputStreamReader( 398 inputStream)); 399 StringBuilder stringBuilder = new StringBuilder(); 400 String line; 401 while ((line = reader.readLine()) != null) { 402 stringBuilder.append(line); 403 } 404 fileInputStream.close(); 405 parcelFileDescriptor.close(); 406 return stringBuilder.toString(); 407 } 408 </pre> 409 410 <h3 id="create">To mt ti liu mi</h3> 411 412 <p>ng dng ca bn c th to mt ti liu mi trong mt trnh cung cp ti liu bng cch s dng nh 413 {@link android.content.Intent#ACTION_CREATE_DOCUMENT} 414 . to mt tp, bn cp cho nh ca mnh mt kiu MIME v tn tp, v 415 khi chy n bng mt m yu cu duy nht. Phn cn li s c lm h bn:</p> 416 417 418 <pre> 419 // Here are some examples of how you might call this method. 420 // The first parameter is the MIME type, and the second parameter is the name 421 // of the file you are creating: 422 // 423 // createFile("text/plain", "foobar.txt"); 424 // createFile("image/png", "mypicture.png"); 425 426 // Unique request code. 427 private static final int WRITE_REQUEST_CODE = 43; 428 ... 429 private void createFile(String mimeType, String fileName) { 430 Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); 431 432 // Filter to only show results that can be "opened", such as 433 // a file (as opposed to a list of contacts or timezones). 434 intent.addCategory(Intent.CATEGORY_OPENABLE); 435 436 // Create a file with the requested MIME type. 437 intent.setType(mimeType); 438 intent.putExtra(Intent.EXTRA_TITLE, fileName); 439 startActivityForResult(intent, WRITE_REQUEST_CODE); 440 } 441 </pre> 442 443 <p>Sau khi to mt ti liu mi, bn c th nhn URI ca ti liu trong 444 {@link android.app.Activity#onActivityResult onActivityResult()}, sao cho bn 445 c th tip tc ghi n.</p> 446 447 <h3 id="delete">Xa mt ti liu</h3> 448 449 <p>Nu bn c URI cho mt ti liu v 450 {@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS} 451 ca ti liu cha 452 {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE}, 453 bn c th xa ti liu . V d:</p> 454 455 <pre> 456 DocumentsContract.deleteDocument(getContentResolver(), uri); 457 </pre> 458 459 <h3 id="edit">Chnh sa mt ti liu</h3> 460 461 <p>Bn c th s dng SAF chnh sa mt ti liu vn bn ngay ti ch. 462 on m HTML ny th hin 463 nh {@link android.content.Intent#ACTION_OPEN_DOCUMENT} v s dng 464 th loi {@link android.content.Intent#CATEGORY_OPENABLE} ch hin th 465 nhng ti liu c th m c. N lc thm ch hin th nhng tp vn bn:</p> 466 467 <pre> 468 private static final int EDIT_REQUEST_CODE = 44; 469 /** 470 * Open a file for writing and append some text to it. 471 */ 472 private void editDocument() { 473 // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's 474 // file browser. 475 Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); 476 477 // Filter to only show results that can be "opened", such as a 478 // file (as opposed to a list of contacts or timezones). 479 intent.addCategory(Intent.CATEGORY_OPENABLE); 480 481 // Filter to show only text files. 482 intent.setType("text/plain"); 483 484 startActivityForResult(intent, EDIT_REQUEST_CODE); 485 } 486 </pre> 487 488 <p>Tip theo, t {@link android.app.Activity#onActivityResult onActivityResult()} 489 (xem <a href="#results">Kt qu tin trnh</a>) bn c th gi m thc hin chnh sa. 490 on m HTML sau nhn c mt {@link java.io.FileOutputStream} 491 t {@link android.content.ContentResolver}. Theo mc nh, n s dng ch ghi. 492 Cch tt nht l yu cu lng quyn truy cp bn cn mc t nht, v th ng yu cu 493 quyn c/ghi nu bn ch cn quyn ghi:</p> 494 495 <pre>private void alterDocument(Uri uri) { 496 try { 497 ParcelFileDescriptor pfd = getActivity().getContentResolver(). 498 openFileDescriptor(uri, "w"); 499 FileOutputStream fileOutputStream = 500 new FileOutputStream(pfd.getFileDescriptor()); 501 fileOutputStream.write(("Overwritten by MyCloud at " + 502 System.currentTimeMillis() + "\n").getBytes()); 503 // Let the document provider know you're done by closing the stream. 504 fileOutputStream.close(); 505 pfd.close(); 506 } catch (FileNotFoundException e) { 507 e.printStackTrace(); 508 } catch (IOException e) { 509 e.printStackTrace(); 510 } 511 }</pre> 512 513 <h3 id="permissions">C nh cc quyn</h3> 514 515 <p>Khi ng dng ca bn m mt tp c hoc ghi, h thng s cp cho 516 ng dng ca bn mt quyn URI c cp cho tp . Quyn ny s ko di ti khi thit b ca bn khi ng li. 517 Nhng gi s ng dng ca bn l mt ng dng chnh sa hnh nh, v bn mun ngi dng c th 518 truy cp 5 hnh nh cui cng m h chnh sa, trc tip t ng dng ca bn. Nu thit b ca ngi dng 519 khi ng li, bn s phi gi ngi dng tr li b chn h thng tm 520 cc tp , y r rng khng phi l cch l tng.</p> 521 522 <p> trnh iu ny xy ra, bn c th c nh cc quyn m h thng 523 cp cho ng dng ca bn. ng dng ca bn s "nhn" cp quyn URI c th c nh 524 m h thng cung cp mt cch hiu qu. iu ny cho php ngi dng c quyn lin tc truy cp cc tp 525 thng qua ng dng ca bn, ngay c khi thit b b khi ng li:</p> 526 527 528 <pre>final int takeFlags = intent.getFlags() 529 & (Intent.FLAG_GRANT_READ_URI_PERMISSION 530 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 531 // Check for the freshest data. 532 getContentResolver().takePersistableUriPermission(uri, takeFlags);</pre> 533 534 <p>Cn mt bc cui cng. Bn c th lu cc 535 URI gn y nht m ng dng ca bn truy cp, nhng chng cn th khng cn hp l—mt ng dng khc 536 c th xa hoc sa i ti liu. V th, bn lun nn gi 537 {@code getContentResolver().takePersistableUriPermission()} kim tra 538 d liu mi nht.</p> 539 540 <h2 id="custom">Ghi mt Trnh cung cp Ti liu Ty chnh</h2> 541 542 <p> 543 Nu bn ang pht trin mt ng dng cung cp dch v lu tr cho tp (chng hn nh 544 mt dch v lu tr m my), bn c th cung cp cc tp ca mnh thng qua 545 SAF bng cch ghi mt trnh cung cp ti liu ty chnh. Phn ny m t cch lm iu 546 ny.</p> 547 548 549 <h3 id="manifest">Bn k khai</h3> 550 551 <p> trin khai mt trnh cung cp ti liu ty chnh, hy thm ni dung sau vo bn k khai 552 ca ng dng ca bn:</p> 553 <ul> 554 555 <li>Mt mc tiu API mc 19 hoc cao hn.</li> 556 557 <li>Mt phn t <code><provider></code> khai bo trnh cung cp lu tr 558 ty chnh ca bn. </li> 559 560 <li>Tn ca trnh cung cp ca bn, l tn lp ca n, bao gm tn gi. 561 V d: <code>com.example.android.storageprovider.MyCloudProvider</code>.</li> 562 563 <li>Tn thm quyn ca bn, tc l tn gi ca bn (trong v d ny l 564 <code>com.example.android.storageprovider</code>) cng vi kiu ca trnh cung cp ni dung 565 (<code>documents</code>). V d, {@code com.example.android.storageprovider.documents}.</li> 566 567 <li>Thuc tnh <code>android:exported</code> c t thnh <code>"true"</code>. 568 Bn phi xut trnh cung cp ca mnh cc ng dng khc c th thy n.</li> 569 570 <li>Thuc tnh <code>android:grantUriPermissions</code> c t thnh 571 <code>"true"</code>. Thit t ny cho php h thng cp cho cc ng dng khc quyn truy cp 572 vo ni dung trong trnh cung cp ca bn. tho lun v cch c nh quyn c cp cho 573 mt ti liu c th, hy xem phn<a href="#permissions">C nh cc quyn</a>.</li> 574 575 <li>Quyn {@code MANAGE_DOCUMENTS}. Theo mc nh, mt trnh cung cp s c sn 576 i vi mi ngi. Vic thm quyn ny s hn ch trnh cung cp ca bn vo h thng. 577 Hn ch ny c ngha quan trng i vi vn bo mt.</li> 578 579 <li>Thuc tnh {@code android:enabled} c t thnh mt gi tr boolean c nh ngha trong mt tp 580 ti nguyn. Mc ch ca thuc tnh ny l v hiu ha trnh cung cp trn cc thit b chy phin bn Android 4.3 hoc thp hn. 581 V d, {@code android:enabled="@bool/atLeastKitKat"}. Bn 582 cnh vic nu thuc tnh ny trong bn k khai, bn cn lm nh sau: 583 <ul> 584 <li>Trong tp ti nguyn {@code bool.xml} ca bn bn di {@code res/values/}, hy thm 585 dng sau: <pre><bool name="atLeastKitKat">false</bool></pre></li> 586 587 <li>Trong tp ti nguyn {@code bool.xml} ca bn bn di {@code res/values-v19/}, hy thm 588 dng sau: <pre><bool name="atLeastKitKat">true</bool></pre></li> 589 </ul></li> 590 591 <li>Mt b lc nh cha hnh ng 592 {@code android.content.action.DOCUMENTS_PROVIDER}, sao cho trnh cung cp ca bn 593 xut hin trong b chn khi h thng tm kim trnh cung cp.</li> 594 595 </ul> 596 <p>Sau y l cc on trch t mt bn k khai mu cha mt trnh cung cp:</p> 597 598 <pre><manifest... > 599 ... 600 <uses-sdk 601 android:minSdkVersion="19" 602 android:targetSdkVersion="19" /> 603 .... 604 <provider 605 android:name="com.example.android.storageprovider.MyCloudProvider" 606 android:authorities="com.example.android.storageprovider.documents" 607 android:grantUriPermissions="true" 608 android:exported="true" 609 android:permission="android.permission.MANAGE_DOCUMENTS" 610 android:enabled="@bool/atLeastKitKat"> 611 <intent-filter> 612 <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> 613 </intent-filter> 614 </provider> 615 </application> 616 617 </manifest></pre> 618 619 <h4 id="43">H tr cc thit b chy phin bn Android 4.3 v thp hn</h4> 620 621 <p> nh 622 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} ch c sn 623 trn cc thit b chy phin bn Android 4.4 tr ln. 624 Nu bn mun ng dng ca mnh h tr {@link android.content.Intent#ACTION_GET_CONTENT} 625 to iu kin cho cc thit b ang chy phin bn Android 4.3 v thp hn, bn nn 626 v hiu ha b lc nh {@link android.content.Intent#ACTION_GET_CONTENT} trong 627 bn k khai ca bn cho cc thit b chy phin bn Android 4.4 tr ln. Mt 628 trnh cung cp ti liu v {@link android.content.Intent#ACTION_GET_CONTENT} nn c xem xt 629 loi tr ln nhau. Nu bn h tr c hai ng thi, ng dng ca bn s 630 xut hin hai ln trong UI ca b chn h thng, a ra hai cch khc nhau truy cp 631 d liu lu ca bn. iu ny c th khin ngi dng b nhm ln.</p> 632 633 <p>Sau y l cch c khuyn co v hiu ha b lc nh 634 {@link android.content.Intent#ACTION_GET_CONTENT} i vi cc thit b 635 chy phin bn Android 4.4 hoc cao hn:</p> 636 637 <ol> 638 <li>Trong tp ti nguyn {@code bool.xml} ca bn bn di {@code res/values/}, hy thm 639 dng sau: <pre><bool name="atMostJellyBeanMR2">true</bool></pre></li> 640 641 <li>Trong tp ti nguyn {@code bool.xml} ca bn bn di {@code res/values-v19/}, hy thm 642 dng sau: <pre><bool name="atMostJellyBeanMR2">false</bool></pre></li> 643 644 <li>Thm mt 645 <a href="{@docRoot}guide/topics/manifest/activity-alias-element.html">b danh 646 hot ng</a> v hiu ha b lc nh {@link android.content.Intent#ACTION_GET_CONTENT} 647 i vi cc phin bn 4.4 (API mc 19) tr ln. V d: 648 649 <pre> 650 <!-- This activity alias is added so that GET_CONTENT intent-filter 651 can be disabled for builds on API level 19 and higher. --> 652 <activity-alias android:name="com.android.example.app.MyPicker" 653 android:targetActivity="com.android.example.app.MyActivity" 654 ... 655 android:enabled="@bool/atMostJellyBeanMR2"> 656 <intent-filter> 657 <action android:name="android.intent.action.GET_CONTENT" /> 658 <category android:name="android.intent.category.OPENABLE" /> 659 <category android:name="android.intent.category.DEFAULT" /> 660 <data android:mimeType="image/*" /> 661 <data android:mimeType="video/*" /> 662 </intent-filter> 663 </activity-alias> 664 </pre> 665 </li> 666 </ol> 667 <h3 id="contract">Hp ng</h3> 668 669 <p>Thng khi bn ghi mt trnh cung cp ni dung ty chnh, mt trong nhng tc v l 670 trin khai cc lp hp ng nh c m t trong hng dn cho nh pht trin 671 <a href="{@docRoot}guide/topics/providers/content-provider-creating.html#ContractClass"> 672 Trnh cung cp Ni dung</a>. Lp hp ng l mt lp {@code public final} m 673 cha cc nh ngha hng s cho URI, tn ct, kiu MIME v 674 siu d liu khc lin quan ti trnh cung cp. SAF 675 cung cp nhng lp hp ng ny cho bn, v th bn khng cn t 676 ghi:</p> 677 678 <ul> 679 <li>{@link android.provider.DocumentsContract.Document}</li> 680 <li>{@link android.provider.DocumentsContract.Root}</li> 681 </ul> 682 683 <p>V d, sau y l cc ct bn c th tr v trong mt con chy khi 684 trnh cung cp ti liu ca bn c truy vn v ti liu hoc phn gc:</p> 685 686 <pre>private static final String[] DEFAULT_ROOT_PROJECTION = 687 new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES, 688 Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, 689 Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID, 690 Root.COLUMN_AVAILABLE_BYTES,}; 691 private static final String[] DEFAULT_DOCUMENT_PROJECTION = new 692 String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, 693 Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED, 694 Document.COLUMN_FLAGS, Document.COLUMN_SIZE,}; 695 </pre> 696 697 <h3 id="subclass">Phn lp con DocumentsProvider</h3> 698 699 <p>Bc tip theo trong khi ghi mt trnh cung cp ti liu ty chnh l phn lp con 700 cho lp tm tt {@link android.provider.DocumentsProvider}. Ti thiu, bn cn trin khai 701 cc phng php sau:</p> 702 703 <ul> 704 <li>{@link android.provider.DocumentsProvider#queryRoots queryRoots()}</li> 705 706 <li>{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}</li> 707 708 <li>{@link android.provider.DocumentsProvider#queryDocument queryDocument()}</li> 709 710 <li>{@link android.provider.DocumentsProvider#openDocument openDocument()}</li> 711 </ul> 712 713 <p>y l nhng phng php duy nht m bn c yu cu phi trin khai, nhng cn 714 nhiu phng php na m bn c th mun trin khai. Xem {@link android.provider.DocumentsProvider} 715 bit chi tit.</p> 716 717 <h4 id="queryRoots">Trin khai queryRoots</h4> 718 719 <p>Vic bn trin khai {@link android.provider.DocumentsProvider#queryRoots 720 queryRoots()} phi tr v mt {@link android.database.Cursor} tr v tt c 721 th mc gc trong trnh cung cp ti liu ca bn, bng cch s dng cc ct c nh ngha trong 722 {@link android.provider.DocumentsContract.Root}.</p> 723 724 <p>Trong on m HTML sau, tham s {@code projection} biu din cc trng c th 725 m hm gi mun nhn v. on m HTML to mt con chy mi 726 v thm mt hng vo n—mt th mc gc, mc cao nht, nh 727 Downloads hoc Images. Hu ht cc trnh cung cp ch c mt phn gc. Bn c th c nhiu hn mt, 728 v d, trong trng hp nhiu ti khon ngi dng. Trong trng hp , ch cn thm mt 729 hng th hai vo con chy.</p> 730 731 <pre> 732 @Override 733 public Cursor queryRoots(String[] projection) throws FileNotFoundException { 734 735 // Create a cursor with either the requested fields, or the default 736 // projection if "projection" is null. 737 final MatrixCursor result = 738 new MatrixCursor(resolveRootProjection(projection)); 739 740 // If user is not logged in, return an empty root cursor. This removes our 741 // provider from the list entirely. 742 if (!isUserLoggedIn()) { 743 return result; 744 } 745 746 // It's possible to have multiple roots (e.g. for multiple accounts in the 747 // same app) -- just add multiple cursor rows. 748 // Construct one row for a root called "MyCloud". 749 final MatrixCursor.RowBuilder row = result.newRow(); 750 row.add(Root.COLUMN_ROOT_ID, ROOT); 751 row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary)); 752 753 // FLAG_SUPPORTS_CREATE means at least one directory under the root supports 754 // creating documents. FLAG_SUPPORTS_RECENTS means your application's most 755 // recently used documents will show up in the "Recents" category. 756 // FLAG_SUPPORTS_SEARCH allows users to search all documents the application 757 // shares. 758 row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | 759 Root.FLAG_SUPPORTS_RECENTS | 760 Root.FLAG_SUPPORTS_SEARCH); 761 762 // COLUMN_TITLE is the root title (e.g. Gallery, Drive). 763 row.add(Root.COLUMN_TITLE, getContext().getString(R.string.title)); 764 765 // This document id cannot change once it's shared. 766 row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir)); 767 768 // The child MIME types are used to filter the roots and only present to the 769 // user roots that contain the desired type somewhere in their file hierarchy. 770 row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir)); 771 row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace()); 772 row.add(Root.COLUMN_ICON, R.drawable.ic_launcher); 773 774 return result; 775 }</pre> 776 777 <h4 id="queryChildDocuments">Trin khai queryChildDocuments</h4> 778 779 <p>Vic bn trin khai 780 {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} 781 phi tr v mt {@link android.database.Cursor} m ch n tt c tp trong 782 th mc c ch nh, bng cch s dng cc ct c nh ngha trong 783 {@link android.provider.DocumentsContract.Document}.</p> 784 785 <p>Phng php ny c gi khi bn chn mt th mc gc ng dng trong UI b chn. 786 N nhn c ti liu con ca mt th mc nm di phn gc. N c th c gi bt k mc no trong phn cp tp 787 , khng ch phn gc. on m HTML 788 ny to mt con chy mi bng cc ct c yu cu, sau thm thng tin v 789 mi tp con trc tip trong th mc m vo con chy. 790 Tp con c th l mt hnh nh, mt th mc khc—bt k tp no:</p> 791 792 <pre>@Override 793 public Cursor queryChildDocuments(String parentDocumentId, String[] projection, 794 String sortOrder) throws FileNotFoundException { 795 796 final MatrixCursor result = new 797 MatrixCursor(resolveDocumentProjection(projection)); 798 final File parent = getFileForDocId(parentDocumentId); 799 for (File file : parent.listFiles()) { 800 // Adds the file's display name, MIME type, size, and so on. 801 includeFile(result, null, file); 802 } 803 return result; 804 } 805 </pre> 806 807 <h4 id="queryDocument">Trin khai queryDocument</h4> 808 809 <p>Vic bn trin khai 810 {@link android.provider.DocumentsProvider#queryDocument queryDocument()} 811 phi tr v mt {@link android.database.Cursor} m ch n tp c ch nh, 812 bng cch s dng cc ct c nh ngha trong {@link android.provider.DocumentsContract.Document}. 813 </p> 814 815 <p>Phng php {@link android.provider.DocumentsProvider#queryDocument queryDocument()} 816 tr v cng thng tin c chuyn trong 817 {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}, 818 nhng l i vi mt tp c th:</p> 819 820 821 <pre>@Override 822 public Cursor queryDocument(String documentId, String[] projection) throws 823 FileNotFoundException { 824 825 // Create a cursor with the requested projection, or the default projection. 826 final MatrixCursor result = new 827 MatrixCursor(resolveDocumentProjection(projection)); 828 includeFile(result, documentId, null); 829 return result; 830 } 831 </pre> 832 833 <h4 id="openDocument">Trin khai openDocument</h4> 834 835 <p>Bn phi trin khai {@link android.provider.DocumentsProvider#openDocument 836 openDocument()} tr v mt {@link android.os.ParcelFileDescriptor} biu din 837 tp c ch nh. Cc ng dng khc c th s dng {@link android.os.ParcelFileDescriptor} 838 c tr v truyn pht d liu. H thng gi phng php ny sau khi ngi dng chn mt tp 839 v ng dng my khch yu cu truy cp n bng cch gi 840 {@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()}. 841 V d:</p> 842 843 <pre>@Override 844 public ParcelFileDescriptor openDocument(final String documentId, 845 final String mode, 846 CancellationSignal signal) throws 847 FileNotFoundException { 848 Log.v(TAG, "openDocument, mode: " + mode); 849 // It's OK to do network operations in this method to download the document, 850 // as long as you periodically check the CancellationSignal. If you have an 851 // extremely large file to transfer from the network, a better solution may 852 // be pipes or sockets (see ParcelFileDescriptor for helper methods). 853 854 final File file = getFileForDocId(documentId); 855 856 final boolean isWrite = (mode.indexOf('w') != -1); 857 if(isWrite) { 858 // Attach a close listener if the document is opened in write mode. 859 try { 860 Handler handler = new Handler(getContext().getMainLooper()); 861 return ParcelFileDescriptor.open(file, accessMode, handler, 862 new ParcelFileDescriptor.OnCloseListener() { 863 @Override 864 public void onClose(IOException e) { 865 866 // Update the file with the cloud server. The client is done 867 // writing. 868 Log.i(TAG, "A file with id " + 869 documentId + " has been closed! 870 Time to " + 871 "update the server."); 872 } 873 874 }); 875 } catch (IOException e) { 876 throw new FileNotFoundException("Failed to open document with id " 877 + documentId + " and mode " + mode); 878 } 879 } else { 880 return ParcelFileDescriptor.open(file, accessMode); 881 } 882 } 883 </pre> 884 885 <h3 id="security">Bo mt</h3> 886 887 <p>Gi s trnh cung cp ti liu ca bn l mt dch v lu tr m my c bo v bng mt khu 888 v bn mun m bo rng ngi dng c ng nhp trc khi bn bt u chia s tp ca h. 889 ng dng ca bn nn lm g nu ngi dng khng ng nhp? Gii php l tr v 890 phn gc 0 trong trin khai {@link android.provider.DocumentsProvider#queryRoots 891 queryRoots()} ca bn. C th l mt con chy gc trng:</p> 892 893 <pre> 894 public Cursor queryRoots(String[] projection) throws FileNotFoundException { 895 ... 896 // If user is not logged in, return an empty root cursor. This removes our 897 // provider from the list entirely. 898 if (!isUserLoggedIn()) { 899 return result; 900 } 901 </pre> 902 903 <p>Bc cn li l gi {@code getContentResolver().notifyChange()}. 904 Bn cn nh {@link android.provider.DocumentsContract} ch? Chng ta ang s dng n to 905 URI ny. on m HTML sau bo cho h thng truy vn cc phn gc trong 906 trnh cung cp ti liu ca bn bt c khi no trng thi ng nhp ca ngi dng thay i. Nu ngi dng khng c 907 ng nhp, lnh gi ti {@link android.provider.DocumentsProvider#queryRoots queryRoots()} s tr v mt 908 con chy trng nh minh ha bn trn. iu ny m bo rng ti liu ca mt trnh cung cp ch 909 c sn nu ngi dng ng nhp vo trnh cung cp .</p> 910 911 <pre>private void onLoginButtonClick() { 912 loginOrLogout(); 913 getContentResolver().notifyChange(DocumentsContract 914 .buildRootsUri(AUTHORITY), null); 915 } 916 </pre>