文档章节

Android四大组件之ContentProvide(内容提供者)

 早早的太阳
发布于 2016/09/25 22:08
字数 4689
阅读 47
收藏 1

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

1. 访问私有数据库

创建一个项目,在项目中利用SQLiteOpenHelper创建一个名称为account的数据库,并在数据库中创建一张名为info的表。

public class MyOpenHelper extends SQLiteOpenHelper {
    public MyOpenHelper(Context context) {
        super(context, "account.db", null, 1);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("create table info (_id integer primary key autoincrement,name varchar(20),money varchar(20))");
        db.execSQL("insert into info('name','money') values ('张三','2000')");
        db.execSQL("insert into info ('name','money') values ('李四','5000')");   
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
    }
}

在MainActivity中需要调用以下代码才能创建数据库:

private MyOpenHelper myOpenHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    myOpenHelper = new MyOpenHelper(this);
    myOpenHelper.getReadableDatabase();
}

运行程序,我们利用DDMS中的FileExplorer查看我们的数据库文件: 
这里写图片描述

从上图可以看到创建了一个account.db的数据库,查看文件权限可以看到,对其他用户没有权限。

SQLiteDatabase有一个静态的方法,可以直接加载某个路径下的数据库文件。我们再创建一个项目工程,在这个工程中用这个静态方法访问account.db。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    SQLiteDatabase db = SQLiteDatabase.openDatabase("/data/data/com.itheima.transation/databases/account.db", null, SQLiteDatabase.OPEN_READWRITE);
    Cursor cursor = db.query("info", null, null, null, null, null, null);
       if (cursor!=null && cursor.getCount()>0){        
            while (cursor.moveToNext()) {
                String name=cursor.getString(cursor.getColumnIndex("name"));
                String money=cursor.getString(cursor.getColumnIndex("money"));
                System.out.println("name"+name + "money"+money);
        }           
    } 
}

运行结果: 
这里写图片描述 
这里写图片描述

以上结果说明我们使用SQLiteDatabase.openDatabase()方法打开数据库需要有权限才能够访问。我们改变account.db的访问权限,使其他用户也能访问该文件:

这里写图片描述

再次运行程序,访问account.db数据库,这时候就能够访问到数据库中的数据了。查看日志输出如下:

这里写图片描述

这种方式虽然能够访问到其他应用程序的数据库,但是这种方式需要手动改变其他应用程序数据库的访问权限,并且这是一种非常不安全的操作,如果改变应用数据库的访问权限,其他程序很容易修改数据库的内容。那么如何才能访问其他应用程序的数据库呢?Google给我们提供了Android中另一个组件ContentProvider内容提供者来解决这个问题。

2. 内容提供者

ContentProvider(内容提供者)是Android中的四大组件之一,在一般的开发中,可能使用的比较少。

ContentProvider为不同的软件之间数据共享,提供统了一套接口。也就是说,如果我们想让其他的应用使用我们自己程序内的数据,就可以使用ContentProvider定义一组对外开放的接口,从而使得其他的应用可以使用咱们应用的文件、数据库内存储的信息。

当然,自己开发的应用需要给其他应用共享信息的需求可能比较少见,但是在Android系统中,很多系统自带应用,比如联系人信息,图片库,音频库等应用,为了对其他应用暴露数据,所以就使用了ContentProvider机制。所以,学习ContentProvider的基本使用,在遇到获取联系人信息,图片库,音频库等需求的时候,才能更好的开发。

2.1. 内容提供者的原理

内容提供者定义了一组对外开放的接口,使其他应用可以访问自己的应用的数据库内容,下图是外部应用访问系统联系人应用数据库的原理图: 
这里写图片描述

从上图可以看出,普通外部应用不可以直接访问私有的数据库,只能通过内容提供者访问私有数据库,内容提供者处于应用内部,在内容提供者中定义了一些访问路径匹配等操作,当外部应用通过路径访问私有数据库时,内容提供者根据路径匹配出具体的操作,将私有数据返回给外部应用。

