Android點將臺:濟世儒俠[-ContentProvider-]

張風捷特烈發表於2019-02-28

零、前言

本文聚焦
[1]通過簡訊認識ContentProvider的查詢功能
[2]通過圖片查詢瞭解ContentProvider插入、修改、更新、查詢等操作
[3]查詢聯絡人看一下兩個表之間該怎麼辦
[4]簡單看一下Android系統如何實現簡訊的ContentProvider
[5]如何自定義一個ContentProvider,來給別的應用使用
複製程式碼

一、ContentProvider的查詢功能(簡訊為例)

許可權自理:<uses-permission android:name="android.permission.READ_SMS"/>

展示.png


1:實體類
/**
 * 作者:張風捷特烈
 * 時間:2018/4/12:16:46
 * 郵箱:1981462002@qq.com
 * 說明:簡訊實體類
 */
class SMSBean {
    var address: String? = null//簡訊傳送方
    var name: String? = null//號碼在通訊錄中的姓名:無為null
    var date: String? = null//簡訊時間
    var body: String? = null//簡訊內容
    var type: Int = 0//1 接收簡訊 2 傳送簡訊
    var thread_id: Int = 0//同一個手機號互發的簡訊,其序號是相同的
}
複製程式碼

2.查詢處理

ContentProvider查詢操作.png

/**
 * 獲取簡訊:SMSBean:address發信人  date時間  body資訊內容
 *
 * @param ctx 上下文
 * @return 簡訊bean集合 注意新增讀取簡訊許可權
 */
public static List<SMSBean> getSMS(Context ctx) {
    List<SMSBean> smsBeans = new ArrayList<>();
    //[1.]獲得ContentResolver物件
    ContentResolver resolver = ctx.getContentResolver();
    //[2.1]得到Uri :訪問raw_contacts的url
    Uri uri = Uri.parse("content://sms");
    //[3]查詢表,獲得sms表遊標結果集
    String[] projection = {"address", "date", "body", "type", "person", "thread_id"};//訪問表的欄位
    Cursor cursor = resolver.query(
            uri, projection, null, null, null);
    while (cursor.moveToNext()) {//遍歷遊標,獲取資料,儲存在bean中
        SMSBean smsBean = new SMSBean();
        smsBean.setAddress(cursor.getString(cursor.getColumnIndex("address")));
        smsBean.setDate(cursor.getString(cursor.getColumnIndex("date")));
        smsBean.setBody(cursor.getString(cursor.getColumnIndex("body")));
        smsBean.setType(cursor.getInt(cursor.getColumnIndex("type")));
        smsBean.setName(cursor.getString(cursor.getColumnIndex("person")));
        smsBean.setThread_id(cursor.getInt(cursor.getColumnIndex("thread_id")));
        smsBeans.add(smsBean);
    }
    //[4] 關閉cursor
    cursor.close();
    return smsBeans;
}
複製程式碼

讀取簡訊.png


3.關於查詢的五個引數

sql的基礎知識

* @param uri 資源地址
The URI, using the content:// scheme, for the content to retrieve.

* @param projection 想要查詢的列, null查詢所有列,最低效
A list of which columns to return. Passing null will return all columns, which is inefficient.

* @param selection 查詢過濾語句(不用加where)
A filter declaring which rows to return, formatted as an SQL WHERE clause (excluding the WHERE itself).
Passing null will return all rows for the given URI.

* @param selectionArgs 查詢過濾語句中的引數
You may include ?s in selection, which will be replaced by the values from selectionArgs, 
in the order that they appear in the selection. The values will be bound as Strings.

* @param sortOrder 排序
How to order the rows, formatted as an SQL ORDER BY clause (excluding the ORDER BY itself).
Passing null will use the default sort order, which may be unordered.
複製程式碼

下面親測可用,就補貼圖了

|-- 查詢10086發來的資訊
Cursor cursor = resolver.query(
                uri, projection, "address=10086", null, null);

|-- 查詢指定號碼發來的資訊
public static List<SMSBean> getSMSByPhone(Context ctx, String phone) {
    ...
    Cursor cursor = resolver.query(
            uri, projection, "address=?", new String[]{phone}, null);
    ...
}

|-- 按時間倒序排序
Cursor cursor = resolver.query(
        uri, projection, "address=?", new String[]{phone}, "date desc");


|-- 按時間倒序排序 + 取前八條資料
Cursor cursor = resolver.query(
        uri, projection, "address=?", new String[]{phone}, "date desc  limit 0,8");
複製程式碼

二、關於多媒體的ContentProvider操作(圖片為例)

media作為手機的三鼎之一,自然是少不了內容提供者來向外界暴漏資訊
主要儲存在external.db(外部)internal.db(內部)兩個資料庫中
資料庫中圖片的主要欄位有:

