Android中的內(nèi)容提供者

Android中的內(nèi)容提供者

為什么需要內(nèi)容提供者

為了跨程序訪問數(shù)據(jù)训枢。試想如果在App-1中創(chuàng)建了一個(gè)私有數(shù)據(jù)庫,App-2是不能直接訪問的迹缀。因?yàn)闄?quán)限不夠瓦糕,雖然可以使用chmod 777來修改權(quán)限胁艰,然后使用SQLiteDatabase.openDatabase的靜態(tài)方法款筑,填上具體的路徑和模式來訪問。但這并不推薦腾么,有沒有更好的辦法醋虏?官方推薦使用ContentProvider--內(nèi)容提供者。

創(chuàng)建內(nèi)容提供者

簡(jiǎn)單起見哮翘,使用以前的數(shù)據(jù)庫的項(xiàng)目DatabaseTest,同時(shí)建立兩個(gè)表book和category毛秘, onUpgrade方法實(shí)現(xiàn)了數(shù)據(jù)庫的升級(jí)功能饭寺。onUpgrade里面強(qiáng)制onCreate,注意必須先刪除原來的表叫挟,否則我們創(chuàng)建時(shí)候發(fā)現(xiàn)原來的表還存在就會(huì)報(bào)錯(cuò)艰匙。

package com.example.administrator.databasetest;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;


public class MyDatabaseHelper extends SQLiteOpenHelper {

    public static final String CREATE_BOOK = "create table book ("
            + "id integer primary key autoincrement, "
            + "author text, "
            + "price real, "
            + "pages integer, "
            + "name text)";

    public static final String CREATE_CATEGORY = "create table category ("
            + "id integer primary key autoincrement, "
            + "category_name text, "
            + "category_code integer)";

    private Context mContext;

    public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        this.mContext = context;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        db.execSQL(CREATE_CATEGORY);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("drop table if exists book");
        db.execSQL("drop table if exists category");
        onCreate(db);
    }
}

MainActivity里面就顯示下界面,省略了抹恳。

如果想讓這個(gè)數(shù)據(jù)庫共享员凝,其他應(yīng)用也能訪問?只需新增一個(gè)內(nèi)容提供者即可奋献。

New -> Other -> ContentProvider健霹,AS會(huì)幫我們?cè)贏ndroidManifest.xml里注冊(cè)好。有一個(gè)屬性authorities比較重要瓶蚂,一般命名方式是<包名>.provider糖埋,比如com.example.cptest.provider

可以看到窃这,內(nèi)容提供者的方法和操作數(shù)據(jù)庫差不多瞳别。最大的不同是操作數(shù)據(jù)庫需要填上表名,而內(nèi)容提供者中的方法需要填上Uri杭攻。為什么呢祟敛?因?yàn)槭强绯绦蛟L問數(shù)據(jù),多個(gè)應(yīng)用的表名可能一樣兆解,這樣就不知道到底訪問哪個(gè)應(yīng)用的數(shù)據(jù)了馆铁。Uri的格式一般如下

content://<package_name>.provider/<path>/<id>,舉個(gè)例子content://com.example.databasetest.provider/book/2表示訪問book表的id為2的那行數(shù)據(jù)锅睛。

甚至可以使用通配符

  • *表示匹配任意長(zhǎng)度的任意字符
  • #表示匹配任意長(zhǎng)度的數(shù)字

于是可以匹配任意表的URI可以寫成content://com.example.databasetest.provider/*

可以匹配一個(gè)表中任意一行的URI可以寫成content://com.example.databasetest.provider/book/#

package com.example.administrator.databasetest;

import android.content.ContentProvider;
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;

public class DatabaseProvider extends ContentProvider {
    // 0123是自定義代碼叼架,用于清楚表達(dá)我們想要訪問訪問數(shù)據(jù)庫的哪個(gè)表或者哪一行數(shù)據(jù)
    public static final int BOOK_DIR = 0;
    public static final int BOOK_ITEM = 1;
    public static final int CATEGORY_DIR = 2;
    public static final int CATEGORY_ITEM = 3;
    // 和清單文件里provider的authority屬性一致
    public static final String AUTHORITY = "com.example.databasetest.provider";

