1. 程式人生 > >Android基礎筆記(十三)- 內容提供者原理和簡單使用

Android基礎筆記(十三)- 內容提供者原理和簡單使用

暴露 tel java 四大組件 per 存儲 建數據庫 開發 fun

  • 為什麽要有內容提供者
  • 內容提供者的工作原理
  • 使用內容解析者對內容提供者進行增刪改查操作
  • 利用內容提供者和內容解析者備份手機短信
  • 利用內容提供者插入短信

技術分享

為什麽要有內容提供者

內容提供者技術的目的是:
把私有數據庫的數據的內容暴露給外部使用;

我們知道,微信、QQ等應用都能夠讀取手機中聯系人和短信的數據。而聯系人和短信都是系統內置的應用,它們的數據都存儲在相應的數據庫中。

com.android.provider.telephony/databases/mmssms.db就是短信的數據庫,通過以下的圖能夠知道mmssms.db的權限為-rw-rw---

。也就是其它用戶不能夠直接訪問。是私有的。
技術分享

相同的com.android.provider.contacts/databases/contact2.db是聯系人的數據庫。相同也是私有的權限。
技術分享

既然它們都是私有的。可是又有需求獲取裏面的數據應該怎麽辦?Google提供者了內容提供者的技術,就能夠解決這一需求。

內容提供者的工作原理

由於數據庫是私有的,外部應用不能夠直接訪問。那麽便在應用內部使用內容提供者,向外部暴露接口,這樣外部能夠借助“中間人”訪問到內部的數據庫。原理圖例如以下:
技術分享

內容提供者的編寫過程例如以下:
①創建數據庫、表。由於內容提供者主要是把私有數據庫數據提供給外部訪問。
②寫一個類繼承ContentProvider類,並重寫當中的onCreate()query()insert()update()delete()getType()等方法。
③確定主機名(authority),加入路徑匹配規則。此處會利使用到UriMatcher類。
④在清單文件裏聲明內容提供者。並加入關鍵屬性android:authorities
⑤編寫內容提供者的onCreate()query()insert()update()delete()
等方法。

接下來就一步一步把全部步驟說清。

第一步,創建數據庫、表。非常easy寫一個繼承SQliteOpenHelper

得類,並創建數據庫。代碼例如以下:

public class MySQLiteOpenHelper extends SQLiteOpenHelper {

    public MySQLiteOpenHelper(Context context) {super(context, "account.db", null, 1);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        // 創建數據庫,並初始化兩條語句
        db.execSQL("create table account (_id integer primary key autoincrement,name varchar(20),money varchar(20))");
        db.execSQL("insert into account (‘name‘,‘money‘) values (‘張三‘,‘2000‘)");
        db.execSQL("insert into account (‘name‘,‘money‘) values (‘李四‘,‘5000‘)");
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  }
}

第二步,寫一個類繼承ContentProvider類,並重寫當中方法。主要都是一些骨架的代碼。也沒什麽難度。

public class CopyOfAccountContentProvider extends ContentProvider {
    // 內容提供者初始化的時候調用
    @Override
    public boolean onCreate() {
        return true;
    }
    // 提供給外部應用調用的查詢方法
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        return null;
    }
    // 提供給外部應用調用的添加方法
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }
    // 提供給外部應用調用的刪除方法
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }
    // 提供給外部應用調用的更新方法
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }
    @Override
    public String getType(Uri uri) {
        // 沒有什麽用,此處就不寫了
        return null;
    }
}

第三步,從第三步開始就比較關鍵了。要確定主機名(authority),加入路徑匹配規則,此處是使用內容解析者時。調用其它應用私有數據庫所使用的。代碼例如以下。對了這些代碼是寫在內容提供者類中的

public static final int QUERY_SUCCESS = 0;

public static final int UPDATE_SUCCESS = 1;

public static final int DELETE_SUCCESS = 2;

public static final int INSERT_SUCCESS = 3;

// UriMatcher是一個工具類。用於幫助內容提供者匹配URIs
private static final UriMatcher MURI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);

// 加入路徑匹配規則
static {
    // 加入一個path(匹配規則),假設匹配成功會返回code值。
    // authority:主機名(我們能夠自定義。任意寫,當使用內容解析者時會使用到)
    // path:路徑。通常是操作名稱
    // code:響應碼
    MURI_MATCHER.addURI("com.bzh.account.contentprovider", "insert", INSERT_SUCCESS);
    MURI_MATCHER.addURI("com.bzh.account.contentprovider", "delete", DELETE_SUCCESS);
    MURI_MATCHER.addURI("com.bzh.account.contentprovider", "update", UPDATE_SUCCESS);
    MURI_MATCHER.addURI("com.bzh.account.contentprovider", "query", QUERY_SUCCESS);
}

第四步。內容提供者是四大組件之中的一個,四大組件都須要在清單文件裏聲明。在清單文件裏聲明內容提供者。並加入關鍵屬性android:authorities,這個屬性的內容和加入路徑匹配規則addURI()的第一個參數必須一致。代碼例如以下:

<provider
    android:name="com.bzh.contentprovider.AccountContentProvider"
    android:authorities="com.bzh.account.contentprovider" >
</provider>

第五步,編寫內容提供者的onCreate()query()insert()update()delete()
等方法。這些方法的內容都是使用SQLiteDatabase內部的方法直接調用,也比較簡單。

private SQLiteDatabase db;
private static final String ACCOUNT = "account";

// 內容提供者初始化的時候調用
@Override
public boolean onCreate() {
    MySQLiteOpenHelper mySQLiteOpenHelper = new MySQLiteOpenHelper(getContext());
    db = mySQLiteOpenHelper.getWritableDatabase();
    return true;
}

// 提供給外部應用調用的查詢方法
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
    int MATCH_CODE = MURI_MATCHER.match(uri);
    // 推斷路徑是否匹配成功
    if (MATCH_CODE == QUERY_SUCCESS) {
        // 調用SQLiteDatabase中的方法查詢私有的數據庫
        return db.query(ACCOUNT, projection, selection, selectionArgs, null, null, null);
    }
    return null;
}

// 提供給外部應用調用的添加方法
@Override
public Uri insert(Uri uri, ContentValues values) {
    int MATCH_CODE = MURI_MATCHER.match(uri);
    // 推斷路徑是否匹配成功
    if (MATCH_CODE == INSERT_SUCCESS) {
        long insertResult = db.insert(ACCOUNT, null, values);
        // 此處的Uri能夠任意寫
        return Uri.parse("com.bzh.account.contentprovider-result:" + insertResult);
    }
    return null;
}

// 提供給外部應用調用的刪除方法
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
    int MATCH_CODE = MURI_MATCHER.match(uri);
    // 推斷路徑是否匹配成功
    if (MATCH_CODE == DELETE_SUCCESS) {
        return db.delete(ACCOUNT, selection, selectionArgs);
    }
    return 0;
}

// 提供給外部應用調用的更新方法
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    int MATCH_CODE = MURI_MATCHER.match(uri);
    // 推斷路徑是否匹配成功
    if (MATCH_CODE == UPDATE_SUCCESS) {
        return db.update(ACCOUNT, values, selection, selectionArgs);
    }
    return 0;
}

@Override
public String getType(Uri uri) {
    // 沒有什麽用。此處就不寫了
    return null;
}

至此,整個內容提供者就編寫完成了,接下就是怎樣使用了,詳細怎樣使用將在下一節講述。

使用內容解析者對內容提供者進行增刪改查操作

回憶一下上面的原理圖,訪問私有數據庫的內容提供者已經準備完成,家下來是使用內容解析者間接調用數據庫內容的時候了。

相對於創建內容提供者而言,使用解析者調用提供者提供的方法就簡單非常多了。

創建一個工程,並加入幾個增刪改查的button,布局之類的比較簡單不貼了。請看圖:
技術分享

接下來要做的事情就是。當點擊button時調用內容提供者相應的方法。當中最值得註意的是Uri.parse()內容的寫法。

Uri uri = Uri.parse("content://com.bzh.account.contentprovider/query");

對於這句內容而言,主要由三部分組成–①content://②主機地址/③路徑,而這部分是固定的寫法,當中主機地址是內容提供者清單文件裏聲明的authorities屬性;而路徑是內容提供者中UriMatcher.addURI()第二個參數相應的值。

以下是代碼:

public void insert(View v) {
    // 獲取到內容解析者
    ContentResolver resolver = getContentResolver();

    // 匹配路徑和數據
    Uri url = Uri.parse("content://com.bzh.account.contentprovider/insert");
    ContentValues values = new ContentValues();
    values.put("name", "別誌華");
    values.put("money", "999");

    // 使用解析者借助內容提供者向私有數據庫插數據
    Uri insert = resolver.insert(url, values);
    System.out.println("數據插入---結果:" + insert.toString());
}