_id:id標識             _data: 圖片絕對路徑         _size: 圖片大小            mime_type: 型別
data_added:新增的時間   data_modifide:最後修改時間  _display_name:顯示名稱     description:描述
width:寬                height:高
複製程式碼

media的內容提供者資料庫.png


1.獲取內容提供者並新增一條自定義資訊的圖片
private void insertImg() {
    //1.建立ContentValues物件,記錄插入照片資訊
    ContentValues values = new ContentValues();
    values.put(MediaStore.Images.Media.DISPLAY_NAME, "張風捷特烈");
    values.put(MediaStore.Images.Media.DESCRIPTION, "天下無雙");
    values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
    //2.獲取內容提供者,插入(外部圖片儲存Uri,values),返回插入圖片的Uri
    Uri imgFileUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
    L.d(imgFileUri + L.l());//content://media/external/images/media/1064830
    //3.通過開啟圖片的意圖新增額外資訊將imgFileUri傳送給系統相機
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, imgFileUri);
    startActivity(intent);
}
複製程式碼

2.通過內容提供者讀取剛才插入的圖片
 private void readImg() {
     try {
         //1.獲取內容提供者,通過剛才的Uri開啟輸入流
         Uri imgUri = Uri.parse("content://media/external/images/media/1064830");
         InputStream is = getContentResolver().openInputStream(imgUri);
         //2.將圖片解碼展示
         Bitmap bitmap = BitmapFactory.decodeStream(is);
         mImageView.setImageBitmap(bitmap);
     } catch (FileNotFoundException e) {
         e.printStackTrace();
     }
 }
複製程式碼

3.更新描述資訊
private void updateImg() {
    ContentValues values = new ContentValues(2);
    values.put(MediaStore.Images.Media.DISPLAY_NAME, "Toly");
    values.put(MediaStore.Images.Media.DESCRIPTION, "Only Me");
    //1.獲取內容提供者,通過剛才的Uri開啟輸入流
    Uri imgUri = Uri.parse("content://media/external/images/media/1064830");
    getContentResolver().update(imgUri, values, null, null);
}
複製程式碼

4.查表

既然是內容提供者,玩個表再所難免,驗證上面的修改方法是否成功

private void queryImg() {
    //1.查詢獲得遊標
    Cursor cursor = getContentResolver().query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, "_id=1064830",
            null, null, null);
    //2.讓遊標移動,到尾為止
    while (cursor.moveToNext()) {
        String filePath = cursor.getString(cursor.getColumnIndex("_data"));
        String name = cursor.getString(cursor.getColumnIndex("_display_name"));
        String description = cursor.getString(cursor.getColumnIndex("description"));
        L.d(filePath + L.l());//storage/emulated/0/DCIM/Camera/1539834068417.jpg
        L.d(name + L.l());//Toly   
        L.d(description + L.l());//Only Me   
    }
複製程式碼

5.刪除
private void deleteImg() {
    Uri imgUri = Uri.parse("content://media/external/images/media/1064830");
    int delete = getContentResolver().delete(imgUri, "_id=1064830", null);
    L.d(delete + L.l());//1 表示刪除了1行
}
複製程式碼

6.獲取所有圖片的路徑

一共12540張圖片,方法耗時:1.289秒,屬於耗時操作應該放在子執行緒
可以獲取資料庫中的欄位,封裝一個圖片的實體類,以便使用

private ArrayList<String> queryAllImg() {
    ArrayList<String> imgPaths = new ArrayList<>();
    //1.查詢獲得遊標
    Cursor cursor = getContentResolver().query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, "",
            null, null, null);
    //2.讓遊標移動,到尾為止
    while (cursor.moveToNext()) {
        String filePath = cursor.getString(cursor.getColumnIndex("_data"));
        imgPaths.add(filePath);
    }
    return imgPaths;
}
複製程式碼

查詢所有圖片.png

7.顯示最近100張圖片

為了簡便,使用Picasso來載入圖片--詳情可見:開源框架之[-Picasso-]應用篇

查詢最近100張圖片.png


7.1.獲取最近100條資料庫記錄

排序條件:"date_added desc"表示根據date_added欄位倒序查詢
將資料盛放在List中,並根據列表元素個數來決定跳出while迴圈

private ArrayList<String> queryPic100() {
    ArrayList<String> imgPaths = new ArrayList<>();
    //1.查詢獲得遊標
    String queryCol = MediaStore.Images.Media.DATE_ADDED;
    Cursor cursor = getContentResolver().query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, "",
            null, "date_added desc", null);
    //2.讓遊標移動,到尾為止
    while (cursor.moveToNext()) {
        if (imgPaths.size() >= 100) {
            break;
        }
        String filePath = cursor.getString(cursor.getColumnIndex("_data"));
        imgPaths.add(filePath);
    }
    return imgPaths;
}
複製程式碼

