1. 程式人生 > >Android四大元件之ContentProvider(一)

Android四大元件之ContentProvider(一)

1.什麼是ContentProvider?

內容提供程式管理對資料結構化資料集的訪問。它們封裝資料,並提供用於定義資料安全性的機制。內容提供者是連線一個程序中的資料與另一個程序中執行的程式碼的標準介面。
是不同應用程式之間進行資料交換的標準API,以某種Uri的形式對外提供資料,允許其他應用訪問或修改資料;其他程式使用ContentResolver根據Uri去訪問操作指定的資料。

是IPC通訊的一種。

2.ContentProvider和ContentResolver

  1. 建立ContentProvider的派生類,重寫必要的方法,方法有:(ContentResolve
    r的方法與它類似)
  • onCreate():當其他程式第一個訪問ContentProvider時回撥此方法。
  • String getType(Uri uri):用於返回當前Uri所代表的MIME型別,如果包含多條資料則返回“vnd.android.cursor.dir/開頭”,單條資料則返回“vnd.android.cursor.item/開頭”,開頭部分格式:vnd.<authority>.<path>
  • insert(Uri uri,ContentValue values):根據該uri插入對應的value值
  • update(Uri uri,…)
    :根據uri修改對應引數條件的所有值
  • delete(Uri uri):根據uri刪除符合引數條件的所有值
  • query(Uri uri):根據uri查詢符合條件的所有值
  1. AndroidManifest.xml檔案中為其註冊
 <provider
            android:name=".contentProvider.MyContentProvider"
            android:authorities="org.wdl.book"
            android:enabled="true"
            android:exported="true"
           />
屬性 意義
android:name 類名
android:authorities 指定相應的域名
android:exported 是否提供給外部程式使用
  1. 客戶端程式通過ContentResolver的一系列方法呼叫ContentProvider提供的API進行資料的CRUD

相對應的流程如圖:
在這裡插入圖片描述
ContentResolver.query()。 query() 方法會某某提供程式所定義的 ContentProvider.query() 方法。 以下程式碼行顯示了 ContentResolver.query() 呼叫:(其他方法類似)

// Queries the user dictionary and returns results
mCursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
    mProjection,                        // The columns to return for each row
    mSelectionClause                    // Selection criteria
    mSelectionArgs,                     // Selection criteria
    mSortOrder);                        // The sort order for the returned rows

