ContentProvider再探——Document Provider


Introduction to this section:

After studying the previous section, I believe you already know how to use the ContentProvider or custom ContentProvider provided by the system. It has basically met the needs of daily development. Interestingly, I saw these other Providers in the official documentation:

1.png

##Calendar Provider: The calendar provider is a resource library for calendar-related events. Through the API it provides, we You can add, delete, modify and check calendar, time, meetings, reminders and other contents!
Contacts Provider: Contact provider, needless to say, this one is used the most~ I’ll go back and translate this article later when I have time!
Storage Access Framework (SAF): Storage Access Framework, a new thing introduced after 4.4, allows users to browse the files on their mobile phones. Storage of content provides convenience, and the content that can be accessed not only includes: documents, pictures, videos, audios, downloads, but also includes all Content provided by a specific ContentProvider (which must have an agreed API). No matter where the content comes from, no matter which application By calling the command to browse the contents of system files, the system will use a unified interface for you to browse. In fact, it is a built-in application called DocumentsUI. Because its IntentFilter does not have LAUNCHER, we do not Find this stuff on the desktop! Hey, try the following code. Here we have selected two mobile phones for comparison: For comparison, Lenovo S898T in 4.2 and Nexus 5 in 5.0.1, execute the following code:

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory (Intent.CATEGORY_OPENABLE);
                                                                                                                                                      (Intent.CATEGORY_OPENABLE);The following is the running result:

2.png3.png

The one on the right is the new thing brought to us by 4.4. Generally, we can use it when we get the file Url~ Next, simply go down the document~


2. Simply go down the document:

1) The composition of the SAF framework:

  • Document provider: A special ContentProvider that allows a storage service (such as Google Drive) to Display the files you manage to the outside world. It is a subclass of DocumentsProvider. In addition, the storage format of document-provider It is consistent with the traditional file storage format. As for how to store your content, it is completely up to you. The Android system has built-in several Such a Document provider, such as a Document provider for downloads, pictures and videos!
  • Client app: An ordinary client software that can receive returns from the Document provider by triggering ACTION_OPEN_DOCUMENT and/or ACTION_CREATE_DOCUMENT content, such as selecting a picture, Then return a Uri.
  • Picker: An interface similar to a file manager, and a system-level interface that provides access to client filtering conditions The channel of Document provider content is the DocumentsUI program mentioned in the beginning!

Some features:

  • Users can browse content provided by all document providers, not just a single application Program
  • provides long-term, continuous access to files in the document provider and data persistence. Users can add, delete, edit, and save content maintained by the document provider
  • Supports multi-user and temporary content services, such as USB storage providers that will only appear when the driver is installed successfully

2) Overview:

The core of SAF is to implement a subclass of DocumentsProvider or a ContentProvider. in a document provider is organized by a traditional file directory tree:

4.png

3) Flow chart:

As mentioned above, document provider data is based on the traditional File hierarchical structure, but that is only the external representation. How to store your data is up to you, as long as your overseas interface can be accessed through the API of DocumentsProvider. The flow chart below shows the possible structure of a photo application using SAF:

5.png

Analysis:

From the picture above, we can see that Picker is a bridge between the caller and the content provider! It provides and tells the caller that it can choose Which content providers, such as DriveDocProvider, UsbDocProvider, CloundDocProvider here.
When the client triggers the Intent of ACTION_OPEN_DOCUMENT or ACTION_CREATE_DOCUMENT, the above interaction will occur. Of course, we can also add filtering conditions to the Intent, such as limiting the MIME type to "image"!

6.png

is the above things, if you also install If you use other software to view pictures, you will also see them here! To put it simply: after the client sends the Intent of the above two Actions, the Picker UI will be opened, where the relevant available options will be displayed. Document Provider, for users to choose, users can get relevant information about the file after selecting it!


4) The client calls and gets the returned Uri

The implementation code is as follows:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    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 = new 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, Intent data) {
        if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
            Uri uri;
            if (data != null) {
                uri = data.getData();
                Log.e("HeHe", "Uri: " + uri.toString());
            }
        }
    }
}

Running results: For example, if we select the dog, the Picker UI will turn off itself, and then you can see such a uri on Logcat:

7.png


##5) Get file parameters based on uri

The core code is as follows:

public void dumpImageMetaData(Uri uri) {
Cursor cursor = getContentResolver()
.query(uri, null, null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
String displayName = Cursor.getString (
Cursor.getColumNindex (OpenableColumns.display_name);
Log.e ("Hehe", "Display name:"+DisplayName);
SizeIndex = Cursor.GetColumNindex (OpenableColumns. SIZE);
String size = null;
if (!cursor.isNull(sizeIndex)) {
size = cursor.getString(sizeIndex);
}else {
            size = "Unknown “; #}

Run result: It’s still the same dog. After calling the method, the file name and file size will be input, in bytes.

8.png


6) Obtain Bitmap

based on Uri. The core code is as follows:

private Bitmap getBitmapFromUri(Uri uri) throws IOException {
ParcelFileDescriptor parcelFileDescriptor =
getContentResolver().openFileDescriptor(uri, "r");
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
parcelFileDe scriptor.close();
return image;
}

Run result:

9.gif

##7) Get the input stream based on Uri

The core code is as follows:

##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();
}

The above content only tells you what you can know through a Uri, and the Uri is obtained through SAF!


8) Create new files and delete files:

Create files:

private void createFile(String mimeType, String fileName) {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(mimeType);
intent.putExtra(Intent.EXTRA_TITLE, fileName);
startActivityForResult(intent, WRITE_REQUEST_CODE);
}

The uri of the created file can be obtained in onActivityResult()

Delete the file:

The premise is that Document.COLUMN_FLAGS contains SUPPORTS_DELETE

##DocumentsContract.deleteDocument(getContentResolver(), uri);

9) Write a custom Document Provider

If you want your application data to be opened in documentsui, you need to write your own document provider. The following describes the steps to customize DocumentsProvider:

  • The API version is 19 or higher
  • Register the Provider in manifest.xml
  • The name of the Provider Add the package name to the class name, for example: com.example.android.storageprovider.MyCloudProvider
  • Authority is the package name + the type name of the provider, such as: com.example.android .storageprovider.documents
  • The value of android:exported attribute is true
##The following is an example of how to write Provider:

<manifest... >
...
<uses-sdk
android:minSdkVersion="19"
android:targetSdkVersion="19" />
       ....
                                                        < ## Android: Granturipermissions = "TRUE" ## android: Exported = "TRUE"
Android: Permission = "Android.Permission.Manage_documents"
Road: enabled = "@Bool/Atleastkitkat" & GT;
                                         >
</application>

</manifest>

10) The subclass of DocumentsProvider

implements at least the following methods:

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

There are other methods, but they are not necessary. The following demonstrates an implementation of accessing the file system The rough way to write DocumentsProvider.

Implement queryRoots

@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 "MyCloud".
    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 "Recents" 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;
}

Implement queryChildDocuments

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

Implement queryDocument

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

Okay, that’s about it in the document: At first I wanted to translate it myself, but then I found the Chinese translation of this document while spending time online, so I was too lazy~

Chinese translation link:android storage access framework Storage Access Framework


3.Android 4.4 Obtain resource path problem:

In fact, the place where we use this SAF more is nothing more than getting the Uri of the image, and from above Examples we also found: The link we obtain is like this:

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

For such a link, we can directly obtain the uri through the above method!

Of course, this is version 4.4 or above~!

If it is a previous version: the uri may be like this:

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

Here is a comprehensive plan I saw elsewhere, the original link: Android4.4 Problem getting resource path

public static String getPath(final Context context, final Uri uri) {
    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)) {
                return Environment.getExternalStorageDirectory() + "/" + split[1];
            }

            // TODO handle non-primary volumes  
        }
        // DownloadsProvider  
        else if (isDownloadsDocument(uri)) {

            final String id = DocumentsContract.getDocumentId(uri);
            final Uri contentUri = ContentUris.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 (and 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;
}

/**
 * Get the value of the data column for this Uri. This is useful for 
 * MediaStore Uris, and other file-based ContentProviders. 
 *
 * @param context The context. 
 * @param uri The Uri to query. 
 * @param selection (Optional) Filter used in the query. 
 * @param selectionArgs (Optional) Selection arguments used in the query. 
 * @return The value of the _data column, which is typically a file path. 
 */
public static String getDataColumn(Context context, Uri uri, String selection,
                                   String[] selectionArgs) {

    Cursor cursor = null;
    final String column = "_data";
    final String[] projection = {
            column
    };

    try {
        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)
            cursor.close();
    }
    return null;
}


/**
 * @param uri The Uri to check. 
 * @return Whether the Uri authority is ExternalStorageProvider. 
 */
public static boolean isExternalStorageDocument(Uri uri) {
    return "com.android.externalstorage.documents".equals(uri.getAuthority());
}

/**
* @param uri The Uri to check.
* @return Why the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers. downloads.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check. 
 * @return Whether the Uri authority is MediaProvider. 
 */
public static boolean isMediaDocument(Uri uri) {
return "com .android.providers.media.documents".equals(uri.getAuthority());
}

Summary of this section:

Okay, about this That’s it for the Android Storage Access Framework SAF. There are no examples. We will use it later and study it in depth. Just know it. It will be much easier to obtain the file path after 4.4~