7.2.RecyclerView的簡單使用(佈局很簡單就免了)

1).建立介面卡類和ViewHolder
2).設定RecyclerView樣式

/**
 * 介面卡
 */
class PicRVAdapter extends RecyclerView.Adapter<PicViewHolder> {
    @NonNull
    @Override
    public PicViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(Pic100Activity.this).inflate(R.layout.item_img, null);
        return new PicViewHolder(view);
    }
    @Override
    public void onBindViewHolder(@NonNull PicViewHolder holder, int position) {
        //使用Picasso載入檔案圖片
        Picasso.get().setIndicatorsEnabled(true);
        Picasso.get()
                .load(new File(mList.get(position)))//檔案
                .resize(200,200)//重設尺寸
                .into(holder.mIv_icon);
    }
    @Override
    public int getItemCount() {
        return mList.size();
    }
}

/**
 * ViewHolder
 */
public class PicViewHolder extends RecyclerView.ViewHolder {
    public final ImageView mIv_icon;
    /**
     * itemView為MyViewHolder中onCreateViewHolder載入的佈局
     *
     * @param itemView 條目
     */
    public PicViewHolder(View itemView) {
        super(itemView);
        mIv_icon = itemView.findViewById(R.id.id_iv_item);
    }
}
複製程式碼
//1.設定介面卡
mIdRvPic100.setAdapter(new PicRVAdapter());
//2.!!建立佈局管理器
mGLM = new GridLayoutManager(this, 4, GridLayoutManager.VERTICAL, false);
//3.!!!設定佈局管理器
mIdRvPic100.setLayoutManager(mGLM);
複製程式碼

MediaStore是一個為例方便操作媒體ContentProvider而給出的類,
圖片、音訊、視訊三大頂樑柱都涉及了,所以圖片會操作,那兩個也不在話下

MediaStore.png


三、通訊錄的查詢

許可權自理:<uses-permission android:name="android.permission.READ_CONTACTS"/>

1.實現分析:

raw_contacts表中查到contact_id欄位,在每個contact_id下,根據contact_id查詢data表欄位,
然後判斷mimetype的值,新建實體類,將資料設定到實體中,將實體放入實體集合,查完返回集合。

聯絡人資料庫

聯絡人.png

raw_contacts表關注欄位contact_id

raw_contacts.png

data表mimetype表:關注欄位:mimetype_id 、raw_contact_id 、data1

data.png


2.聯絡人實體類
/**
 * 作者:張風捷特烈<br></br>
 * 時間:2019/2/27/027:19:48<br></br>
 * 郵箱:1981462002@qq.com<br></br>
 * 說明:聯絡人
 */
class ContactBean {
    var name: String? = null//聯絡人姓名
    var address: String? = null//聯絡人地址
    var email: String? = null//聯絡人郵箱
    var phone: String? = null//聯絡人電話
    var photo: Bitmap? = null//聯絡人頭像
}
複製程式碼

3.查詢到ContactBean集合
/**
 * 獲取聯絡人:ContactBean欄位:name姓名  address地址  email郵箱 phone手機號
 *
 * @param ctx 上下文
 * @return ContactBean集合
 */
public static List<ContactBean> getContact(Context ctx) {
    //建立一個容器放結果
    List<ContactBean> contactBeans = new ArrayList<>();
    //[1.]獲得ContentResolver物件
    ContentResolver resolver = ctx.getContentResolver();
    //[2.1]得到Uri :訪問raw_contacts的url
    Uri raw_contactsUri = Uri.parse("content://com.android.contacts/raw_contacts");
    //[2.2]得到Uri ://訪問data的url
    Uri dataUri = Uri.parse("content://com.android.contacts/data");
    //[3]查詢表,獲得raw_contact表遊標結果集
    Cursor raw_contactsCursor = resolver.query(
            raw_contactsUri, new String[]{"contact_id"}, null, null, null);
    //[4]遍歷遊標,獲取資料,儲存在bean中
    while (raw_contactsCursor.moveToNext()) {
        //[4.1]查詢到contact_id
        String contact_id = raw_contactsCursor.getString(0);
        if (contact_id != null) {
            //[4.2]查詢表,獲得data表遊標結果集
            Cursor dataCursor = resolver.query(dataUri,
                    new String[]{"data1", "mimetype"},//注意不是mimetype_id
                    "raw_contact_id=?",
                    new String[]{contact_id}, null);
            ContactBean contactBean = new ContactBean();
            while (dataCursor.moveToNext()) {
                String result = dataCursor.getString(0);
                //[4.4]根據實體類判斷資料,放入實體類中
                String mimetype = dataCursor.getString(1);
                if (mimetype != null) {
                    //[4.3]新建實體類
                    switch (mimetype) {
                        case "vnd.android.cursor.item/phone_v2"://手機號
                            contactBean.setPhone(result);
                            break;
                        case "vnd.android.cursor.item/email_v2"://email
                            contactBean.setEmail(result);
                            break;
                        case "vnd.android.cursor.item/name"://姓名
                            contactBean.setName(result);
                            break;
                        case "vnd.android.cursor.item/postal-address_v2"://地址
                            contactBean.setAddress(result);
                            break;
                    }
                }
            }
            if (contactBean.getPhone() != null) {
                contactBeans.add(contactBean);//加入集合
            }
            //[5.1]關閉data表Cursor
            dataCursor.close();
        }
    }
    //[5.2]關閉raw_contacts表Cursor
    raw_contactsCursor.close();
    return contactBeans;
}
複製程式碼

