美文网首页
Android中的内容提供者

Android中的内容提供者

作者: sunhaiyu | 来源:发表于2017-06-06 00:08 被阅读57次

    Android中的内容提供者

    为什么需要内容提供者

    为了跨程序访问数据。试想如果在App-1中创建了一个私有数据库,App-2是不能直接访问的。因为权限不够,虽然可以使用chmod 777来修改权限,然后使用SQLiteDatabase.openDatabase的静态方法,填上具体的路径和模式来访问。但这并不推荐,有没有更好的办法?官方推荐使用ContentProvider--内容提供者。

    创建内容提供者

    简单起见,使用以前的数据库的项目DatabaseTest,同时建立两个表book和category, onUpgrade方法实现了数据库的升级功能。onUpgrade里面强制onCreate,注意必须先删除原来的表,否则我们创建时候发现原来的表还存在就会报错。

    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里面就显示下界面,省略了。

    如果想让这个数据库共享,其他应用也能访问?只需新增一个内容提供者即可。

    New -> Other -> ContentProvider,AS会帮我们在AndroidManifest.xml里注册好。有一个属性authorities比较重要,一般命名方式是<包名>.provider,比如com.example.cptest.provider

    可以看到,内容提供者的方法和操作数据库差不多。最大的不同是操作数据库需要填上表名,而内容提供者中的方法需要填上Uri。为什么呢?因为是跨程序访问数据,多个应用的表名可能一样,这样就不知道到底访问哪个应用的数据了。Uri的格式一般如下

    content://<package_name>.provider/<path>/<id>,举个例子content://com.example.databasetest.provider/book/2表示访问book表的id为2的那行数据。

    甚至可以使用通配符

    • *表示匹配任意长度的任意字符
    • #表示匹配任意长度的数字

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

    可以匹配一个表中任意一行的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是自定义代码,用于清楚表达我们想要访问访问数据库的哪个表或者哪一行数据
        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);
          // 第三个参数填上自定义的代码,对应于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;
        }
        // 一旦使用到内容提供者就调用此方法,并得到数据库连接的实例,返回true表示内容提供者初始化成功
        @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;
        }
    }
    
    

    流程是这样的,一旦需要内容提供者时就会调用其onCreate方法并且实例化了数据库连接帮助类。提供了UriMatcher,在静态代码块里初始化,添加上我们期望匹配的URI

     static {
          // NO_MATCH就是-1
            uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
          // 第三个参数填上自定义的代码,对应于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);
        }
    

    接收三个参数,分别是authority、path和自定义唯一码。

    增删改查的方法就不说了,注意两点。

    1. 有个新方法uri.getPathSegments().get(1);这是什么意思呢?简单来说比如一个URI是这样的content://com.example.databasetest.provider/book/2,那么以provider/处分割,后面的部分是<path>.<id>,那么get(0)就获取到了路径,get(1)就获取到了id。
    2. insert方法返回的是一个新的URI,比如新增的一行db.insert返回一个新的id为3,那么内容提供者的insert方法返回的新URI为content://com.example.databasetest.provider/book/3

    最后介绍getType()这个方法 -- 根据传入的内同URI来返回相应的MIME类型。

    • 必须以vnd开头
    • 如果URI以path结尾,则后接android.cursor.dir/;如果URI以id结尾,则后接android.cursor.item/
    • 最后接上vnd.<authority>.<path>

    对于content://com.example.databasetest.provider/book这个URI,对应的MIME是vnd.android.cursor.dir/vnd.com.example.databasetest.book;

    对于content://com.example.databasetest.provider/book/2这个URI,对应的MIME是vnd.android.cursor.item/vnd.com.example.databasetest.book

    好了内容提供者写好了,赶紧在另外一个应用里尝试一下!

    通过内容提供者访问数据

    假设此应用时App-B,上面的应用是App-A。

    布局实现对上述应用数据库中的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()。得到内容提供者后,尝试访问App-A的数据。此时App-A里内容提供者的onCreate方法得到执行,由此创建了数据库。

    我们只需正确匹配Uri就能访问到App-A中的数据库了,代码很简单,不需要讲解了。

    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是每插入一条数据就会被赋值的,所以进行更新和删除操作时只能操作最后插入的数据,其他数据不会受到影响
        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中的内容。还有一些常见的例子,比如访问联系人和短信数据等。

    短信的备份

    由于短信的数据库已经通过内容提供者暴露出来 所以我们直接通过内容的解析者去查询数据库,查看源码其authority是sms;查看添加的URI,其中有一条null,表示读取全部短信。

    备份好的xml大概长这样

    <Smss>
        <Sms>
            <address>123456</number>
            <date>"2017/4/10"</date>
            <body>"请你吃饭,快出来!给你5秒 5"</body>
        </Sms>
        <Sms>
            <address>123456</number>
            <date>"2017/2/10"</date>
            <body>"请你吃饭,快出来!给你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="恢复短信" />
    
    </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:
                    // 点击按钮查询短信内容 然后把短信内容进行备份
                    try {
                        //[1]获取XmlSerializer的实例
                        XmlSerializer serializer = Xml.newSerializer();
                        //[2]设置序列化器参数
                        File file = new File(Environment.getExternalStorageDirectory().getPath(), "smsbackup.xml");
                        FileOutputStream fos = new FileOutputStream(file);
                        serializer.setOutput(fos, "utf-8");
                        //[3]写xml文档开头, 第二个参数,是否独立,xml默认独立,填写true
                        serializer.startDocument("utf-8", true);
    
                        //[4]写xml的根节点
                        serializer.startTag(null, "smss");
                        //[5]构造uri,这个authority为sms从源码可以看到,同时path为null表示查询所有的短信,所以URI就是下面的样子了
                        Uri uri = Uri.parse("content://sms/"); // content://sms 也可以
    
                        //[6]由于短信的数据库已经通过内容提供者暴露出来 所以我们直接通过内容解析者查询
                        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节点
                                serializer.startTag(null, "sms");
    
                                //[8]写address节点
                                serializer.startTag(null, "address");
                                serializer.text(address);
                                serializer.endTag(null, "address");
                                //[9]写date节点
                                serializer.startTag(null, "date");
                                serializer.text(date);
                                serializer.endTag(null, "date");
                                //[10]写body节点
                                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卡根目录下,所以需要申请权限。而且读取短信也需要权限。

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

    之后再申请运行时权限。

    没有实现短信恢复的功能,这个功能现在实现起来麻烦了。

    不能直接申请WRITE_SMSA权限了!Android 4.4 (KitKat) 开始,更新了 SMS 的部分API。只有default SMS app才能对短信数据库有写权限,但是用户可以把第三方应用设置为default SMS app。详情看这里

    其实想想也正常,如果任何应用都能写入短信数据库,将是一大安全隐患。

    读取手机联系人

    手机联系人信息也通过provider对外暴露。任何应用都可以轻松访问到。ContactsContract。CommonDataKinds.Phone这个类已经帮我们封装好了,使得用户不用操心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();
            }
        }
    }
    
    

    记得添加权限<uses-permission android:name="android.permission.READ_CONTACTS"/>

    插入联系人

    插入联系人也很方便,使用方式和上面大同小异。

    首先是布局,输入姓名和手机号码,点击按钮就可插入到联系人。

    <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="请输入姓名" />
    
       <EditText
           android:id="@+id/et_phone"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:hint="请输入电话号码" />
    
       <Button
           android:id="@+id/bt_add_contact"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="插入到联系人" />
    
    </LinearLayout>
    

    然后关键是addContact方法了,注意先插入空值以获得一个新的id,之后使用这个ID添加联系人姓名和手机好,下面的代码可以说是一个模板。

    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) {
            // 创建一个空的ContentValues
            ContentValues values = new ContentValues();
    
            // 向RawContacts.CONTENT_URI空值插入,用于获取Android系统返回的rawContactId。后面要基于此id插入值
            Uri rawContactUri = getContentResolver().insert(ContactsContract.RawContacts.CONTENT_URI, values);
            long rawContactId = ContentUris.parseId(rawContactUri);
            // 添加下一条之前先清空
            values.clear();
            // 1. 添加联系人姓名
            // id
            values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId);
            // 内容类型添加了才会显示出来,否则显示无姓名
            values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
            // 联系人名字
            values.put(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, name);
            // 向联系人URI添加联系人名字
            getContentResolver().insert(ContactsContract.Data.CONTENT_URI, values);
    
    
            values.clear();
            // 2. 添加手机号码,这个id要保证和上面的一致
            values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId);
            values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
            // 联系人的电话号码
            values.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber);
            // 电话类型
            values.put(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
            // 向联系人电话号码URI添加电话号码
            getContentResolver().insert(ContactsContract.Data.CONTENT_URI, values);
    
            Toast.makeText(this, "联系人数据添加成功", Toast.LENGTH_SHORT).show();
        }
    
    }
    

    记得添加权限<uses-permission android:name="android.permission.WRITE_CONTACTS"/>

    内容观察者简介

    当某一个应用中内容提供者中共享的数据发生改变时候,就会收到一个通知。具体来说,当App-A访问或者修改了内容提供者的数据时,同时发送一个通知。(调用了getContentResolver().notifyChange())然后App-B中会响应onChange()方法。起到一个监视的作用。

    一个简单的例子,监听短信数据库的变化。系统源码中已经发送了通知,我们只需接受即可。

    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]注册一个内容观察者
            Uri uri = Uri.parse("content://sms/");
            // false表示指定的这个URI和其父路径 true还能表示其子路径
            getContentResolver().registerContentObserver(uri, true, new MyContentObserver(new Handler()));
    
        }
    
        private class MyContentObserver extends ContentObserver{
    
            public MyContentObserver(Handler handler) {
                super(handler);
            }
            //当观察的内容发生改变的时候调用
            @Override
            public void onChange(boolean selfChange) {
                Toast.makeText(MainActivity.this, "短信数据库变化", Toast.LENGTH_SHORT).show();
                Log.d("Sms", "onChange: ");
                super.onChange(selfChange);
            }
    
        }
    }
    
    

    记得添加权限<uses-permission android:name="android.permission.READ_SMS" /


    by @sunhaiyu

    2017.6.5

    相关文章

      网友评论

          本文标题:Android中的内容提供者

          本文链接:https://www.haomeiwen.com/subject/ciowfxtx.html