2.2. 内容提供者的使用

(a)创建一个类继承ContentProvider,实现其中的方法

public class AccountProvider extends ContentProvider {
    //当内容提供者创建的时候调用
    @Override
    public boolean onCreate() {
        return true;
    }
    //用于查询数据库数据,返回值是Cursor即结果的数据集
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
      String[] selectionArgs, String sortOrder) {
    }
    //返回MIME类型的字符串,如果返回null,说明没有数据类型
    @Override
    public String getType(Uri uri) {
        return null;
    }
    //用于向数据库插入记录
    @Override
    public Uri insert(Uri uri, ContentValues values) {  
    }
    //用于删除数据库记录
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
    }
    //用于更新数据库记录
    @Override
    public int update(Uri uri, ContentValues values, String selection,
    String[] selectionArgs) {   
    }
}

(b)清单文件中配置内容提供者

<provider
     android:name="com.itheima.transation.AccountProvider"
     //设置主机名
     android:authorities="com.itheima.account.provider" >
</provider>

(c)定义路径匹配规则

//定义当路径匹配成功后对指定的组件返回的返回码
private static final int QUERYSUCESS = 1;
private static final int ADDSUCESS = 2;
private static final int DELSUCESS = 3;
private static final int UPDATESUCESS = 4;
//创建Uri的匹配对象
static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
    //增加匹配规则,参数1为主机名,参数2为路径,参数3为当路径匹配成功后对指定的组件返回的返回码,必须是正数。该主机名必须和清单文件中配置的主机名一致。
    matcher.addURI("com.itheima.account.provider", "query", QUERYSUCESS);
    matcher.addURI("com.itheima.account.provider", "add", ADDSUCESS); 
    matcher.addURI("com.itheima.account.provider", "delete", DELSUCESS);
    matcher.addURI("com.itheima.account.provider", "update", UPDATESUCESS);
}

注意:主机名必须和清单文件中配置的主机名一致。

Uri的组成规则:协议名://主机名或authority/路径/ID。Uri的组成可以参考下图: 
这里写图片描述

(d)创建SQLiteOpenHelper

public class MyOpenHelper extends SQLiteOpenHelper {    
    public MyOpenHelper(Context context) {
        super(context, "account.db", null, 1);
    }
    @Override
    public void onCreate(SQLiteDatabase db){        
        db.execSQL("create table info (_id integer primary key autoincrement,name varchar(20),money varchar(20))");
        db.execSQL("insert into info ('name','money') values ('张三','2000')");
        db.execSQL("insert into info ('name','money') values ('李四','5000')");   
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
    {       
    }
}

获取数据库,在ContentProvider中实现覆盖的增删改查方法:

@Override
public boolean onCreate() {
    //在onCreate()方法中创建SQLiteOpenHelper对象
    helper = new MyOpenHelper(getContext());
    return true;
}

//内容提供者的查询方法
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[]
 selectionArgs, String sortOrder) {
     //匹配路径,返回值是一个int类型的返回码,如果匹配不成功,返回-1
    int code = matcher.match(uri);
    if (code == QUERYSUCESS) {
        SQLiteDatabase db = helper.getReadableDatabase();
        //调用SQLiteDatabase对象的query()查询数据
        Cursor cursor = db.query("info", projection, selection, selectionArgs, null, null, sortOrder);
        return cursor;
    } else {
        throw new IllegalArgumentException("路径不匹配 请检查");
    }
}

@Override
public Uri insert(Uri uri, ContentValues values) {
    int code = matcher.match(uri);
    if (code == ADDSUCESS) {
        SQLiteDatabase db = helper.getReadableDatabase();
        long insert = db.insert("info", null, values);
        return Uri.parse("com.itheima.account.provider" + insert);
    } else {
        throw new IllegalArgumentException("路径不匹配 请检查");
    }
}