    private static UriMatcher uriMatcher;

    private MyDatabaseHelper dbHelper;

    static {
      // NO_MATCH就是-1
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
      // 第三個(gè)參數(shù)填上自定義的代碼畔裕,對(duì)應(yīng)于uriMatcher.match(uri)
        uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
        uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
        uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
        uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
    }

    @Override
    public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
        // Implement this to handle requests to delete one or more rows.
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int deletedRows = 0;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                deletedRows = db.delete("book", selection, selectionArgs);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                deletedRows = db.delete("book", "id = ?", new String[]{bookId});
                break;
            case CATEGORY_DIR:
                deletedRows = db.delete("category", selection, selectionArgs);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                deletedRows = db.delete("category", "id = ?", new String[]{categoryId});
                break;
            default:
        }
        return deletedRows;
    }

    @Override
    public String getType(@NonNull Uri uri) {
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.administrator.databasetest.provider.book";
            case BOOK_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.administrator.databasetest.provider.book";
            case CATEGORY_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.administrator.databasetest.provider.category";
            case CATEGORY_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.administrator.databasetest.provider.category";
            default:
                return null;
        }
    }

    @Override
    public Uri insert(@NonNull Uri uri, ContentValues values) {
        // TODO: Implement this to handle requests to insert a new row.
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        Uri uriReturn = null;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
            case BOOK_ITEM:
                long newBookId = db.insert("book", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId);
                break;
            case CATEGORY_DIR:
            case CATEGORY_ITEM:
                long newCategoryId = db.insert("category", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" + newCategoryId);
                break;
            default:
        }
        return uriReturn;
    }
    // 一旦使用到內(nèi)容提供者就調(diào)用此方法,并得到數(shù)據(jù)庫連接的實(shí)例乖订,返回true表示內(nèi)容提供者初始化成功
    @Override
    public boolean onCreate() {
        // TODO: Implement this to initialize your content provider on startup.
        dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 19);
        return true;
    }

    @Override
    public Cursor query(@NonNull Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        // TODO: Implement this to handle query requests from clients.
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = null;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                cursor = db.query("book", projection, selection, selectionArgs, null, null, sortOrder);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1); // 這里path是/book/bookId,get(1)就是bookId
                cursor = db.query("book", projection, "id = ?", new String[]{bookId}, null, null, sortOrder);
                break;
            case CATEGORY_DIR:
                cursor = db.query("category", projection, selection, selectionArgs, null, null, sortOrder);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                cursor = db.query("category", projection, "id = ?", new String[]{categoryId}, null, null, sortOrder);
                break;
            default:
        }
        return cursor;
    }

    @Override
    public int update(@NonNull Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // TODO: Implement this to handle requests to update one or more rows.
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int updatedRows = 0;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                updatedRows = db.update("book", values, selection, selectionArgs);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                updatedRows = db.update("book", values, "id = ?", new String[]{bookId});
                break;
            case CATEGORY_DIR:
                updatedRows = db.update("category", values, selection, selectionArgs);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                updatedRows = db.update("category", values, "id = ?", new String[]{categoryId});
                break;
            default:
        }
        return updatedRows;
    }
}

流程是這樣的扮饶,一旦需要內(nèi)容提供者時(shí)就會(huì)調(diào)用其onCreate方法并且實(shí)例化了數(shù)據(jù)庫連接幫助類。提供了UriMatcher乍构,在靜態(tài)代碼塊里初始化甜无,添加上我們期望匹配的URI

 static {
      // NO_MATCH就是-1
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
      // 第三個(gè)參數(shù)填上自定義的代碼霸株,對(duì)應(yīng)于uriMatcher.match(uri)
        uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
        uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
        uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
        uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
    }

接收三個(gè)參數(shù)碗旅,分別是authority、path和自定義唯一碼销部。