4.額外說一下獲取聯絡人頭像
/**
 * 根據號碼獲得聯絡人頭像
 *
 * @param ctx    上下文
 * @param number 號碼
 * @return 圖片
 */
public static Bitmap getContactPhoto(Context ctx, String number) {
    Bitmap bmpHead = null;
    ContentResolver resolver = ctx.getContentResolver();//獲得ContentResolver物件
    // 獲得Uri
    Uri uriNumber2Contacts = Uri.parse("content://com.android.contacts/"
            + "data/phones/filter/" + number);
    // 查詢Uri,返回資料集
    Cursor cursorCantacts = resolver.query(uriNumber2Contacts, null, null, null, null);
    // 如果該聯絡人存在
    if (cursorCantacts.getCount() > 0) {
        // 移動到第一條資料
        cursorCantacts.moveToFirst();
        // 獲得該聯絡人的contact_id
        Long contactID = cursorCantacts.getLong(cursorCantacts.getColumnIndex("contact_id"));
        // 獲得contact_id的Uri
        Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactID);
        // 開啟頭像圖片的InputStream
        InputStream input = ContactsContract.Contacts.openContactPhotoInputStream(resolver, uri);
        // 從InputStream獲得bitmap
        bmpHead = BitmapFactory.decodeStream(input);
    }
    return bmpHead;
複製程式碼

下面是以前做的小專案,用到的聯絡人獲取

Android點將臺:濟世儒俠[-ContentProvider-]

相信到這裡,你應道知道ContentProvider有什麼用了吧。
接下來看一下簡訊的ContentProvider在Android系統中是怎樣實現的。


四、簡訊的ContentProvider在Android中

看了一下,就這個還hold住,其他幾個都要命的長。如果你下載了Android原始碼,可見:
原始碼目錄\packages\providers\TelephonyProvider\src\com\android\providers\telephony\SmsProvider.java
一共不到900行

1.成員變數

從這裡可以看到uri和資料庫表的相關欄位

public class SmsProvider extends ContentProvider {
    private static final Uri NOTIFICATION_URI = Uri.parse("content://sms");
    private static final Uri ICC_URI = Uri.parse("content://sms/icc");
    static final String TABLE_SMS = "sms";
    static final String TABLE_RAW = "raw";
    private static final String TABLE_SR_PENDING = "sr_pending";
    private static final String TABLE_WORDS = "words";
    static final String VIEW_SMS_RESTRICTED = "sms_restricted";

    private static final Integer ONE = Integer.valueOf(1);

    private static final String[] CONTACT_QUERY_PROJECTION =
            new String[] { Contacts.Phones.PERSON_ID };
    private static final int PERSON_ID_COLUMN = 0;

    /**
     * These are the columns that are available when reading SMS
     * messages from the ICC.  Columns whose names begin with "is_"
     * have either "true" or "false" as their values.
       這些列在從ICC讀取SMS訊息時可用。名稱以“is_”開頭的列的值要麼為“true”,要麼為“false”。
     */
    private final static String[] ICC_COLUMNS = new String[] {
        // N.B.: 這些列的出現順序必須與要新增的呼叫在convertIccToSms中出現的順序相同。
        "service_center_address",       // getServiceCenterAddress
        "address",                      // getDisplayOriginatingAddress
        "message_class",                // getMessageClass
        "body",                         // getDisplayMessageBody
        "date",                         // getTimestampMillis
        "status",                       // getStatusOnIcc
        "index_on_icc",                 // getIndexOnIcc
        "is_status_report",             // isStatusReportMessage
        "transport_type",               // Always "sms".
        "type",                         // Always MESSAGE_TYPE_ALL.
        "locked",                       // Always 0 (false).
        "error_code",                   // Always 0
        "_id"
    };
    
--->private SQLiteOpenHelper mOpenHelper;//資料庫操作類