@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
    int code = matcher.match(uri);
    if (code == DELSUCESS) {
        SQLiteDatabase db = helper.getReadableDatabase();
        int delete = db.delete("info", selection, selectionArgs);
        return delete;
    } else {
        throw new IllegalArgumentException("路径不匹配 请检查");
    }
}

@Override
public int update(Uri uri, ContentValues values, String selection, String[]
 selectionArgs) {
    int code = matcher.match(uri);
    if (code == UPDATESUCESS) {
        SQLiteDatabase db = helper.getReadableDatabase();
        int update = db.update("info", values, selection, selectionArgs);
        return update;
    } else {
        throw new IllegalArgumentException("路径不匹配 请检查");
    }
}

2.3. 访问自定义内容提供者

内容提供者定义好了,那么如何在其他应用中访问内容提供者提供的数据呢?这时候需要用到ContentResolver来访问。 
创建另外一个项目,在项目中利用ContentResolver来访问其他应用的内容提供者。 
界面布局:

<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" >
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="add"
        android:text="add" />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="del"
        android:text="del" />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="update"
        android:text="update" />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="find"
        android:text="find" />
</LinearLayout>

在MainActivity中通过Context获取ContentResolver来访问私有数据库:

public class MainActivity extends Activity {

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

    public void add(View v){
        //定义访问路径
        Uri uri = Uri.parse("content://com.itheima.account.provider/add");
        //创建一系列ContentResolver能够处理的数据,其值为key-value的结构,key必须和数据库字段一致
        ContentValues values = new ContentValues();
        values.put("name", "zhaoqi"); 
        values.put("money", "1000"); 
        //通过Context对象获取ContentResolver对象,调用ContentResolver对象的insert()方法插入数据,参数1为访问uri,参数2为插入的数据 
        Uri insert = getContentResolver().insert(uri, values);
        System.out.println("uri--"+insert.toString());      
    }

    public void del(View v){
        Uri uri = Uri.parse("content://com.itheima.account.provider/delete");
        int delete = getContentResolver().delete(uri, "name=?", new String[]{"zhaoqi"});    
        System.out.println("delete=="+delete);      
    }

    public void update(View v){
        Uri uri = Uri.parse("content://com.itheima.account.provider/update");
        ContentValues values = new ContentValues();
        values.put("money", "20000");
        int update = getContentResolver().update(uri, values, "name=?", new String[]{"zhaoqi"});
        System.out.println("update--"+update);      
    }

    public void find(View v){
        Uri uri = Uri.parse("content://com.itheima.account.provider/query");
        Cursor cursor = getContentResolver().query(uri, null, null, null, null);
        if (cursor!=null && cursor.getCount()>0){   
                while (cursor.moveToNext()) {
                    String name = cursor.getString(cursor.getColumnIndex("name")); 
                    String money = cursor.getString(cursor.getColumnIndex("money"));    
                    System.out.println("name"+name + "money"+money);
                }               
        }       
    }
}

运行后,发现其他应用就可以通过内容提供者来访问私有数据库了。

注意:匹配路径后面是可以携带数字的,即Uri中的ID,Android中有一个方便的api来获取这个值:

int id = (int) ContentUris.parseId(uri);

3. 短信备份

本案例通过内容提供者获取系统短信数据库中的短信数据,将这些短信数据通过XML序列化到文件中。 
系统短信数据的位置存放在系统短信应用包名下的databases下,如下图: 
这里写图片描述

导出数据库,利用SQLiteStudio工具查看数据中的数据: 
这里写图片描述

要备份短信数据,需要备份短信数据库中sms表中的短信的address、date、sms三个字段的数据。此外当通过ContentResolver来查询的时候需要提供访问的Uri,那么这个Uri如何获取呢?可以查看系统短信的源码来得到访问Uri,因为短信源码中肯定定义了供其他应用访问的ContentProvider。

首先我们找到源码目录,在清单文件中查找: 
这里写图片描述

从上图可以得知短信Provider的主机名。 
接着找到源码中SmsProvider这个类,查看Uri后面的path: 
这里写图片描述