增刪改查的方法就不說了眠饮,注意兩點(diǎn)奥帘。

  1. 有個(gè)新方法uri.getPathSegments().get(1);這是什么意思呢?簡(jiǎn)單來說比如一個(gè)URI是這樣的content://com.example.databasetest.provider/book/2仪召,那么以provider/處分割寨蹋,后面的部分是<path>.<id>,那么get(0)就獲取到了路徑扔茅,get(1)就獲取到了id已旧。
  2. insert方法返回的是一個(gè)新的URI,比如新增的一行db.insert返回一個(gè)新的id為3召娜,那么內(nèi)容提供者的insert方法返回的新URI為content://com.example.databasetest.provider/book/3

最后介紹getType()這個(gè)方法 -- 根據(jù)傳入的內(nèi)同URI來返回相應(yīng)的MIME類型运褪。

  • 必須以vnd開頭
  • 如果URI以path結(jié)尾,則后接android.cursor.dir/;如果URI以id結(jié)尾玖瘸,則后接android.cursor.item/
  • 最后接上vnd.<authority>.<path>

對(duì)于content://com.example.databasetest.provider/book這個(gè)URI秸讹,對(duì)應(yīng)的MIME是vnd.android.cursor.dir/vnd.com.example.databasetest.book;

對(duì)于content://com.example.databasetest.provider/book/2這個(gè)URI,對(duì)應(yīng)的MIME是vnd.android.cursor.item/vnd.com.example.databasetest.book雅倒。

好了內(nèi)容提供者寫好了嗦枢,趕緊在另外一個(gè)應(yīng)用里嘗試一下!

通過內(nèi)容提供者訪問數(shù)據(jù)

假設(shè)此應(yīng)用時(shí)App-B屯断,上面的應(yīng)用是App-A文虏。

布局實(shí)現(xiàn)對(duì)上述應(yīng)用數(shù)據(jù)庫中的book表的CURD

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >


    <Button
        android:id="@+id/add_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Add to Book" />

    <Button
        android:id="@+id/query_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Query From Book" />

    <Button
    android:id="@+id/update_data"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Update Data" />

    <Button
        android:id="@+id/del_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Delete Data" />

</LinearLayout>

MainActivity,所有的方法都是基于getContentResolver()殖演。得到內(nèi)容提供者后氧秘,嘗試訪問App-A的數(shù)據(jù)。此時(shí)App-A里內(nèi)容提供者的onCreate方法得到執(zhí)行趴久,由此創(chuàng)建了數(shù)據(jù)庫丸相。

我們只需正確匹配Uri就能訪問到App-A中的數(shù)據(jù)庫了,代碼很簡(jiǎn)單彼棍,不需要講解了灭忠。

package com.sunhaiyu.contentprovidertest;

import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    // newId是每插入一條數(shù)據(jù)就會(huì)被賦值的膳算,所以進(jìn)行更新和刪除操作時(shí)只能操作最后插入的數(shù)據(jù),其他數(shù)據(jù)不會(huì)受到影響
    private String newId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button addData = (Button) findViewById(R.id.add_data);
        addData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
                ContentValues values = new ContentValues();
                values.put("name", "A Clash of Kings");
                values.put("author", "George Martin");
                values.put("pages", 1040);
                values.put("price", 19.99);
                Uri newUri = getContentResolver().insert(uri, values);
                if (newUri != null) {
                    newId = newUri.getPathSegments().get(1);
                }
            }
        });

        Button queryData = (Button) findViewById(R.id.query_data);
        queryData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
                Cursor cursor = getContentResolver().query(uri, null, null, null, null);
                if (cursor != null) {
                    while (cursor.moveToNext()) {
                        String name = cursor.getString(cursor.getColumnIndex("name"));
                        String author = cursor.getString(cursor.getColumnIndex("author"));
                        int pages = cursor.getInt(cursor.getColumnIndex("pages"));
                        double price = cursor.getDouble(cursor.getColumnIndex("price"));

                        Log.d("MainActivity", "book name is " + name);
                        Log.d("MainActivity", "book author is " + author);
                        Log.d("MainActivity", "book pages is " + pages);
                        Log.d("MainActivity", "book price is " + price);
                    }
                    cursor.close();
                }
            }
        });

        Button  updataData = (Button) findViewById(R.id.update_data);
        updataData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId);
                ContentValues values = new ContentValues();
                values.put("name", "A Storm of Swords");
                values.put("pages", 1216);
                values.put("price", 24.05);
                getContentResolver().update(uri, values, null, null);
            }
        });

        Button delData = (Button) findViewById(R.id.del_data);
        delData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId);
                getContentResolver().delete(uri, null, null);
            }
        });
    }
}