    private final static String TAG = "SmsProvider";
    private final static String VND_ANDROID_SMS = "vnd.android.cursor.item/sms";
    private final static String VND_ANDROID_SMSCHAT =
            "vnd.android.cursor.item/sms-chat";
    private final static String VND_ANDROID_DIR_SMS =
            "vnd.android.cursor.dir/sms";

    private static final String[] sIDProjection = new String[] { "_id" };
---->操作識別符號--------------------------------------------
    private static final int SMS_ALL = 0;
    private static final int SMS_ALL_ID = 1;
    private static final int SMS_INBOX = 2;
    private static final int SMS_INBOX_ID = 3;
    private static final int SMS_SENT = 4;
    private static final int SMS_SENT_ID = 5;
    private static final int SMS_DRAFT = 6;
    private static final int SMS_DRAFT_ID = 7;
    private static final int SMS_OUTBOX = 8;
    private static final int SMS_OUTBOX_ID = 9;
    private static final int SMS_CONVERSATIONS = 10;
    private static final int SMS_CONVERSATIONS_ID = 11;
    private static final int SMS_RAW_MESSAGE = 15;
    private static final int SMS_ATTACHMENT = 16;
    private static final int SMS_ATTACHMENT_ID = 17;
    private static final int SMS_NEW_THREAD_ID = 18;
    private static final int SMS_QUERY_THREAD_ID = 19;
    private static final int SMS_STATUS_ID = 20;
    private static final int SMS_STATUS_PENDING = 21;
    private static final int SMS_ALL_ICC = 22;
    private static final int SMS_ICC = 23;
    private static final int SMS_FAILED = 24;
    private static final int SMS_FAILED_ID = 25;
    private static final int SMS_QUEUED = 26;
    private static final int SMS_UNDELIVERED = 27;
--------------------------------------------------------
    private static final UriMatcher sURLMatcher =
            new UriMatcher(UriMatcher.NO_MATCH);

--->static {//靜態程式碼塊定義可操作項
--->    sURLMatcher.addURI("sms", null, SMS_ALL);//null是,表示全部
        sURLMatcher.addURI("sms", "#", SMS_ALL_ID);
        sURLMatcher.addURI("sms", "inbox", SMS_INBOX);
        sURLMatcher.addURI("sms", "inbox/#", SMS_INBOX_ID);
        sURLMatcher.addURI("sms", "sent", SMS_SENT);
        sURLMatcher.addURI("sms", "sent/#", SMS_SENT_ID);
        sURLMatcher.addURI("sms", "draft", SMS_DRAFT);
        sURLMatcher.addURI("sms", "draft/#", SMS_DRAFT_ID);
        sURLMatcher.addURI("sms", "outbox", SMS_OUTBOX);
        sURLMatcher.addURI("sms", "outbox/#", SMS_OUTBOX_ID);
        sURLMatcher.addURI("sms", "undelivered", SMS_UNDELIVERED);
        sURLMatcher.addURI("sms", "failed", SMS_FAILED);
        sURLMatcher.addURI("sms", "failed/#", SMS_FAILED_ID);
        sURLMatcher.addURI("sms", "queued", SMS_QUEUED);
        sURLMatcher.addURI("sms", "conversations", SMS_CONVERSATIONS);
        sURLMatcher.addURI("sms", "conversations/*", SMS_CONVERSATIONS_ID);
        sURLMatcher.addURI("sms", "raw", SMS_RAW_MESSAGE);
        sURLMatcher.addURI("sms", "attachments", SMS_ATTACHMENT);
        sURLMatcher.addURI("sms", "attachments/#", SMS_ATTACHMENT_ID);
        sURLMatcher.addURI("sms", "threadID", SMS_NEW_THREAD_ID);
        sURLMatcher.addURI("sms", "threadID/*", SMS_QUERY_THREAD_ID);
        sURLMatcher.addURI("sms", "status/#", SMS_STATUS_ID);
        sURLMatcher.addURI("sms", "sr_pending", SMS_STATUS_PENDING);
        sURLMatcher.addURI("sms", "icc", SMS_ALL_ICC);
        sURLMatcher.addURI("sms", "icc/#", SMS_ICC);
        //we keep these for not breaking old applications
        sURLMatcher.addURI("sms", "sim", SMS_ALL_ICC);
        sURLMatcher.addURI("sms", "sim/#", SMS_ICC);
    }
複製程式碼

2. onCreate()方法

這裡獲取了一個MmsSmsDatabaseHelper的資料庫操作類

@Override
    public boolean onCreate() {
        setAppOps(AppOpsManager.OP_READ_SMS, AppOpsManager.OP_WRITE_SMS);
        mOpenHelper = MmsSmsDatabaseHelper.getInstance(getContext());
        return true;
    }
複製程式碼

3.查詢方法
@Override
    public Cursor query(Uri url, String[] projectionIn, String selection,
            String[] selectionArgs, String sort) {

        final boolean accessRestricted = ProviderUtil.isAccessRestricted(
                getContext(), getCallingPackage(), Binder.getCallingUid());
        final String smsTable = getSmsTable(accessRestricted);
        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        // Generate the body of the query.---生成查詢的主體。
        int match = sURLMatcher.match(url);//獲取操作識別符號
        switch (match) {//下面根據情況進行查詢
            case SMS_ALL:
                constructQueryForBox(qb, Sms.MESSAGE_TYPE_ALL, smsTable);
                break;
            case SMS_UNDELIVERED:
                constructQueryForUndelivered(qb, smsTable);
                break;
            case SMS_FAILED:
                constructQueryForBox(qb, Sms.MESSAGE_TYPE_FAILED, smsTable);
                break;
            case SMS_QUEUED:
                constructQueryForBox(qb, Sms.MESSAGE_TYPE_QUEUED, smsTable);
                break;
            case SMS_INBOX:
                constructQueryForBox(qb, Sms.MESSAGE_TYPE_INBOX, smsTable);
                break;
            case SMS_SENT:
                constructQueryForBox(qb, Sms.MESSAGE_TYPE_SENT, smsTable);
                break;
            ...略
        }

        String orderBy = null;

        if (!TextUtils.isEmpty(sort)) {//如果排序非空
            orderBy = sort;//賦值
        } else if (qb.getTables().equals(smsTable)) {
            orderBy = Sms.DEFAULT_SORT_ORDER;//使用預設的排序
        }

--->    SQLiteDatabase db = mOpenHelper.getReadableDatabase();//獲得SQLiteDatabase
--->    Cursor ret = qb.query(db, projectionIn, selection, selectionArgs,
                              null, null, orderBy);//資料庫的查詢操作

        ret.setNotificationUri(getContext().getContentResolver(),
                NOTIFICATION_URI);
        return ret;//將查詢到的Cursor返回出去
    }
    
private void constructQueryForBox(SQLiteQueryBuilder qb, int type, String smsTable) {
    qb.setTables(smsTable);

    if (type != Sms.MESSAGE_TYPE_ALL) {//如果不是All,就根據傳入的型別查詢
        qb.appendWhere("type=" + type);
    }
}
複製程式碼

4.刪除方法
    @Override
    public int delete(Uri url, String where, String[] whereArgs) {
        int count;
        int match = sURLMatcher.match(url);
--->    SQLiteDatabase db = mOpenHelper.getWritableDatabase();//獲取SQLiteDatabase
        switch (match) {//根據分支處理
            case SMS_ALL:
                count = db.delete(TABLE_SMS, where, whereArgs);//執行刪除語句
                if (count != 0) {
                    // Don't update threads unless something changed.
                    MmsSmsDatabaseHelper.updateAllThreads(db, where, whereArgs);
                }
                break;

            case SMS_ALL_ID:
                ...
                break;

            case SMS_CONVERSATIONS_ID:
                ...
                break;

            case SMS_RAW_MESSAGE:
                count = db.delete("raw", where, whereArgs);//執行刪除語句
                break;

            case SMS_STATUS_PENDING:
                count = db.delete("sr_pending", where, whereArgs);//執行刪除語句
                break;

            case SMS_ICC:
                String messageIndexString = url.getPathSegments().get(1);
                return deleteMessageFromIcc(messageIndexString);

            default:
                throw new IllegalArgumentException("Unknown URL");
        }
        if (count > 0) {
            notifyChange(url);
        }
        return count;
    }
複製程式碼

就分析這兩個吧,可見,就是根據Uri對應不同的操作, 核心還是SQLiteDatabase的資料庫的操作,ContentProvider只是封裝一下,並暴露給所有人


5.最最重要的一點不要忘記要配置一下

配置.png


五、自定義ContentProvider:SwordProvider

這個應該很少用,不是系統級的應用提供的資料你敢用?這裡稍作了解
下面進入Sqlite資料庫相關,非戰鬥人員火速備瓜...還是拿《萬界神兵錄》的表來看吧

|--- 表分析:
資料庫名:  weapon
表名:      sworld
欄位: 
id      id      INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL
名稱    name    VARCHAR(32) NOT NULL
攻擊力  atk     SMALLINT UNSIGNED DEFAULT 1000
持有人  user    VARCHAR(32) NOT NULL

|--- 建表語句
CREATE TABLE sword (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
name VARCHAR(32) NOT NULL,
atk SMALLINT UNSIGNED NOT NULL DEFAULT 1000,
user VARCHAR(32) NOT NULL,
); 

複製程式碼

1.建立類繼承自ContentProvider

ContentProvider是一個抽象類,需要實現下面幾個方法

/**
 * 作者:張風捷特烈<br/>
 * 時間:2019/2/28/028:11:42<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:萬界神兵錄
 */
public class SwordProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        return false;
    }
    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        return null;
    }
    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }
    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        return null;
    }
    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
}
複製程式碼