从上图可以得知访问短信数据库的Uri的权限是sms,查看匹配规则,第一个匹配规则的路径为null,代表查询所有数据,这样我们就知道查询短信数据库的Uri为content://sms/。 
接下来我们通过ContentResolver来查询短信数据库并将数据序列化到文件中:

public void click(View v){      
    try {
        //创建一个xml序列化对象
        XmlSerializer serializer = Xml.newSerializer();
        //备份文件存储位置
        File file = new File(Environment.getExternalStorageDirectory().getPath(),"smsback.xml");
        FileOutputStream fos = new FileOutputStream(file);
        serializer.setOutput(fos, "utf-8");
        serializer.startDocument("utf-8", true);
        serializer.startTag(null, "smss");
        Uri uri = Uri.parse("content://sms/");
        //通过ContentResolver获取数据库中的内容
        Cursor cursor = getContentResolver().query(uri, new String[]{"address","date","body"}, null, null, null);
        while(cursor.moveToNext()){             
            String address = cursor.getString(0);
            String date = cursor.getString(1);
            String body = cursor.getString(2);
            serializer.startTag(null,"sms");
            serializer.startTag(null, "address");
            serializer.text(address);
            serializer.endTag(null, "address"); 
            serializer.startTag(null, "date");
            serializer.text(date);
            serializer.endTag(null, "date");
            serializer.startTag(null, "body");
            serializer.text(body);
            serializer.endTag(null, "body");
            serializer.endTag(null, "sms");
        }
        cursor.close(); 
        serializer.endTag(null, "smss");
        serializer.endDocument();
        fos.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

运行结果: 
这里写图片描述 
这里写图片描述

导出到电脑查看: 
这里写图片描述

4. 还原短信

利用ContentResolver向数据库中插入一条短信。

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    public void click(View v){
        Uri uri = Uri.parse("content://sms/");
        ContentValues values = new ContentValues();
        values.put("address", "95553"); 
        values.put("body", "您的余额为0.00元.....");
        values.put("date", System.currentTimeMillis());
        getContentResolver().insert(uri, values); 
    }
}

加入权限:

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

运行结果: 
这里写图片描述

这里写图片描述

5. 获取联系人

首先我们查看手机系统联系人和联系人数据库。 
这里写图片描述

这里写图片描述

将contact2.db导出到电脑,使用SQLiteStudio查看数据库,查看data表: 
这里写图片描述

通过data表,可以发现data1字段存储的是联系人的信息,那么如何区这些信息是哪个联系人的呢?通过观察可以发现raw_contact_id表示的就是属于某个联系人,然后查看raw_contacts表: 
这里写图片描述

通过查看raw_contacts表,发现contact_id就与刚才data表中的raw_contact_id是对应的。接下来需要知道data表中data1字段的信息是姓名呢,还是电话或者其他的类型,这时候就需要通过minetype_id来区分信息的类型,如下图: 
这里写图片描述

接着,到mimetypes表中查找对应的mimetype_id是什么类型,如下图: 
这里写图片描述

从上图的mimetypes表可以看出,id为5表示手机号码,6表示姓名,1表示邮箱。 
接下来我们就通过这几张表查询出联系人信息: 
(a)raw_contacts表:可以得到所有联系人的id 
contact_id:联系人id 
(b)data表:联系人的具体信息,一个信息占一行 
data1:信息的具体内容 
raw_contact_id:联系人id,描述信息属于哪个联系人 
mimetype_id:描述信息是属于什么类型 
(c)mimetypes表:通过mimetype_id到该表查看具体类型

查询联系人的步骤: 
1. 通过raw_contacts获取联系人id,表查询一共有多少个联系人; 
2. 通过联系人id获取data表中的data1字段和mimetypes字段; 
3. 通过mimetypes表查询类型。 
得到了如何查询到联系人后,接下来使用ContentResolver来查询联系人信息,那么问题来了,如何知道访问的Uri呢?这又需要通过查看联系人源码才能得到Uri。查看ContactsProvider源码清单文件如下图:

这里写图片描述

在Activity通过raw_contacts表查询所有的contact_id即一共多少联系人:

Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
Cursor cursor = getContentResolver().query(uri, new String[]{"contact_id"}, null, null, null);
while (cursor.moveToNext()) {
    String contact_id = cursor.getString(0);
    System.out.println("contact_id====="+ contact_id);
}

运行结果: 
这里写图片描述

运行出错,查看日志提示需要在清单文件中加入权限:

<uses-permission android:name="android.permission.READ_CONTACTS"/>
  • 1

再次运行程序,运行结果获取到了联系人id: 
这里写图片描述

通过data表查询data1、mimetype_id字段:

Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
Uri datauri = Uri.parse("content://com.android.contacts/data");
Cursor cursor = getContentResolver().query(uri, new String[]{"contact_id"}, null, null, null);
while (cursor.moveToNext()) {
    String contact_id = cursor.getString(0);
    System.out.println("contact_id====="+ contact_id);
    Cursor dataCursor = getContentResolver().query(datauri, new String[] {"data1","mimetype_id"}, "raw_contact_id=?", new String[]{contact_id}, null);
    while (dataCursor.moveToNext()) {
        String data1 = dataCursor.getString(0);
        String mimetype_id = dataCursor.getString(1);
        System.out.println("data1 = "+data1+"-----mimetype_id = "+mimetype_id);
    }
}

运行结果报错,提示无效的列mimetype_id: 
这里写图片描述

为什么会报无效的列mimetype_id呢?这是因为系统的ContentProvider在做查询的时候不是直接查询的mimetype_id这个字段,而是查询view_data这个视图,这个视图将data表和mimetypes表联系起来,所以查询的字段应该是mimetypes表中的mimetype字段,如下图: 
这里写图片描述 
这里写图片描述

我们将查询的字段改成mimetype,再次查询,运行结果如下: 
这里写图片描述

拿到联系人信息后,判断mimetype的类型:

if ("vnd.android.cursor.item/email_v2".equals(mimetype)) {
    System.out.println("邮件data:"+data1);
}else if("vnd.android.cursor.item/name".equals(mimetype)){
    System.out.println("姓名data:"+data1);
}else if("vnd.android.cursor.item/phone_v2".equals(mimetype)){
    System.out.println("电话号码data:"+data1);
}

运行结果如下: 
这里写图片描述

将数据封装成对象,定义联系人实体bean:

public class Contact {
    private String id;
    private String name;
    private String phone;
    private String email;
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPhone() {
        return phone;
    }
    public void setPhone(String phone) {
        this.phone = phone;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    @Override
    public String toString() {
        return "Contact [id=" + id + ", name=" + name + ", phone=" + phone + ", email=" + email + "]";
    }
}

查询联系人数据并且将数据封装,并且创建联系人对象集合用来存储联系人:

//创建保存联系人的集合
List<Contact> contactLists = new ArrayList<Contact>();              
Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
Uri datauri = Uri.parse("content://com.android.contacts/data");
Cursor cursor = getContentResolver().query(uri, new String[]{"contact_id"}, null, null, null);
while(cursor.moveToNext()){
    String contact_id = cursor.getString(0); 
    //一定要判断contact_id是否为空,因为删除联系人后,联系人的数据还会存在数据库中,但是contact_id为null
    if (contact_id != null) {
        //创建联系人对象
        Contact contact = new Contact(); 
        //设置联系人id
        contact.setId(contact_id);
        Cursor dataCursor = getContentResolver().query(datauri, new String[]{"data1","mimetype"}, "raw_contact_id=?", new String[]{contact_id}, null);
        while(dataCursor.moveToNext()){
            String data1 = dataCursor.getString(0);   
            String mimetype = dataCursor.getString(1); 
            if ("vnd.android.cursor.item/email_v2".equals(mimetype)) {
                //设置联系人email
                contact.setEmail(data1);
            }else if("vnd.android.cursor.item/name".equals(mimetype)){
                //设置联系人的姓名
                contact.setName(data1);
            }else 
              if("vnd.android.cursor.item/phone_v2".equals(mimetype)){
                //设置联系人电话
                contact.setPhone(data1);
            }   
        }
        //将联系人添加到集合中
        contactLists.add(contact);
    }
}

注意:第9行,判断contact_id是否为空,因为删除联系人后,联系人的数据还会存在数据库中,但是contact_id为null; 
谷歌为何这样设计呢,是由于,在国外,联系人的数据都会上传到服务器,如果删除联系人,那么联系人的数据data1字段都需要删除,那么当同步的时候,就非常的麻烦。

6. 插入联系人

输入姓名,电话,邮箱,点击“还原”按钮,将联系人数据插入到数据库中。 
界面布局:

<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="请输入电话号码" />
    <EditText
        android:id="@+id/et_email"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入email" />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="click"
        android:text="还原" />
</LinearLayout>

MainActivity中实现插入联系人,插入数据之前先查询raw_contacts表,查询一共多少联系人数据,确定新的联系人的id(查询值+1),然后向raw_contacts表中插入新的联系人id,最后向data表中插入数据(raw_contact_id,mimetype_id,data1等)。

public class MainActivity extends Activity {
    private EditText et_name;
    private EditText et_phone;
    private EditText et_email;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        et_name = (EditText) findViewById(R.id.et_name);
        et_phone = (EditText) findViewById(R.id.et_phone);
        et_email = (EditText) findViewById(R.id.et_email);
    }
    public void click(View v) {
        String email = et_email.getText().toString().trim();
        String name = et_name.getText().toString().trim();
        String phone = et_phone.getText().toString().trim();
        Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
        Uri datauri = Uri.parse("content://com.android.contacts/data");
        Cursor cursor = getContentResolver().query(uri, null, null, null, null);
        int count = cursor.getCount();
        int contact_id = count + 1; 
        ContentValues values = new ContentValues();
        values.put("contact_id", contact_id);
        getContentResolver().insert(uri, values);
        ContentValues nameValues = new ContentValues();
        nameValues.put("raw_contact_id", contact_id);
        nameValues.put("mimetype", "vnd.android.cursor.item/name");
        nameValues.put("data1", name);
        getContentResolver().insert(datauri, nameValues);
        ContentValues emailValues = new ContentValues();
        emailValues.put("raw_contact_id", contact_id);
        emailValues.put("mimetype", "vnd.android.cursor.item/email_v2");
        emailValues.put("data1", email);
        getContentResolver().insert(datauri, emailValues);
        ContentValues phoneValues = new ContentValues();
        phoneValues.put("raw_contact_id", contact_id);
        phoneValues.put("mimetype", "vnd.android.cursor.item/phone_v2");
        phoneValues.put("data1", phone);
        getContentResolver().insert(datauri, phoneValues);
    }
}

7. 内容观察者

利用ContentResolver可以获取内容提供者提供的其他应用是私有数据库信息,但是如果有这样的需求,当其他应用的私有数据库发生改变时,我们的应用能够收到数据变化的通知,这里就用到了ContentObserver内容观察者来实现。 
接下来,利用内容观察者实现短信数据库变化:

public class MainActivity extends Activity {
    private Uri uri;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);         
        uri = Uri.parse("content://sms/");
        //调用getContentResolver().registerContentObserver()方法注册内容观察者,参数1表示需要观察的Uri,参数2表当为false表示观察精确的Uri,true表示指定Uri或者指定Uri下的所有Uri都能匹配
        getContentResolver().registerContentObserver(uri, true, new MyObserver(new Handler())); 
    }

    //定义内容观察者,该类继承ContentObserver
    private class MyObserver extends ContentObserver{
        public MyObserver(Handler handler) {
            super(handler);
        }   

        //当内容发生变化的时候调用
        @Override
        public void onChange(boolean selfChange) {
            Cursor cursor = getContentResolver().query(uri, new String[]{"address","body"}, null, null, "date desc");
            cursor.moveToFirst();
            String address = cursor.getString(0);
            String body = cursor.getString(1);
            System.out.println("body:"+body+"address:"+address);
            super.onChange(selfChange);
        }   
    }
}