是不是很方便弛作?使用ContentProvider就能式樣App-A輕松訪問到App-B中的內(nèi)容涕蜂。還有一些常見的例子,比如訪問聯(lián)系人和短信數(shù)據(jù)等映琳。

短信的備份

由于短信的數(shù)據(jù)庫已經(jīng)通過內(nèi)容提供者暴露出來 所以我們直接通過內(nèi)容的解析者去查詢數(shù)據(jù)庫机隙,查看源碼其authority是sms;查看添加的URI萨西,其中有一條null有鹿,表示讀取全部短信。

備份好的xml大概長(zhǎng)這樣

<Smss>
    <Sms>
        <address>123456</number>
        <date>"2017/4/10"</date>
        <body>"請(qǐng)你吃飯谎脯,快出來葱跋!給你5秒 5"</body>
    </Sms>
    <Sms>
        <address>123456</number>
        <date>"2017/2/10"</date>
        <body>"請(qǐng)你吃飯,快出來源梭!給你5秒 4"</body>
    </Sms>
</Smss>

布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.sunhaiyu.smscontentprovider.MainActivity">

    <Button
        android:id="@+id/bt_backup_sms"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="備份短信" />

    <Button
        android:id="@+id/bt_restore_sms"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="恢復(fù)短信" />

</LinearLayout>

MainActivity

package com.sunhaiyu.smscontentprovider;

import android.Manifest;
import android.support.v7.app.AppCompatActivity;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Xml;
import android.view.View;
import android.widget.Button;

import org.xmlpull.v1.XmlSerializer;

import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private List<String> permissons = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            permissons.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        }
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_SMS) != PackageManager.PERMISSION_GRANTED) {
            permissons.add(Manifest.permission.READ_SMS);
        }
        if (!permissons.isEmpty()) {
            ActivityCompat.requestPermissions(MainActivity.this, permissons.toArray(new String[permissons.size()]), 1);

        }
        Button btBackup = (Button) findViewById(R.id.bt_backup_sms);
        Button btRestore = (Button) findViewById(R.id.bt_restore_sms);
        btBackup.setOnClickListener(this);
        btRestore.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.bt_backup_sms:
                // 點(diǎn)擊按鈕查詢短信內(nèi)容 然后把短信內(nèi)容進(jìn)行備份
                try {
                    //[1]獲取XmlSerializer的實(shí)例
                    XmlSerializer serializer = Xml.newSerializer();
                    //[2]設(shè)置序列化器參數(shù)
                    File file = new File(Environment.getExternalStorageDirectory().getPath(), "smsbackup.xml");
                    FileOutputStream fos = new FileOutputStream(file);
                    serializer.setOutput(fos, "utf-8");
                    //[3]寫xml文檔開頭, 第二個(gè)參數(shù)娱俺,是否獨(dú)立,xml默認(rèn)獨(dú)立咸产,填寫true
                    serializer.startDocument("utf-8", true);

                    //[4]寫xml的根節(jié)點(diǎn)
                    serializer.startTag(null, "smss");
                    //[5]構(gòu)造uri,這個(gè)authority為sms從源碼可以看到仲闽,同時(shí)path為null表示查詢所有的短信脑溢,所以URI就是下面的樣子了
                    Uri uri = Uri.parse("content://sms/"); // content://sms 也可以

                    //[6]由于短信的數(shù)據(jù)庫已經(jīng)通過內(nèi)容提供者暴露出來 所以我們直接通過內(nèi)容解析者查詢
                    Cursor cursor = getContentResolver().query(uri, new String[]{"address", "date", "body"}, null, null, null);

                    if (cursor != null) {
                        while (cursor.moveToNext()) {
                            String address = cursor.getString(cursor.getColumnIndex("address"));
                            String date = cursor.getString(cursor.getColumnIndex("date"));
                            String body = cursor.getString(cursor.getColumnIndex("body"));

                            //[7]寫sms節(jié)點(diǎn)
                            serializer.startTag(null, "sms");

                            //[8]寫address節(jié)點(diǎn)
                            serializer.startTag(null, "address");
                            serializer.text(address);
                            serializer.endTag(null, "address");
                            //[9]寫date節(jié)點(diǎn)
                            serializer.startTag(null, "date");
                            serializer.text(date);
                            serializer.endTag(null, "date");
                            //[10]寫body節(jié)點(diǎn)
                            serializer.startTag(null, "body");
                            serializer.text(body);
                            serializer.endTag(null, "body");

                            serializer.endTag(null, "sms");

                        }
                        cursor.close();
                    }

                    serializer.endTag(null, "smss");
                    serializer.endDocument();

                } catch (Exception e) {
                    e.printStackTrace();
                }

                break;
            case R.id.bt_restore_sms:
                break;
            default:
                break;
        }
    }
}