2.資料庫相關
2.1--生成資料庫

我是根據MmsSmsDatabaseHelper原始碼來寫的,畢竟是大佬寫的,參考一下書寫風格

生成資料庫.png

public class SwordDatabaseHelper extends SQLiteOpenHelper {
    private static String DATABASE_NAME = "weapon.db";//資料庫名
    private static int DATABASE_VERSION = 1;//資料庫版本
    private static SwordDatabaseHelper sInstance;
    //雙檢鎖單例
    public static synchronized SwordDatabaseHelper getInstance(Context context) {
        if (sInstance == null) {
            synchronized (SwordDatabaseHelper.class) {
                if (sInstance == null) {
                    sInstance = new SwordDatabaseHelper(context);
                }
            }
        }
        return sInstance;
    }
    public SwordDatabaseHelper(@Nullable Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        createSwordTable(db);
    }
    /**
     * 建立sword表
     *
     * @param db SQLiteDatabase
     */
    private void createSwordTable(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE sword (" +
                "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
                "name VARCHAR(32) NOT NULL," +
                "atk INTEGER  DEFAULT 1000," +
                "user VARCHAR(32) NOT NULL" +
                "); ");
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}
複製程式碼

2.CURD測試--增刪改查

也就是sql的基礎語法

sql操作測試.png

SwordDatabaseHelper helper = new SwordDatabaseHelper(this);
SQLiteDatabase db = helper.getWritableDatabase();
|-- 插入測試
db.execSQL("INSERT INTO sword(name,atk,user) VALUES" +
                    "('絕世好劍',7000,'步驚雲')");
|-- 修改測試                 
db.execSQL("UPDATE sword SET atk=3500 WHERE id=1");

|-- 查詢測試
Cursor cursor = db.rawQuery("SELECT * FROM sword", null);
while (cursor.moveToNext()) {
    String id = cursor.getString(cursor.getColumnIndex("id"));
    String name = cursor.getString(cursor.getColumnIndex("name"));
    String atk = cursor.getString(cursor.getColumnIndex("atk"));
    String user = cursor.getString(cursor.getColumnIndex("user"));
    Log.e(TAG, "rawQuery: "+id + "---" + name + "---" + atk + "---" + user );
    //rawQuery: 1---絕世好劍---3500---步驚雲
}
cursor.close();//關閉遊標

|-- 刪除測試
db.execSQL("DELETE FROM sword WHERE id=1");
複製程式碼

3.SwordDatabaseHelper在ContentProvider中的使用

既然測試ok,那就去實現一下ContentProvider的幾個方法,uri就設定為增刪改查四個...
規則可以自己設定,在方法裡都可以根據sUriMatcher.match(uri)獲取對應碼來區別處理

/**
 * 作者:張風捷特烈<br/>
 * 時間:2019/2/28/028:11:42<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:萬界神兵錄
 */
public class SwordProvider extends ContentProvider {
    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    private static final int SWORD_QUERY = 0;
    private static final int SWORD_INSERT = 1;
    private static final int SWORD_UPDATE = 2;
    private static final int SWORD_DELETE = 3;