模拟器中模拟接收一条短信: 
这里写图片描述

查看日志打印,获取到了最新的短信: 
这里写图片描述

注意,在自定义的内容提供者中,我们需要在改变数据库数据后通知内容观察者数据发生改变:

ContentResolver cr = getContext().getContentResolver();
//发出通知,所有注册在这个uri上的内容观察者都可以收到通知
cr.notifyChange(uri, null);

© 著作权归作者所有

粉丝 1
博文 37
码字总数 97504
作品 0
私信 提问
Android Studio教程02-应用程序结构图及应用基础

目录 1. Android应用程序开发技术结构图 2.Android的应用基础 2.1. Android的四大组件: 2.2.启动四大组件的方法 2.3. 清单文件 1. Android应用程序开发技术结构图 一、应用程序层 该层提供一...

Bricker666
01/16
0
0
Android 应用程序四大组件

Android 应用程序组件是一个Android应用程序的基本构建块,这些组件由应用清单文件松耦合的组织,AndroidManifest.xml描述了应用程序的每个组件,以及他们如何交互 Android:应用程序组件主要...

嘿嘿嘿IT
03/29
8
0
android:exported 属性详解

昨天在用360扫描应用漏洞时,扫描结果,出来一个android:exported属性,其实之前根本不知道这个属性,更不知道这个属性用来干嘛的,详情见下图: 因此,查了官方API,学习了一下这个属性! a...