由于我直接八備份文件放在了sd卡根目錄下,所以需要申請(qǐng)權(quán)限赖欣。而且讀取短信也需要權(quán)限屑彻。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_SMS" />

之后再申請(qǐng)運(yùn)行時(shí)權(quán)限。

沒有實(shí)現(xiàn)短信恢復(fù)的功能顶吮,這個(gè)功能現(xiàn)在實(shí)現(xiàn)起來麻煩了社牲。

不能直接申請(qǐng)WRITE_SMSA權(quán)限了!Android 4.4 (KitKat) 開始悴了,更新了 SMS 的部分API搏恤。只有default SMS app才能對(duì)短信數(shù)據(jù)庫有寫權(quán)限,但是用戶可以把第三方應(yīng)用設(shè)置為default SMS app湃交。詳情看這里

其實(shí)想想也正常熟空,如果任何應(yīng)用都能寫入短信數(shù)據(jù)庫,將是一大安全隱患搞莺。

讀取手機(jī)聯(lián)系人

手機(jī)聯(lián)系人信息也通過provider對(duì)外暴露息罗。任何應(yīng)用都可以輕松訪問到。ContactsContract才沧。CommonDataKinds.Phone這個(gè)類已經(jīng)幫我們封裝好了迈喉,使得用戶不用操心URI的匹配問題绍刮,十分方便。

package com.sunhaiyu.contacttest;

import android.Manifest;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
        }

        Cursor cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
        if (cursor != null) {
            while (cursor.moveToNext()) {
                String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                String phoneNumber = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                Log.d("Contact", displayName + " : " + phoneNumber);
            }
            cursor.close();
        }
    }
}

記得添加權(quán)限<uses-permission android:name="android.permission.READ_CONTACTS"/>

插入聯(lián)系人

插入聯(lián)系人也很方便挨摸,使用方式和上面大同小異孩革。

首先是布局,輸入姓名和手機(jī)號(hào)碼油坝,點(diǎn)擊按鈕就可插入到聯(lián)系人嫉戚。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical"
   tools:context=".MainActivity" >

   <EditText
       android:id="@+id/et_name"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:hint="請(qǐng)輸入姓名" />

   <EditText
       android:id="@+id/et_phone"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:hint="請(qǐng)輸入電話號(hào)碼" />

   <Button
       android:id="@+id/bt_add_contact"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="插入到聯(lián)系人" />

</LinearLayout>

