ContentProvider再探索——ドキュメントプロバイダー
このセクションの概要:
前のセクションを学習した後、システムによって提供される ContentProvider の使用方法、または ContentProvider をカスタマイズする方法はすでに理解していると思います。 興味深いことに、これは公式ドキュメントで次の他のプロバイダーを確認しました:
カレンダープロバイダー: カレンダープロバイダーは、彼が提供する API を介したカレンダー関連イベントのリソースライブラリです。私たちは カレンダー、時間、会議、リマインダーなどのコンテンツを追加、削除、変更、確認できます!
連絡先プロバイダー: プロバイダーに連絡します。言うまでもなく、これが最もよく使われます~後で時間があるときに戻ってこの記事を翻訳します!
ストレージ アクセス フレームワーク (SAF): 4.4 以降に導入された新しいものであるストレージ アクセス フレームワークを使用すると、ユーザーは携帯電話でファイルを参照できます。 コンテンツのストレージは利便性を提供し、アクセスできるコンテンツにはドキュメント、写真、ビデオ、オーディオ、ダウンロードだけでなく、すべてのコンテンツが含まれます。 特定の ContentProvider によって提供されるコンテンツ (合意された API が必要です)。コンテンツの出所やアプリケーションに関係なく コマンドを呼び出してシステム ファイルの内容を参照すると、システムは統合インターフェイスを使用して参照できるようになります。
実際、これはDocumentsUIと呼ばれる組み込みアプリケーションです。そのIntentFilterにはLAUNCHERがないため、 デスクトップ上でこんなものを見つけてください!次のコードを試してください。ここでは比較のために 2 台の携帯電話を選択しました。 4.2 の Lenovo S898T と 5.0.1 の Nexus 5 を比較して、次のコードを実行します:
intent.setType ("image/*");
startActivity(意図);
右側のものは 4.4 によってもたらされた新しいもので、通常、ファイルの URL を取得するときに使用できます。 次に、ドキュメントを下に進みます~
2. ドキュメントを下に進みます:
1) SAF フレームワークの構成:
- ドキュメント プロバイダー: ストレージ サービス (たとえば、 Googleドライブ)できる 管理しているファイルを外部に公開します。 DocumentsProviderのサブクラスであり、ドキュメントプロバイダーの保存形式です。 従来のファイル ストレージ形式と一致していますが、コンテンツを保存する方法については、Android システムにいくつかの機能が組み込まれています。 ダウンロード、写真、ビデオ用のドキュメント プロバイダーなどのドキュメント プロバイダーです。
- クライアントアプリ: 写真の選択など、ACTION_OPEN_DOCUMENTやACTION_CREATE_DOCUMENTをトリガーすることでドキュメントプロバイダーから返されたコンテンツを受信できる通常のクライアントソフトウェア。 次に、Uri を返します。
- ピッカー: ファイルマネージャーに似たインターフェース、およびクライアントのフィルタリング条件へのアクセスを提供するシステムレベルのインターフェース Document プロバイダー コンテンツのチャネルは、冒頭で説明したDocumentsUI プログラムです。
いくつかの機能:
- ユーザーは、単一のアプリケーションだけでなく、すべてのドキュメントプロバイダーによって提供されるコンテンツを参照できます
- ドキュメントプロバイダー内のファイルへの長期的かつ継続的なアクセスとデータ変更の永続性を提供します。 ユーザーは、ドキュメント プロバイダーが管理するコンテンツを追加、削除、編集、保存できます
- ドライバーが正常にインストールされた場合にのみ表示される USB ストレージ プロバイダーなど、マルチユーザーおよび一時的なコンテンツ サービスをサポートします
2) 概要:
SAF の核心は、ContentProvider でもあるDocumentsProvider のサブクラスを実装することです。ドキュメントプロバイダーで は、従来のファイル ディレクトリ ツリーで構成されています:
3) フローチャート:
上で述べたように、ドキュメント プロバイダーのデータは従来のファイル階層に基づいていますが、それは外部表現にすぎません。 DocumentsProvider の API を介して海外インターフェースにアクセスできる限り、データの保存方法は自由です。 以下のフローチャートは、SAF を使用した写真アプリケーションの可能な構造を示しています:
分析:
上の図から、Picker が発信者とコンテンツ プロバイダーの間の橋渡しであることがわかります。呼び出し元に選択できることを提供して伝えます。 ここでは、DriveDocProvider、UsbDocProvider、CloudDocProvider などのコンテンツ プロバイダーを指定します。
クライアントが ACTION_OPEN_DOCUMENT または ACTION_CREATE_DOCUMENT のインテントをトリガーすると、上記のインタラクションが発生します。 もちろん、MIMEタイプを「画像」に限定するなど、インテントにフィルター条件を追加することもできます
他の画像閲覧ソフトをインストールしている場合も同様です。ここに到着! 簡単に言うと、クライアントが上記 2 つのアクションのインテントを送信した後、ピッカー UI が開き、関連する利用可能なオプションが表示されます。 ユーザーが選択できるドキュメント プロバイダー。ユーザーはファイルを選択した後、そのファイルに関する関連情報を取得できます。
4) クライアントは呼び出して、返された Uri を取得します
実装コードは次のとおりです:
private static final int READ_REQUEST_CODE = 42;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main) );
Button btn_show = (Button) findViewById(R.id.btn_show);
btn_show.setOnClickListener(this);
}
@Override
public void onClick(View v) {
インテント intent = 新しい インテント(Intent. ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
startActivityForResult(intent, READ_REQUEST_CODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode,インテント データ) {
if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
Uri uri;
if (data != null) {
uri = data.getData();
Log.e("彼" , "Uri: " + uri.toString());
}
}
}
}
実行結果:たとえば、犬を選択すると、ピッカー UI が自動的にオフになり、Logcat でそのような URI が表示されます:
5) ファイルパラメータに基づいて取得します。 uri
のコアコードは次のとおりです:
Cursorcursor = getContentResolver()
.query(uri, null, null, null, null, null);
{cursor!= null &&cursor。movetofirst()){
文字列displayname = cursor.getcolumnindex(openablecolumns.display_name_name)) + displayname);
int sizeindex = cursor.getColumnIndex(openableColumns.size);
実行結果: 相変わらず同じ犬です。メソッドを呼び出した後、ファイル名とファイルサイズがバイト単位で入力されます
6) Uri
に基づいてビットマップを取得します。コードは次のとおりです:
use using ‐ ‐ ‐ ‐ ‐ ‐ ‐ .getFileDescriptor();
ビットマップ画像 = BitmapFactory.decodeFileDescriptor(fileDescriptor);
パーセルファイル記述子.close ();
Return Image
}
:
7) URI に従って入力ストリーム
コアコードを取得します:Priving String ReadtextFromuri (Uri)ウリ) Thrown Thrown IOException {
inputStream));
StringBuilder stringBuilder = new String Builder();
String line;
while ( (line = reader .readline())!= null){
stringbuilder。
上記の内容は Uri を通じて知ることができることのみを示しており、Uri は SAF を通じて取得されます。
8) 新しいファイルの作成とファイルの削除:
ファイルの作成:
Intenttent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
tent.addCategory( Intent.CATEGORY_OPENABLE);
tent.setType(mimeType);
tent.putExtra(Intent.EXTRA_TITLE, fileName);
startActivityForResult(intent, WRITE_REQUEST_CODE);
}
onActivityResult( ) で URI を取得できます。作成されたファイル
ファイルの削除:
前提として、Document.COLUMN_FLAGS には SUPPORTS_DELETE
9) カスタム ドキュメント プロバイダーを作成します
アプリケーション データを documentui で開くようにしたい場合は、独自のドキュメント プロバイダーを作成する必要があります。 以下に、DocumentsProvider をカスタマイズする手順を説明します。
- API バージョンは 19 以降です。
- manifest.xml にプロバイダーを登録します。
- プロバイダーの名前は、クラス名にパッケージ名を加えたものです (例: com)。 example.android.storageprovider.MyCloudProvider
- Authority は、次のようなパッケージ名 + プロバイダー タイプ名です: com.example.android.storageprovider.documents
- android: エクスポートされた属性の値は true
以下はプロバイダの記述方法の例です:
... ....
Provider Android: name = "com.example.android.storageProvider.myCloudProvider"
Android :authorities = "com.example.android.dorageprovider.doc.doc uments" d android:granturipermissions = "true" 「す」 ‐ action.DOCUMENTS_PROVIDER" /> ;
10)DocumentsProvider
のサブクラスは、少なくとも次のメソッドを実装します:
- queryRoots()
- queryChildDocuments()
- queryDocument()
- openDocument()
他のメソッドもありますが、必須ではありません。以下は、ファイル システムにアクセスする実装を示しています。 DocumentsProvider の大まかな書き方。
queryRootsを実装する
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
// リクエストされたフィールド、または デフォルトの
// 「projection」 が null の場合は、カーソル を作成します。
最終的なMatrixCursorの結果=
new MatrixCursor(resolveRootProjection(projection));
// ユーザーがログインしていない場合は、空のルート カーソルを返します。 これにより、
// プロバイダーがリストから完全に削除されます。
if (!isUserLoggedIn()) {
return result;
}
// 複数のルートを持つ可能性があります(例:
// 同じアプリ内の複数のアカウントの場合) -- 複数のカーソル行を追加するだけです。
//「MyCloud」というルートに 1 つの行を構築します。
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 は、ルートの下にある少なくとも 1 つのディレクトリがドキュメントの作成をサポートすることを意味します
// FLAG_SUPPORTS_RECENTS は、アプリケーションの最も多くのドキュメントを意味します
//最近使用したドキュメントが「最近」カテゴリに表示されます
//FLAG_SUPPORTS_SEARCH を使用すると、ユーザーはアプリケーションが共有するすべてのドキュメントを検索できます
//
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE |
Root.FLAG_SUPPORTS_RECENTS |
Root.FLAG_SUPPORTS_SEARCH);
// COLUMN_TITLE はルートのタイトルです(例:ギャラリー、ドライブ)。
row.add(Ro ot.COLUMN_TITLE, getContext().getString(R.string.title)) ;
// このドキュメント ID は、共有されると変更できません。
row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir));
// 子の MIME タイプは、ルートをフィルタリングし、
にのみ提示するために使用されます。 // ユーザーのルートファイル階層内のどこかに目的のタイプが含まれています。
row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir));
row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace());
row.add(Ro ot.COLUMN_ICON 、R.drawable.ic_launcher);
return result;
}
queryChildDocumentsを実装します
String sortOrder) throws FileNotFoundException {
最終的な MatrixCursor result = new
MatrixCursor(resolveDocumentProjection(projection));
final File parent = getFileForDocId(parentDocumentId);
for (File file : parent.listFiles()) {
// ファイルの表示を追加します名前、MIME タイプ、サイズなど.
includeFile(result, null, file);
}
return result;
}
@Overridepublic Cursor queryDocument(String documentId, Str ing[] projection) throws
FileNotFoundException {最終的な MatrixCursor result = new
MatrixCursor(resolveDocumentProjection(projection));
includeFile(result, documentId, null) ;
結果を返す;
}
それでは、ドキュメントの内容については次のとおりです。 最初は自分で翻訳したかったのですが、オンラインで時間を費やしているときにこのドキュメントの中国語訳を見つけたので、あまりにも面倒でした~
中国語翻訳リンク: android Storage Access Framework
3. Android 4.4 の入手リソース パスの問題:
実際、この SAF をさらに使用するのは、画像の URI を取得することに他なりません。上記の例から、次のこともわかりました。 取得したリンクは次のとおりです:
content://com.android.providers.media.documents/document/image%3A69983
このようなリンクの場合、上記の方法で URI を直接取得できます。
もちろんバージョン4.4以降ですよ〜!
以前のバージョンの場合: uri は次のようになります:
content://media/external/images/media/image%3A69983
これは他の場所で見た包括的な解決策です。元のリンク: Android 4.4 でのリソース パスの取得の問題
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat &&DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split [0];
if( "primary" .equalsignorecase(type)){
entunter.getExternalStorageDirectory()+"/"+split [1];
}
// todo handle nonprimaryボリューム}}}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentU ris.withAppendedId(
Uri.parse("content://downloads/public_downloads" ), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri );
Final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images .Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore .Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[] {
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (および general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
return getDataColumn(context, uri, null, null);
}
/ / File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
/**
* データの値を取得する列この Uri の場合。 これは
に役立ちます * MediaStore Uris、およびその他のファイルベースのコンテンツプロバイダー。
*
* @param context コンテキスト。
* @param uri クエリする Uri 。
* @param 選択 (オプション) クエリで使用されるフィルタ。
* @param selectionArgs (オプション)クエリで使用される選択引数。
* @return _data 列の値。通常はファイル パスです。
*/
public static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = {
column
};
試してみてください {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
final int column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} finally {
if (cursor != null)
cur sor.close();
}
return null;
}
/ **
* @param uri チェックする Uri 。
* @return Uri 権限 が ExternalStorageProvider であるかどうか。
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri チェックする Uri
* @return Uri 権限が DownloadsProvider であれば何でも。
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri) .getAuthority());
}
/**
* @param uri チェックする Uri 。
* @return Uri 権限が MediaProvider であるかどうか。
*/
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority()) ;
}
このセクションの概要:
Android ストレージ アクセス フレームワーク SAF に関するこのセクションはこれで終わりです。サンプルはありません。後で使用して詳しく調べます。 知っておいてください、4.4 以降はファイルパスの取得がはるかに簡単になります~