    private static final String TABLE_NAME = "sword";

    static {
        //給當前sUriMatcher新增匹配規則
        sUriMatcher.addURI("toly1994.com.sword", "query", SWORD_QUERY);
        sUriMatcher.addURI("toly1994.com.sword", "insert", SWORD_INSERT);
        sUriMatcher.addURI("toly1994.com.sword", "update", SWORD_UPDATE);
        sUriMatcher.addURI("toly1994.com.sword", "delete", SWORD_DELETE);
    }

    private SQLiteOpenHelper mOpenHelper;

    @Override
    public boolean onCreate() {
        mOpenHelper = SwordDatabaseHelper.getInstance(getContext());
        return true;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        //進行uri匹配
        int result = sUriMatcher.match(uri);
        if (result == SWORD_QUERY) {
            SQLiteDatabase db = mOpenHelper.getReadableDatabase();
            return db.query(TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);

        } else {
            throw new IllegalStateException(" query Uri 錯誤");
        }
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        //進行uri匹配
        int result = sUriMatcher.match(uri);
        if (result == SWORD_INSERT) {
            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
            Long insert = db.insert(TABLE_NAME, null, values);
            //uri:資料傳送變化,通過uri判斷呼叫哪個內容觀察者
            //第二個引數:內容觀察者物件  如果傳null 則註冊了整個uri的內容觀察者皆可以收到通知
            getContext().getContentResolver().notifyChange(uri, null);
            db.close();
            return Uri.parse(String.valueOf(insert));
        } else {
            throw new IllegalStateException("insert Uri 錯誤");
        }
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        //進行uri匹配
        int result = sUriMatcher.match(uri);
        if (result == SWORD_DELETE) {
            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
            int delete = db.delete(TABLE_NAME, selection, selectionArgs);
            db.close();
            return delete;
        } else {
            throw new IllegalStateException("delete Uri  錯誤");
        }
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        //進行uri匹配
        int result = sUriMatcher.match(uri);
        if (result == SWORD_UPDATE) {
            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
            int update = db.update(TABLE_NAME, values, selection, selectionArgs);
            db.close();
            return update;
        } else {
            throw new IllegalStateException("update Uri 錯誤");
        }
    }
}
複製程式碼

4.配置
<provider
        android:name=".provider.swordProvider.SwordProvider"
        android:authorities="toly1994.com.sword"
        android:exported="true"/>
複製程式碼

你應該清楚,上面就相當於系統的SmsProvider,用於提供一個可供全域性操作的資料庫


5.現在到==另一個app==裡進行測試

經測試,是可用的,也就是另一個app可以操作剛才應用中的資料庫

測試.png

/**
 * 作者:張風捷特烈<br/>
 * 時間:2019/2/27/027:19:52<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:另一個app測試SwordProvider
 */
public class SwordProviderActivity extends AppCompatActivity {
    private static final String TAG = "SwordProviderActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ContentResolver resolver = getContentResolver();
//        insert(resolver); //插入測試
//        query(resolver);//查詢測試
        update(resolver);//更新測試
        query(resolver);
        delete(resolver);//刪除測試
        query(resolver);
    }
    /**
     * 刪除測試
     *
     * @param resolver
     */
    private void delete(ContentResolver resolver) {
        Uri uri = Uri.parse("content://toly1994.com.sword/delete");
        resolver.delete(uri, "name=?", new String[]{"屠龍刀"});
    }
    /**
     * 插入測試
     *
     * @param resolver
     */
    private void insert(ContentResolver resolver) {
        Uri uri = Uri.parse("content://toly1994.com.sword/insert");
        ContentValues values = new ContentValues();
        values.put("name", "屠龍刀");
        values.put("atk", "3000");
        values.put("user", "張無忌");
        resolver.insert(uri, values);
    }
    /**
     * 更新測試
     *
     * @param resolver
     */
    private void update(ContentResolver resolver) {
        Uri uri = Uri.parse("content://toly1994.com.sword/update");
        ContentValues values = new ContentValues();
        values.put("name", "屠龍刀");
        values.put("atk", "3500");
        values.put("user", "張無忌");
        resolver.update(uri, values, "name=?", new String[]{"屠龍刀"});
    }
    /**
     * 查詢測試
     *
     * @param resolver
     */
    private void query(ContentResolver resolver) {
        Uri uri = Uri.parse("content://toly1994.com.sword/query");
        Cursor cursor = resolver.query(uri, null, null, null, null);
        while (cursor.moveToNext()) {//遍歷遊標,獲取資料,儲存在bean中
            int id = cursor.getInt(cursor.getColumnIndex("id"));
            String name = cursor.getString(cursor.getColumnIndex("name"));
            int atk = cursor.getInt(cursor.getColumnIndex("atk"));
            String user = cursor.getString(cursor.getColumnIndex("user"));
            Log.e(TAG, "query: " + id + "---" + name + "---" + atk + "---" + user);
            //query: 2---屠龍刀---3000---張無忌
        }
    }
}
複製程式碼

至於ContentProvider的內部實現原理暫時還沒有興趣,未提上日程
好了,到此為止,安卓的四大元件就重新總結了一遍,這是第二次終結
四大元件的幾篇文章都用一個工程測試的,Github地址:歡迎star


後記:捷文規範

1.本文成長記錄及勘誤表
專案原始碼 日期 附錄
V0.1--無 2018-2-28

釋出名:Android點將臺:濟世儒俠[-ContentProvider-]
捷文連結:juejin.im/post/5c7785…

2.更多關於我
筆名 QQ 微信
張風捷特烈 1981462002 zdl1994328

我的github:github.com/toly1994328
我的簡書:www.jianshu.com/u/e4e52c116…
我的簡書:www.jianshu.com/u/e4e52c116…
個人網站:www.toly1994.com

3.宣告

1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援

icon_wx_200.png

相關文章