然后關(guān)鍵是addContact方法了,注意先插入空值以獲得一個(gè)新的id澈圈,之后使用這個(gè)ID添加聯(lián)系人姓名和手機(jī)好彬檀,下面的代碼可以說是一個(gè)模板。

package com.sunhaiyu.addcontacttest;

import android.Manifest;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.provider.ContactsContract;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private EditText etName;
    private EditText etPhone;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_CONTACTS}, 1);
        }

        etName = (EditText) findViewById(R.id.et_name);
        etPhone = (EditText) findViewById(R.id.et_phone);
        Button btAdd = (Button) findViewById(R.id.bt_add_contact);
        btAdd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String name = etName.getText().toString().trim();
                String phone = etPhone.getText().toString().trim();
                addContact(name, phone);
            }
        });

    }

    public void addContact(String name, String phoneNumber) {
        // 創(chuàng)建一個(gè)空的ContentValues
        ContentValues values = new ContentValues();

        // 向RawContacts.CONTENT_URI空值插入瞬女,用于獲取Android系統(tǒng)返回的rawContactId窍帝。后面要基于此id插入值
        Uri rawContactUri = getContentResolver().insert(ContactsContract.RawContacts.CONTENT_URI, values);
        long rawContactId = ContentUris.parseId(rawContactUri);
        // 添加下一條之前先清空
        values.clear();
        // 1. 添加聯(lián)系人姓名
        // id
        values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId);
        // 內(nèi)容類型添加了才會(huì)顯示出來,否則顯示無姓名
        values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
        // 聯(lián)系人名字
        values.put(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, name);
        // 向聯(lián)系人URI添加聯(lián)系人名字
        getContentResolver().insert(ContactsContract.Data.CONTENT_URI, values);


        values.clear();
        // 2. 添加手機(jī)號(hào)碼诽偷,這個(gè)id要保證和上面的一致
        values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId);
        values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
        // 聯(lián)系人的電話號(hào)碼
        values.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber);
        // 電話類型
        values.put(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
        // 向聯(lián)系人電話號(hào)碼URI添加電話號(hào)碼
        getContentResolver().insert(ContactsContract.Data.CONTENT_URI, values);

        Toast.makeText(this, "聯(lián)系人數(shù)據(jù)添加成功", Toast.LENGTH_SHORT).show();
    }

}

記得添加權(quán)限<uses-permission android:name="android.permission.WRITE_CONTACTS"/>

內(nèi)容觀察者簡(jiǎn)介

當(dāng)某一個(gè)應(yīng)用中內(nèi)容提供者中共享的數(shù)據(jù)發(fā)生改變時(shí)候坤学,就會(huì)收到一個(gè)通知。具體來說报慕,當(dāng)App-A訪問或者修改了內(nèi)容提供者的數(shù)據(jù)時(shí)深浮,同時(shí)發(fā)送一個(gè)通知。(調(diào)用了getContentResolver().notifyChange())然后App-B中會(huì)響應(yīng)onChange()方法眠冈。起到一個(gè)監(jiān)視的作用飞苇。

一個(gè)簡(jiǎn)單的例子,監(jiān)聽短信數(shù)據(jù)庫的變化蜗顽。系統(tǒng)源碼中已經(jīng)發(fā)送了通知布卡,我們只需接受即可。

package com.sunhaiyu.contentbservertest;

import android.Manifest;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_SMS)!= PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_SMS}, 1);
        }
        //[1]注冊(cè)一個(gè)內(nèi)容觀察者
        Uri uri = Uri.parse("content://sms/");
        // false表示指定的這個(gè)URI和其父路徑 true還能表示其子路徑
        getContentResolver().registerContentObserver(uri, true, new MyContentObserver(new Handler()));

    }

    private class MyContentObserver extends ContentObserver{

        public MyContentObserver(Handler handler) {
            super(handler);
        }
        //當(dāng)觀察的內(nèi)容發(fā)生改變的時(shí)候調(diào)用
        @Override
        public void onChange(boolean selfChange) {
            Toast.makeText(MainActivity.this, "短信數(shù)據(jù)庫變化", Toast.LENGTH_SHORT).show();
            Log.d("Sms", "onChange: ");
            super.onChange(selfChange);
        }

    }
}