其中引數與之前SQLite儲存提到的類似,詳見](https://blog.csdn.net/qq_34341338/article/details/84069391)

由上圖可以看出URI在這資料互動的過程中起到了關鍵性(進行資料交換的標識)的作用,因此,該URI所對應的要求如下:`

  1. content://開頭,固定不變的
  2. 中間部分為之前xml檔案中配置的android:authorities,固定不變的
  3. 第三部分代表資源部份。這個部分是動態改變,根據訪問者的需求更改
  4. content://authorities/source/#,#代表萬用字元,可以為1,2,3等;content://authorities/*,*代表任意長度字元,表示所有表

分析:
例:
content://org.wdl.provider/books
意味訪問books資料
content://org.wdl.provider/book/2
意味訪問books資料中ID為2的資料
content://org.wdl.provider/book/2/name
意味訪問books資料中ID為2的資料中的name欄位

Uri工具類提供了parse靜態方法,用於將字串轉為Uri

  1. ContentResolver呼叫方法中傳遞的Uri必須能夠與ContentProvider中所暴露Uri的相匹配,否則會丟擲異常。為了確保正常進行資料CRUD,Android提供了UriMatcher工具類:
    void addURI(String authority,String path,int code):向UriMatcher中註冊Uri,authority+path構成Uri,code表示標識碼,根據它來判斷是否能夠進行資料操作
    int match(Uri uri):根據註冊的Uri判斷指定Uri對應的標識碼,匹配不到返回-1
    例:
		matcher = new UriMatcher(UriMatcher.NO_MATCH);
        matcher.addURI(Books.AUTHORITY, "books", MyContentProvider.BOOKS);
        matcher.addURI(Books.AUTHORITY, "book/#", MyContentProvider.BOOK);
		code = matcher.match(uri)

假如現在我有一個需求:
查詢Books中id為2的資料,需對原始的Uri進行拼接,Android為此提供了另一個工具類ContentUris,主要方法如下:

  • withAppendedId(Uri uri,int id):在路徑上拼接ID部分
  • parseId(Uri uri):解析出指定Uri所包含的ID值

例:

ContentUris.withAppendedId(Books.Book.BOOK_CONTENT_URI,1)
拼接後的uri為:content://org.wdl.provider/book/1
 long id = ContentUris.parseId(uri);
 id值為:1

3.詳細使用

MyContentProvider.class

package com.wdl.crazyandroiddemo.contentProvider;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;

import com.wdl.crazyandroiddemo.MySQLiteHelper;

import java.util.Objects;

public class MyContentProvider extends ContentProvider {
    private MySQLiteHelper helper;
    private static UriMatcher matcher;
    private static final int BOOKS = 1;
    private static final int BOOK = 2;

    static {
        matcher = new UriMatcher(UriMatcher.NO_MATCH);
        matcher.addURI(Books.AUTHORITY, "books", MyContentProvider.BOOKS);
        matcher.addURI(Books.AUTHORITY, "book/#", MyContentProvider.BOOK);
    }

    public MyContentProvider() {
    }

    @Override
    public boolean onCreate() {
        Log.e("wdl", "----------onCreate----------");
        helper = new MySQLiteHelper(getContext(), "demo.db", null, 3);
        return true;
    }

    @Override
    public int delete(@NonNull Uri uri, String where, String[] whereArgs) {
        Log.e("wdl", "----------delete----------");
        SQLiteDatabase db = helper.getReadableDatabase();
        int num = 0;
        switch (matcher.match(uri)) {
            case BOOKS:
                num = db.delete("book", where, whereArgs);
                break;
            case BOOK:
                long id = ContentUris.parseId(uri);
                String whereClause = Books.Book.ID + "=" + id;
                if (!TextUtils.isEmpty(where)) {
                    whereClause = whereClause + " and " + where;
                }
                num = db.delete("book", whereClause, whereArgs);
                break;
            default:
                throw new IllegalArgumentException("未知uri");
        }
        Objects.requireNonNull(getContext()).getContentResolver().notifyChange(uri, null);
        return num;
    }

    @Override
    public String getType(@NonNull Uri uri) {
        Log.e("wdl", "----------getType----------");
        switch (matcher.match(uri)) {
            case BOOKS:
                return "vnd.android.cursor.dir/wdl.books";
            case BOOK:
                return "vnd.android.cursor.item/wdl.book";
            default:
                throw new IllegalArgumentException("未知uri");
        }

    }

    @Override
    public Uri insert(@NonNull Uri uri, ContentValues values) {
        Log.e("wdl", "----------insert----------");
        //獲取資料庫例項
        SQLiteDatabase db = helper.getReadableDatabase();
        switch (matcher.match(uri)) {
            case BOOKS:
                //插入
                long rowId = db.insert("book", Books.Book.ID, values);
                if (rowId > 0) {
                    //uri末尾新增id
                    Uri bookUri = ContentUris.withAppendedId(uri, rowId);
                    //通知資料已經改變
                    Objects.requireNonNull(getContext()).getContentResolver().notifyChange(bookUri, null);
                    return bookUri;
                }
                break;
            default:
                throw new IllegalArgumentException("Not yet implemented");
        }
        return null;
    }

    @Override
    public Cursor query(@NonNull Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        Log.e("wdl", "----------query----------");
        SQLiteDatabase db = helper.getReadableDatabase();
        switch (matcher.match(uri)) {
            case BOOKS:
                return db.query("book",
                        projection,
                        selection,
                        selectionArgs,
                        null,
                        null, sortOrder);
            case BOOK:
                long id = ContentUris.parseId(uri);
                String whereClause = Books.Book.ID + "=" + id;
                if (!TextUtils.isEmpty(selection)) {
                    whereClause = whereClause + " and " + selection;
                }
                return db.query("book", projection, whereClause, selectionArgs, null, null, sortOrder);
            default:
                throw new IllegalArgumentException("未知uri");
        }
    }

    @Override
    public int update(@NonNull Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        Log.e("wdl", "----------update----------");
        SQLiteDatabase db = helper.getReadableDatabase();
        int num = 0;
        switch (matcher.match(uri)) {
            case BOOKS:
                num = db.update("book", values, selection, selectionArgs);
                break;
            case BOOK:
                long id = ContentUris.parseId(uri);
                String whereClause = Books.Book.ID + "=" + id;
                if (!TextUtils.isEmpty(selection)) {
                    whereClause = whereClause + " and " + selection;
                }
                num = db.update("book", values, whereClause, selectionArgs);
                break;
            default:
                throw new IllegalArgumentException("未知uri");
        }
        Objects.requireNonNull(getContext()).getContentResolver().notifyChange(uri, null);
        return num;
    }


}

Books.class,此類封裝了可訪問Uri以及可訪問的資料列:

package com.wdl.crazyandroiddemo.contentProvider;

import android.net.Uri;
import android.provider.BaseColumns;

/**
 * author:   wdl
 * time: 2018/11/17 13:43
 * des:    TODO
 */
@SuppressWarnings("unused")
public class Books {
    //定義contentProvider 的Authorities值
    public static final String AUTHORITY = "org.wdl.book";

    //定義靜態內部類,定義contentProvider所包含的資料列
    public static final class Book implements BaseColumns {
        //定義所允許操作的三個資料列
        public final static String ID = "id";
        public final static String NAME = "name";
        public final static String PRICE = "price";
        public final static String DATE = "publishdate";

        //定義提供服務的兩個Uri
        public final static Uri BOOKS_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/books");
        public final static Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");
    }
}

在Client中呼叫:

package com.wdl.contentproviderclient

import android.content.ContentUris
import android.content.ContentValues
import android.content.Intent
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_main.*
import java.lang.StringBuilder

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val contentResolver = contentResolver
        btnQuery.setOnClickListener {
            val intent = Intent("com.wdl.mime")
            intent.data = Books.Book.BOOKS_CONTENT_URI
            startActivity(intent)
            val cursor = contentResolver.query(Books.Book.BOOKS_CONTENT_URI
                    ,null,"price=?", arrayOf("50.2"),null)
            //cursor.close()
            val stringBuilder = StringBuilder()
            while (cursor.moveToNext()){
                val id = cursor.getInt(cursor.getColumnIndexOrThrow(Books.Book.ID))
                val name = cursor.getString(cursor.getColumnIndexOrThrow(Books.Book.NAME))
                val price = cursor.getInt(cursor.getColumnIndexOrThrow(Books.Book.PRICE))
                stringBuilder.append("id=$id,name=$name,price=$price \n")
            }
            cursor.close()
            Log.e("wdl",stringBuilder.toString())
        }
        btnDelete.setOnClickListener {
            //val index = contentResolver.delete(uri,null,null)
            val index = contentResolver.delete(Books.Book.BOOKS_CONTENT_URI,"price=?", arrayOf("55.2"))

            Log.e("wdl",""+index)
        }
        btnInsert.setOnClickListener {
            val value = ContentValues()
            value.put(Books.Book.NAME,"smwdl")
            value.put(Books.Book.PRICE,50.2)
            val uri = contentResolver.insert(Books.Book.BOOKS_CONTENT_URI,value)
            Log.e("wdl",uri.toString())
        }
        btnUpdate.setOnClickListener {
            val value = ContentValues()
            value.put(Books.Book.PRICE,55.2)
            val uri = ContentUris.withAppendedId(Books.Book.BOOK_CONTENT_URI,1)
            val index = contentResolver.update(uri
                    ,value,"name like ?", arrayOf("smwdl"))
            Log.e("wdl",""+index)
        }
    }
}

注意事項:

  1. 在MyContentPrivoder中實現的CRUD方法,都需要通過uri判斷根據match返回值來執行對應的方法。例如:
switch (matcher.match(uri)) {
            case BOOKS:
                return db.query("book",
                        projection,
                        selection,
                        selectionArgs,
                        null,
                        null, sortOrder);
            case BOOK:
                long id = ContentUris.parseId(uri);
                String whereClause = Books.Book.ID + "=" + id;
                if (!TextUtils.isEmpty(selection)) {
                    whereClause = whereClause + " and " + selection;
                }
                return db.query("book", projection, whereClause, selectionArgs, null, null, sortOrder);
            default:
                throw new IllegalArgumentException("未知uri");
        }

上段程式碼中,對匹配碼做出了判斷,並進行區分處理:Books代表對所有資料進行操作,Book代表對單項資料進行操作。而單項資料進行操作的化需要進行SQL語句的拼接。通常為新增ID。

  1. 在static靜態程式碼塊中需進行UriMatcher的註冊
  2. 許可權宣告,在清單檔案中配置相對應的讀寫許可權。系統自帶的ContentProvider需進行其他許可權的宣告。在6.0之後需動態的申請所需許可權,否則無法進行資料的CRUD
  3. 在insert,update,delete之後都必須進行getContext().getContentResolver().notifyChange(uri, null)進行資料改變的響應