SuShine
01/08
10
0
ContentProvider和数据库的区别

大家好,今天我们来讲解ContentProvider和数据库的区别是他们之间的联系. 四大组件之一 1.ContentProvider是如何实现数据共享的? 1.在Android中,为了把自己程序的数据(一般是数据库)提供给其他...

天王盖地虎626
06/17
33
0
Android系统架构 四大组件

Android 系统架构 是怎么样工作的。 Linux 内核层 Android 系统是基于Linux内核的 ,这一层为安卓设备的各种硬件提供了底层驱动,如显卡驱动,音频驱动,照相机驱动,蓝牙WIFi电源等驱动 系统...

一箭落旄头
2018/10/08
49
0

没有更多内容

加载失败,请刷新页面

加载更多

sed -i linux 批量替换命令

批量替换 /usr/local/rocketmq/conf 目录下 的 xml 里头的 ${user.home} 替换为 /usr/local/rocketmq # mkdir -p /usr/local/rocketmq/logs# cd /usr/local/rocketmq/conf && sed -i 's#${......

jxlgzwh
24分钟前
4
0
如何在嵌入式CSS中编写a:hover?

我有一种情况,我必须编写内联CSS代码,并且我想在锚点上应用悬停样式。 如何在HTML样式属性内的CSS中使用a:hover ? 例如,您不能在HTML电子邮件中可靠地使用CSS类。 #1楼 简短的答案:您不...

技术盛宴
31分钟前
4
0
一些常用工具下载

golang: https://dl.google.com/go/go1.13.5.window-amd64.zip https://dl.google.com/go/go1.13.5.linux-amd64.tar.gz 更换版本号可以下载其他版本。...

bobby2006
38分钟前
4
0
centos使用yum安装或者更新时总是提示被PackageKit占用

centos使用yum安装或者更新时总是提示被PackageKit占用 使用yum安装或更新软件时总是提示yum被PackageKit锁定占用 Existing lock /var/run/yum.pid: another copy is running as pid 13090. ...

流麦士
44分钟前
4
0
使用CSS内容添加HTML实体

如何使用CSS content属性添加html实体? 使用这样的东西只打印  到屏幕而不是不间断的空间: .breadcrumbs a:before { content: ' ';} #1楼 更新 :PointedEars提到正确的立...

javail
47分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部