記得添加權(quán)限<uses-permission android:name="android.permission.READ_SMS" /


by @sunhaiyu

2017.6.5

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末雇盖,一起剝皮案震驚了整個(gè)濱河市忿等,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌崔挖,老刑警劉巖贸街,帶你破解...
    沈念sama閱讀 222,627評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異狸相,居然都是意外死亡匾浪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門卷哩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛋辈,“玉大人,你說我怎么就攤上這事±淙埽” “怎么了渐白?”我有些...
    開封第一講書人閱讀 169,346評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)逞频。 經(jīng)常有香客問我纯衍,道長(zhǎng),這世上最難降的妖魔是什么苗胀? 我笑而不...
    開封第一講書人閱讀 60,097評(píng)論 1 300
  • 正文 為了忘掉前任襟诸,我火速辦了婚禮,結(jié)果婚禮上基协,老公的妹妹穿的比我還像新娘歌亲。我一直安慰自己,他們只是感情好澜驮,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評(píng)論 6 398
  • 文/花漫 我一把揭開白布陷揪。 她就那樣靜靜地躺著,像睡著了一般杂穷。 火紅的嫁衣襯著肌膚如雪悍缠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,696評(píng)論 1 312
  • 那天耐量,我揣著相機(jī)與錄音飞蚓,去河邊找鬼。 笑死廊蜒,一個(gè)胖子當(dāng)著我的面吹牛趴拧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播劲藐,決...
    沈念sama閱讀 41,165評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼八堡,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼樟凄!你這毒婦竟也來了聘芜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,108評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤缝龄,失蹤者是張志新(化名)和其女友劉穎汰现,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叔壤,經(jīng)...
    沈念sama閱讀 46,646評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瞎饲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了炼绘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嗅战。...
    茶點(diǎn)故事閱讀 40,861評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出驮捍,到底是詐尸還是另有隱情疟呐,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布东且,位于F島的核電站启具,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏珊泳。R本人自食惡果不足惜鲁冯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望色查。 院中可真熱鬧薯演,春花似錦、人聲如沸综慎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽示惊。三九已至好港,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間米罚,已是汗流浹背钧汹。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評(píng)論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留录择,地道東北人拔莱。 一個(gè)月前我還...
    沈念sama閱讀 49,287評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像隘竭,于是被迫代替她去往敵國和親塘秦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理动看,服務(wù)發(fā)現(xiàn)尊剔,斷路器,智...
    卡卡羅2017閱讀 134,715評(píng)論 18 139
  • 文件存儲(chǔ)菱皆,SharedPreferences存儲(chǔ)以及數(shù)據(jù)庫存儲(chǔ)须误,使用這些持久化技術(shù)所保存的數(shù)據(jù)都只能在當(dāng)前應(yīng)用程序...
    努力生活的西魚閱讀 584評(píng)論 0 0
  • 參考: 內(nèi)容提供程序基礎(chǔ)知識(shí) 創(chuàng)建內(nèi)容提供程序 一. 用途: 跨程序共享數(shù)據(jù)(為其他應(yīng)用程序提供訪問數(shù)據(jù)的接口) ...
    NickelFox閱讀 1,114評(píng)論 2 13
  • 內(nèi)容提供器(ContentProvider)主要用于不同的程序之間實(shí)現(xiàn)數(shù)據(jù)共享的功能,并保證被訪數(shù)據(jù)的安全性仇轻。使用...
    飛行員suke閱讀 525評(píng)論 0 1
  • 昨天(2017年4月30日)京痢,我陪同我孩子參加賣報(bào)活動(dòng),感觸多多篷店。孩子真的也成長(zhǎng)了祭椰。 剛開始,老師讓家長(zhǎng)一個(gè)班,小...
    Jerry_雨中的夢(mèng)閱讀 221評(píng)論 0 0