public void delete(View v) {
    // 獲取到內容解析者
    ContentResolver resolver = getContentResolver();

    // 匹配路徑和數據
    Uri url = Uri.parse("content://com.bzh.account.contentprovider/delete");

    // 使用解析者借助內容提供者向私有數據庫插數據
    int delete = resolver.delete(url, "name=?

", new String[] { "別誌華" }); System.out.println("刪除數據---影響行數:" + delete); } public void update(View v) { // 獲取到內容解析者 ContentResolver resolver = getContentResolver(); // 匹配路徑和數據 Uri uri = Uri.parse("content://com.bzh.account.contentprovider/update"); ContentValues values = new ContentValues(); values.put("money", "999"); // 使用解析者借助內容提供者向私有數據庫插數據 int update = resolver.update(uri, values, "name=?

", new String[] { "別誌華" }); System.out.println("更新數據---影響行數:" + update); } public void query(View v) { // 獲取內容解析者 ContentResolver resolver = getContentResolver(); // com.bzh.account.contentprovider是在上一個應用清單文件裏authorities的屬性值 // 固定寫法:content://主機地址/路徑 Uri uri = Uri.parse("content://com.bzh.account.contentprovider/query"); // 調用內容解析者的查詢方法 Cursor cursor = resolver.query(uri, null, null, null, null); // 拿出遊標中的數據 if (cursor != null && cursor.getCount() > 0) { while (cursor.moveToNext()) { String name = cursor.getString(1); String money = cursor.getString(2); System.out.println("查詢數據---數據內容(姓名:" + name + ",錢:" + money + ")"); } // 關閉遊標 cursor.close(); } }

測試圖例如以下:
技術分享

到此為止,怎樣定義一個內容提供者和怎樣使用內容提供者已經解說完了。

利用內容提供者和內容解析者備份手機短信

像“微信電話薄”應用。能夠讀取手機中的短信數據。我們知道,手機中的短信應用內部維護者一個數據庫,用於存儲短信數據。

而“微信電話薄”就是讀取系統短信應用的數據來達到自己的目的的,可是系統短信應用的數據庫是私有的。那麽外部的應用怎樣拿到私有數據庫的數據?你想的沒錯。Google工程師在短信應用中為我們提供了內容提供者,供我們開發者使用。

在上一節中,我們知道想要使用其它應用提供的內容提供者,須要知道主機名路徑。那麽從哪裏獲取這些信息呢?這就須要去查看源代碼了。我這裏查看的是4.4的源代碼。源代碼的路徑為android4.4\packages\providers\TelephonyProvider

文件夾結構例如以下圖:
技術分享

在上一節中。寫自己的提供提供者時都是在清單文件裏聲明內容提供者。並聲明主機名。讓我們來看一下吧。

<provider android:name="SmsProvider"
          android:authorities="sms"
          android:multiprocess="false"
          android:exported="true"
          android:readPermission="android.permission.READ_SMS"
          android:writePermission="android.permission.WRITE_SMS" />

主機名是sms。而且從中可一知道使用該內容提供者,須要提供讀取短信和寫短信的權限。

知道了主機名,接下來就是去查看路徑了。找到src\com\android\providers\telephony\SmsProvider.java文件,並搜索UriMatcher關鍵字,能夠得到例如以下的結果:

private static final UriMatcher sURLMatcher =
            new UriMatcher(UriMatcher.NO_MATCH);
  static {
      sURLMatcher.addURI("sms", null, SMS_ALL);
      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);
      ... ... ...
  }

知道了主機名和路徑後。在看一下短信數據庫的表結構,要不然我們怎麽獲取數據!從中能夠發如今sms表中addressdatebody分別代表著發送人、時間、短信內容;
技術分享

接下來。使用內容解析者查詢數據並使用XML序列化器,把數據序列化到XML文件裏。短信備份就算完成了,代碼例如以下:

// 內容解析者
ContentResolver resolver = getContentResolver();

Uri uri = Uri.parse("content://sms");

Cursor cursor = resolver.query(uri, new String[] { "address", "date", "body" }, null, null, null);

if (cursor != null && cursor.getCount() > 0) {

    // XML序列化器
    XmlSerializer serializer = Xml.newSerializer();
    FileOutputStream fos;
    try {
        fos = new FileOutputStream(new File(Environment.getExternalStorageDirectory().getPath() + "/backsms.xml"));
        // 初始化XML序列化器參數
        serializer.setOutput(fos, "UTF-8");

        // 文檔開始
        serializer.startDocument("UTF-8", true);

        // 根節點開始
        serializer.startTag(null, "smss");

        while (cursor.moveToNext()) {

            // 元素結點開始
            serializer.startTag(null, "sms");

            // 序列化發送人
            serializer.startTag(null, "address");
            String address = cursor.getString(0);
            serializer.text(address);
            serializer.endTag(null, "address");

            serializer.startTag(null, "date");
            String date = cursor.getString(1);
            serializer.text(date);
            serializer.endTag(null, "date");

            serializer.startTag(null, "body");
            String body = cursor.getString(2);
            serializer.text(body);
            serializer.endTag(null, "body");

            System.out.println("短信發送人:" + address + ",日期:" + date + ",內容:" + body);

            serializer.endTag(null, "sms");
        }

        serializer.endTag(null, "smss");
        serializer.endDocument();
    } catch (Exception e) {
        e.printStackTrace();
    }

    cursor.close();
}

利用內容提供者插入短信

做完備份短信,再做一個插入短信玩玩吧。

首先提供一個界面:
技術分享

代碼也比較簡單:

String body = et_body.getText().toString().trim();
String sender = et_sender.getText().toString().trim();

// 拿到內容解析者
ContentResolver resolver = getContentResolver();

Uri uri = Uri.parse("content://sms");
ContentValues values = new ContentValues();
values.put("address", sender);
values.put("body", body);

// 插入數據
resolver.insert(uri, values);

測試結果:
技術分享

利用提供者和解析這備份和插入短信還是比較簡單的,有難度的在於備份聯系人和插入聯系人。這些將在下一個博客講述。

Android基礎筆記(十三)- 內容提供者原理和簡單使用