android学习笔记----内容提供者

版权声明:转载请注明 https://blog.csdn.net/qq_34115899/article/details/82145998

目录

内容提供者:

暴露增删改查:

通过内容提供者备份短信:

查询联系人信息:

插入联系人:

内容观察者:


内容提供者:

内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。目前,使用内容提供器是android实现跨程序共享数据的标准方式。

内容提供者可以把私有的数据库暴露出来。
内容提供者把数据进行封装然后提供出来,其他应用都是通过内容解析者来访问。

实现内容提供者的步骤:

1.定义一个类继承ContentProvider

2.在清单文件里配置内容提供器

3.写一个静态代码块添加匹配规则

4.暴露自己想暴露的方法(增删改查)

5.其他应用就可以通过内容提供者去操作数据库

来一个小例子:

MainActivity.java

import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyOpenHelper myOpenHelper = new MyOpenHelper(this);
        SQLiteDatabase db = myOpenHelper.getReadableDatabase();
        Cursor cursor = db.query("info", null, null, null, null, null, null);
        if (cursor.moveToFirst()) {
            do {
                String name = cursor.getString(cursor.getColumnIndex("name"));
                String money = cursor.getString(cursor.getColumnIndex("money"));
                Log.d(TAG, "name:" + name + ", money" + money);
            } while (cursor.moveToNext());
        }
    }
}

然后检查清单文件是否有<provider>,android studio创建的时候默认自己加上去的

        <!--authorities是自定义的-->
        <provider
            android:name=".AccountProvider"
            android:authorities="com.example.myprivatedatabase.provider"
            android:enabled="true"
            android:exported="true"></provider>

authority是对于不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式来进行命名,比如程序的包名是com.example.app,那么改程序的authority就可以命名为 com.example.app.provider。

运行结果:

这是本程序自己的查询,那么需要让别的程序也能查询本程序的数据库该怎么做呢

先只暴露出查询方法,在本程序添加一个java文件

AccountProvider.java

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.widget.Toast;

public class AccountProvider extends ContentProvider {
    // 定义路径匹配器
    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    private static final int QUERYSUCCESS = 1;

    // 定义静态代码块,添加匹配规则
    static {
        /**
         * authority参数要和清单文件一致
         */
        sURIMatcher.addURI("com.example.myprivatedatabase.provider", "query", QUERYSUCCESS);
    }

    private MyOpenHelper myOpenHelper;

    public AccountProvider() {
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public String getType(Uri uri) {
        // TODO: Implement this to handle requests for the MIME type of the data
        // at the given URI.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // TODO: Implement this to handle requests to insert a new row.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public boolean onCreate() {
        myOpenHelper = new MyOpenHelper(getContext());

        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        int code = sURIMatcher.match(uri);
        if (code == QUERYSUCCESS) {
            // 说明路径匹配成功,把query方法给实现,数据库的查询方法,对数据库进行查询操作
            // 想操作数据库必须获得sqlitedatabase对象
            SQLiteDatabase db = myOpenHelper.getReadableDatabase();
            Cursor cursor = db.query("info", projection, selection, selectionArgs, null, null, sortOrder);
            // 这里cursor不能关闭
            return cursor;
        } else {
            // 路径不匹配
            Toast.makeText(getContext(), "您的路径不匹配,请检查", Toast.LENGTH_SHORT).show();
            return null;
        }
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // TODO: Implement this to handle requests to update one or more rows.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

public void addURI (String authority, String path, int code)

添加要匹配的URI,以及匹配此URI时要返回的代码。 URI节点可以是精确匹配字符串,匹配任何文本的标记“*”,或仅匹配数字的标记“#”。从API级别Build.VERSION_CODES.JELLY_BEAN_MR2开始,此方法将接受路径中的前导斜杠。

参数
authority String: 匹配的authority 
path String: 匹配的路径。 *可用作任何文本的通配符,#可用作数字的通配符。
code int: URI与给定组件匹配时返回的代码。必须是正数。

再重复一次,authority是对于不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式来进行命名,比如程序的包名是com.example.app,那么改程序的authority就可以命名为 com.example.app.provider。path是用于对同一个应用程序中不同的表做区分的,通常会添加到authority后面,比如某个程序的数据库里存在两张表:table1和table2,这时可以将path分别命名为/table1和/table2,然后把authority和path进行组合,内容URI就变成了com.example.app.provider/table1和com.example.app.provider/table2

不过,目前还很难辨认出这两个字符串就是两个内容URI,我们还需要在字符串的头部加上协议声明。因此,内容URI最标准的格式写法如下:

content://com.example.app.provider/table1

content://com.example.app.provider/table2

记住,必须要加content://  否则后面操作会报错。

看另一个程序ReadOtherApplicationData的代码

MainActivity.java

import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import java.net.URI;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 需求:读取第一个应用私有的数据库
        /*// factory: 游标工厂
        // flags:访问模式
        SQLiteDatabase.openDatabase("/data/data/com.example.myprivatedatabase/databases/Account.db", null, )*/

        // 由于第一个应用里面的私有数据库已经通过内容提供者给暴露出来了,所以可以直接通过内容的解析者进行访问
        // 拿到内容的解析者,直接通过上下文获取
        Uri uri = Uri.parse("content://com.example.myprivatedatabase.provider/query");// 路径和定义的路径一样
        Cursor cursor = getContentResolver().query(uri, null, null, null, null);
        if (cursor.moveToFirst()) {
            do {
                String name = cursor.getString(cursor.getColumnIndex("name"));
                String money = cursor.getString(cursor.getColumnIndex("money"));
                Log.d(TAG, "第二个应用name" + name + "---money:" + money);
            } while (cursor.moveToNext());
        }
    }
}

运行结果:

批注:

得到内容URI字符串后,我们还需要将它解析成Uri对象才能作为参数传入,解析方法如下:

Uri uri = Uri.parse("content://com.example.myprivatedatabase.provider/query");

getContentResolver()返回ContentResolver对象,里面的一个query方法如下

public final Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

查询给定的URI,在结果集上返回一个Cursor。

为获得最佳性能,调用者应遵循以下准则:

1.提供明确的投影,以防止从存储中读取不会被使用的数据。

2.使用问号参数标记,例如'phone =?'而不是选择参数中的显式值,以便只有这些值不同的查询才会被识别为缓存目的相同。

参数
uri Uri: 使用content://方案的URI,用于检索内容。

该值绝不能为空。

projection String: 要返回的列的列表。传递null将返回所有列,这是低效的。
selection String: 一个过滤器,声明要返回哪些行,格式化为SQL WHERE子句(不包括WHERE本身)。传递null将返回给定URI的所有行。
selectionArgs String: 您可以在选择中包含?s,它将被selectionArgs中的值替换,它们将在选择中出现。这些值将绑定为字符串。

该值可以为null。

sortOrder String: 如何对行进行排序,格式化为SQL ORDER BY子句(不包括ORDER BY本身)。传递null将使用默认排序顺序,该顺序可能是无序的。
返回
Cursor Cursor对象,位于第一个条目之前,或者为null

通俗的解释如下:

接着我们把增删改查全部暴露出来

暴露增删改查:

第一个程序,里面的内容提供器把增删改查方法暴露出来供其他程序调用

MainActivity.java

import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyOpenHelper myOpenHelper = new MyOpenHelper(this);
        SQLiteDatabase db = myOpenHelper.getReadableDatabase();
        Cursor cursor = db.query("info", null, null, null, null, null, null);
        while (cursor.moveToNext()) {
            String name = cursor.getString(cursor.getColumnIndex("name"));
            String money = cursor.getString(cursor.getColumnIndex("money"));
            Log.d(TAG, "name:" + name + ", money" + money);
        }
    }
}

MyOpenHelper.java

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

public class MyOpenHelper extends SQLiteOpenHelper {
    private static final String CREATE_TABLE = "create table info(_id integer primary key autoincrement,name varchar(20),money varchar(20))";

    public MyOpenHelper(Context context) {
        super(context, "Account.db", null, 1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_TABLE);
        db.execSQL("insert into info(name, money) values(?,?)", new String[]{"张三", "5000"});
        db.execSQL("insert into info(name, money) values(?,?)", new String[]{"李四", "3000"});
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

AccountProvider.java

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.widget.Toast;

public class AccountProvider extends ContentProvider {
    // 定义路径匹配器
    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    private static final int QUERYSUCCESS = 0;
    private static final int INSERTSUCCESS = 1;
    private static final int UPDATESUCCESS = 2;
    private static final int DELETESUCCESS = 3;

    // 定义静态代码块,添加匹配规则
    static {
        /**
         * authority参数要和清单文件一致
         */
        // 相当于有了一个uri为content://com.example.myprivatedatabase.provider/query
        sURIMatcher.addURI("com.example.myprivatedatabase.provider", "query", QUERYSUCCESS);
        sURIMatcher.addURI("com.example.myprivatedatabase.provider", "insert", INSERTSUCCESS);
        sURIMatcher.addURI("com.example.myprivatedatabase.provider", "update", UPDATESUCCESS);
        sURIMatcher.addURI("com.example.myprivatedatabase.provider", "delete", DELETESUCCESS);

    }

    private MyOpenHelper myOpenHelper;

    public AccountProvider() {
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int match = sURIMatcher.match(uri);
        if (match == DELETESUCCESS) {
            // 说明路径匹配成功
            SQLiteDatabase db = myOpenHelper.getWritableDatabase();
            // 代表影响的行数
            int deleteRow = db.delete("info", selection, selectionArgs);
            return deleteRow;
        } else {
            return 0;
        }
    }

    @Override
    public String getType(Uri uri) {
        // TODO: Implement this to handle requests for the MIME type of the data
        // at the given URI.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        int code = sURIMatcher.match(uri);
        if (code == INSERTSUCCESS) {
            // 路径匹配成功
            SQLiteDatabase db = myOpenHelper.getWritableDatabase();
            // 返回值代表新插入行数的id,比如已有2条数据,再插入返回行号3
            long insert = db.insert("info", null, values);
            Uri uri2 = Uri.parse("com.example.myprivatedatabase.insert/" + insert); // 随便写,供日志方便查看
            return uri2;
        } else {
            return null;
        }
    }

    @Override
    public boolean onCreate() {
        myOpenHelper = new MyOpenHelper(getContext());

        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        int code = sURIMatcher.match(uri);
        if (code == QUERYSUCCESS) {
            // 说明路径匹配成功,把query方法给实现,数据库的查询方法,对数据库进行查询操作
            // 想操作数据库必须获得sqlitedatabase对象
            SQLiteDatabase db = myOpenHelper.getReadableDatabase();
            Cursor cursor = db.query("info", projection, selection, selectionArgs, null, null, sortOrder);
            // 这里cursor不能关闭
            return cursor;
        } else {
            // 路径不匹配
            Toast.makeText(getContext(), "您的路径不匹配,请检查", Toast.LENGTH_SHORT).show();
            return null;
        }
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        int code = sURIMatcher.match(uri);
        if (code == UPDATESUCCESS) {
            SQLiteDatabase db = myOpenHelper.getWritableDatabase();
            int updateRow = db.update("info", values, selection, selectionArgs);
            return updateRow;
        } else {
            return 0;
        }
    }
}

 查看清单文件添加如下:

        <!--authorities是自定义的-->
        <provider
            android:name=".AccountProvider"
            android:authorities="com.example.myprivatedatabase.provider"
            android:enabled="true"
            android:exported="true"></provider>

另一个程序:

activity_main.xml

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

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="click"
        android:text="add" />

    <Button
        android:id="@+id/btn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="click"
        android:text="delete" />

    <Button
        android:id="@+id/btn3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="click"
        android:text="update" />

    <Button
        android:id="@+id/btn4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="click"
        android:text="find" />
</LinearLayout>

MainActivity.java

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.Toast;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 需求:读取第一个应用私有的数据库
        /*// factory: 游标工厂
        // flags:访问模式
        SQLiteDatabase.openDatabase("/data/data/com.example.myprivatedatabase/databases/Account.db", null, )*/


    }

    // 由于第一个应用里面的私有数据库已经通过内容提供者给暴露出来了,所以可以直接通过内容的解析者进行访问
    // 拿到内容的解析者,直接通过上下文获取
    public void click(View view) {
        switch (view.getId()) {
            case R.id.btn1: // add
                Uri uri1 = Uri.parse("content://com.example.myprivatedatabase.provider/insert");
                ContentValues values = new ContentValues();
                // key对应表的字段,value对应值
                values.put("name", "zhaoliu");
                values.put("money", "111111");
                Uri insert = getContentResolver().insert(uri1, values);
                Log.d(TAG, "insert:" + insert.toString());
                break;
            case R.id.btn2: // delete
                Uri uri2 = Uri.parse("content://com.example.myprivatedatabase.provider/delete");
                int delete = getContentResolver().delete(uri2, "name=?", new String[]{"zhaoliu"});
                Toast.makeText(this, "删除了" + delete + "行", Toast.LENGTH_SHORT).show();
                break;
            case R.id.btn3: // update
                Uri uri3 = Uri.parse("content://com.example.myprivatedatabase.provider/update");
                ContentValues values1 = new ContentValues();
                values1.put("money", 0.00);
                int update = getContentResolver().update(uri3, values1, "name=?", new String[]{"zhaoliu"});
                Toast.makeText(this, "更新了" + update + "行", Toast.LENGTH_SHORT).show();
                break;
            case R.id.btn4: // find
                Uri uri4 = Uri.parse("content://com.example.myprivatedatabase.provider/query");// 路径和定义的路径一样
                Cursor cursor = getContentResolver().query(uri4, null, null, null, null);
                if (cursor.moveToFirst()) {
                    do {
                        String name = cursor.getString(cursor.getColumnIndex("name"));
                        String money = cursor.getString(cursor.getColumnIndex("money"));
                        Log.d(TAG, "第二个应用name" + name + "---money:" + money);
                    } while (cursor.moveToNext());
                }
                break;
        }
    }
}

insert后再find:

update:

再find:

接着delete:

再find:

通过内容提供者备份短信:

android系统自带短信数据库,利用内容提供者向外暴露接口,查看源码:

查看android系统源码,找到我们想要查询的Uri,因为这个Uri并不是我们定义的

根目录: /ackages/providers/TelephonyProvider/AndroidManifest.xml

看到这里

然后去找这个SmsProvider类

看到目录:

根目录: /packages/providers/TelephonyProvider/src/com/android/providers/telephony/SmsProvider.java

预览一下系统的短信数据库,7.0以上看不到,可以用6.0及以下的了解一下系统的短信数据库是什么样的,等下用真机8.0系统测试:

在android目录/data/data/com.android.providers.telephony/databases/mmssms.db,保存出来

在Sqlite Expert查看

利用xml序列化器备份短信

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

import org.xmlpull.v1.XmlSerializer;

import java.io.File;
import java.io.FileOutputStream;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

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

    public void click(View view) {
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_SMS)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_SMS}, 1);
        } else {
            backSms();
        }
    }

    private void backSms() {
        try {
            // 获取xml序列化
            XmlSerializer serializer = Xml.newSerializer();
            // 设置序列化参数
            File file = new File(getFilesDir(), "backupSms.xml");
            FileOutputStream fos = new FileOutputStream(file);
            // 忘了设置输出格式和编码方式会java.lang.NullPointerException: Attempt to invoke virtual method 'void java.io.Writer.write(char[], int, int)' on a null object reference
            serializer.setOutput(fos, "utf-8");
            // 开始写xml文档开头
            serializer.startDocument("utf-8", true);
            // 开始写根节点
            serializer.startTag(null, "smss");
            Uri uri = Uri.parse("content://sms/");// 什么都不写表示null的情况下表示查询全部
            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);

                // 写sms结点
                serializer.startTag(null, "sms");
                // 写address结点
                serializer.startTag(null, "address");
                serializer.text(address);
                serializer.endTag(null, "address");

                // 写body结点
                serializer.startTag(null, "body");
                serializer.text(body);
                serializer.endTag(null, "body");

                // 写date结点
                serializer.startTag(null, "date");
                serializer.text(date);
                serializer.endTag(null, "date");

                serializer.endTag(null, "sms");
                Log.d(TAG, "body:" + body);
            }
            serializer.endTag(null, "smss");
            serializer.endDocument();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    backSms();
                } else {
                    Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }
    }
}

然后在清单文件添加:

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

华为荣耀V9系统8.0.0真机测试成功:

 

关于文件存储位置和getExternalStorageDirectory()和getExternalFilesDir()问题可以看这篇博客:

https://blog.csdn.net/nugongahou110/article/details/48154859

如果是需要向手机写短信,在5.0及以上是无法实现的了,4.4之前还可以,android为了防止第三方软件拦截短信和乱写入短信记录,在4.4之后,设置了只有默认的短信应用才会有权限操作短信数据库。

写短信的代码如下(愚人节给别人手机写短信95555发送银行卡余额没了):

    public void click(View view) {
        // 由于短信数据库已经通过内容提供者暴露出来了,所以我们可以直接通过内容解析者操作数据库
        Uri uri = Uri.parse("content://sms/");
        ContentValues values = new ContentValues();
        values.put("address", "95555");
        values.put("body", "您的余额为0.0000元");
        values.put("date", System.currentTimeMillis());
        getContentResolver().insert(uri, values);
    }

权限加上

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

查询联系人信息:

为了理解,先在6.0系统导出来查看

目录:/data/data/com.android.providers.contacts/databases/contacts2.db

可以看到:

data1里面存储的是所有联系人的信息

data表里面的raw_contact_id 实际上是raw_contact表的contact_id

data表里面的mimetype_id列实际对应mimetypes表

实现步骤:

1.先读取raw_contact表,读取contact_id字段,从而就知道手机里有多少联系人

2.再读取data表,根据raw_contact_id读取data1列、mimetype列(注意不能直接读出mimietype_id列)

暗示相关代码在com.android.contacts目录

根目录: /packages/providers/ContactsProvider/src/com/android/providers/contacts/ContactsProvider2.java

代码:

MainActivity.java

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

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @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);
        } else {
            readContacts();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    readContacts();
                } else {
                    Toast.makeText(this, "you denied the permission", Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }

    private void readContacts() {
        // 由于联系人的数据库也是通过内容提供者暴露出来了,所以操作数据库用内容解析者
        // 先查询raw_contact表的contact_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);
            Log.d(TAG, "\n\n\n=================contact_id:" + contact_id);
            // 根据raw_contact_id查询data表,查询data1列和mimetype_id列
            // 小细节 查询的不是data表,查询的是view_data的视图(多张表的组合)
            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);
                //Log.d(TAG, "data1:" + data1);
                Log.d(TAG, "下一条信息的mimetype:" + mimetype);
                if ("vnd.android.cursor.item/name".equals(mimetype)) {
                    Log.d(TAG, "姓名:" + data1);
                } else if ("vnd.android.cursor.item/phone_v2".equals(mimetype)) {
                    Log.d(TAG, "电话号码:" + data1);
                } else if ("vnd.android.cursor.item/vnd.com.tencent.mobileqq.voicecall.profile".equals(mimetype)) {
                    Log.d(TAG, "电话号码:" + data1);
                } else if ("vnd.android.cursor.item/email_v2".equals(mimetype)) {
                    Log.d(TAG, "邮箱:" + data1);
                } //  根据需要设置查询
            }
        }
    }
}

在清单文件加上权限声明

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

华为荣耀V9,android8.0.0真机调试:

 

 

之所以会有重复信息,而且只查得到名字和mimetype类型却查不到其他信息是因为我们之前有过多次操作,其他的应用QQ、微信 都会操作我们的联系人数据库,所以出现这么多让人感觉到多余的信息。

提示:设置手机联系人数据库的时候,哪怕我们删除了某联系人的信息,其实在数据库里信息仍然存在,只是把contact_id置为了null,这样拿到别人手机店里恢复联系人也是用这个原理

插入联系人:

1.先往raw_contact表的contact_id列插入数据

2.同步到data表,data1列存储的是所有联系人数据  

(7.0的模拟器测试成功,但是8.0的真机失败,不知道是不是个人手机问题,插入之后显示无姓名等信息,只是多了一个条目)

MainActivity.java

import android.Manifest;
import android.content.ContentValues;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private EditText et_email;
    private EditText et_phone;
    private EditText et_name;

    @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 onclick(View view) {
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_CONTACTS)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
        } else {
            writeContacts();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    writeContacts();
                } else {
                    Toast.makeText(this, "you denied the permission", Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }


    private void writeContacts() {
        // 获取数据
        String name = et_name.getText().toString().trim();
        String phone = et_phone.getText().toString().trim();
        String email = et_email.getText().toString().trim();
        // 没有插入到视图,视图只能进行查询的逻辑
        // 定义uri
        Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
        Uri dataUri = Uri.parse("content://com.android.contacts/data");
        // 先查询一下raw_contacts表一共多少数据,行数+1就是contact_id的值
        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);

        // 把name phone email 插入到data表
        ContentValues nameValues = new ContentValues();
        nameValues.put("data1", name); // 把数据插入到data1列
        nameValues.put("raw_contact_id", contact_id);   // 告诉数据库我们插入的数据属于哪条联系人
        nameValues.put("mimetype", "vnd.android.cursor.item/name"); // 告诉数据库插入的数据类型
        getContentResolver().insert(dataUri, nameValues);

        ContentValues phoneValues = new ContentValues();
        phoneValues.put("data1", phone);// 把数据插入到data1列
        phoneValues.put("raw_contact_id", contact_id); // 告诉数据库我们插入的数据属于哪条联系人
        // vnd.android.cursor.item/phone_v2
        phoneValues.put("mimetype", "vnd.android.cursor.item/vnd.com.tencent.mobileqq.voicecall.profile"); // 告诉数据库插入的数据类型
        getContentResolver().insert(dataUri, phoneValues);

        ContentValues emailValues = new ContentValues();
        emailValues.put("data1", email);// 把数据插入到data1列
        emailValues.put("raw_contact_id", contact_id); // 告诉数据库我们插入的数据属于哪条联系人
        emailValues.put("mimetype", "vnd.android.cursor.item/email_v2"); // 告诉数据库插入的数据类型
        getContentResolver().insert(dataUri, emailValues);
        Log.d(TAG, "==============插入结束: ");
    }
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.insertcontacts">

    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

内容观察者:

创建数据库的程序中,暴露增删改查的逻辑如下(第一个程序):

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.widget.Toast;

public class AccountProvider extends ContentProvider {
    // 定义路径匹配器
    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    private static final int QUERYSUCCESS = 0;
    private static final int INSERTSUCCESS = 1;
    private static final int UPDATESUCCESS = 2;
    private static final int DELETESUCCESS = 3;

    // 定义静态代码块,添加匹配规则
    static {
        /**
         * authority参数要和清单文件一致
         */
        // 相当于有了一个uri为content://com.example.myprivatedatabase.provider/query
        sURIMatcher.addURI("com.example.myprivatedatabase.provider", "query", QUERYSUCCESS);
        sURIMatcher.addURI("com.example.myprivatedatabase.provider", "insert", INSERTSUCCESS);
        sURIMatcher.addURI("com.example.myprivatedatabase.provider", "update", UPDATESUCCESS);
        sURIMatcher.addURI("com.example.myprivatedatabase.provider", "delete", DELETESUCCESS);

    }

    private MyOpenHelper myOpenHelper;

    public AccountProvider() {
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int match = sURIMatcher.match(uri);
        if (match == DELETESUCCESS) {
            // 说明路径匹配成功
            SQLiteDatabase db = myOpenHelper.getWritableDatabase();
            // 代表影响的行数
            int deleteRow = db.delete("info", selection, selectionArgs);
            db.close();
            if (deleteRow > 0) {
                // 发送一条消息,说明数据库发生了改变
                getContext().getContentResolver().notifyChange(uri, null);
            }
            return deleteRow;
        } else {
            return 0;
        }
    }

    @Override
    public String getType(Uri uri) {
        // TODO: Implement this to handle requests for the MIME type of the data
        // at the given URI.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        int code = sURIMatcher.match(uri);
        if (code == INSERTSUCCESS) {
            // 路径匹配成功
            SQLiteDatabase db = myOpenHelper.getWritableDatabase();
            // 返回值代表新插入行数的id,比如已有2条数据,再插入返回行号3
            long insert = db.insert("info", null, values);
            db.close();
            if (insert > 0) {
                // 发送一条消息,说明数据库发生了改变
                getContext().getContentResolver().notifyChange(uri, null);
            }
            Uri uri2 = Uri.parse("com.example.myprivatedatabase.insert/" + insert); // 随便写,供日志方便查看
            return uri2;
        } else {
            return null;
        }
    }

    @Override
    public boolean onCreate() {
        myOpenHelper = new MyOpenHelper(getContext());

        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        int code = sURIMatcher.match(uri);
        if (code == QUERYSUCCESS) {
            // 说明路径匹配成功,把query方法给实现,数据库的查询方法,对数据库进行查询操作
            // 想操作数据库必须获得sqlitedatabase对象
            SQLiteDatabase db = myOpenHelper.getReadableDatabase();
            Cursor cursor = db.query("info", projection, selection, selectionArgs, null, null, sortOrder);
            // 这里cursor不能关闭
            // 发送一条消息,说明数据库发生了改变
            getContext().getContentResolver().notifyChange(uri, null);
            return cursor;
        } else {
            // 路径不匹配
            Toast.makeText(getContext(), "您的路径不匹配,请检查", Toast.LENGTH_SHORT).show();
            return null;
        }
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        int code = sURIMatcher.match(uri);
        if (code == UPDATESUCCESS) {
            SQLiteDatabase db = myOpenHelper.getWritableDatabase();
            int updateRow = db.update("info", values, selection, selectionArgs);
            db.close();
            if (updateRow > 0) {
                // 发送一条消息,说明数据库发生了改变
                getContext().getContentResolver().notifyChange(uri, null);
            }
            return updateRow;
        } else {
            return 0;
        }
    }
}

读取其他程序数据库的逻辑如下(第二个程序):

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.util.Xml;
import android.view.View;
import android.widget.Toast;

import org.xmlpull.v1.XmlSerializer;

import java.io.File;
import java.io.FileOutputStream;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 需求:读取第一个应用私有的数据库
        /*// factory: 游标工厂
        // flags:访问模式
        SQLiteDatabase.openDatabase("/data/data/com.example.myprivatedatabase/databases/Account.db", null, )*/


    }

    // 由于第一个应用里面的私有数据库已经通过内容提供者给暴露出来了,所以可以直接通过内容的解析者进行访问
    // 拿到内容的解析者,直接通过上下文获取
    public void click(View view) {
        switch (view.getId()) {
            case R.id.btn1: // add
                Uri uri1 = Uri.parse("content://com.example.myprivatedatabase.provider/insert");
                ContentValues values = new ContentValues();
                // key对应表的字段,value对应值
                values.put("name", "zhaoliu");
                values.put("money", "111111");
                Uri insert = getContentResolver().insert(uri1, values);
                Log.d(TAG, "insert:" + insert.toString());
                break;
            case R.id.btn2: // delete
                Uri uri2 = Uri.parse("content://com.example.myprivatedatabase.provider/delete");
                int delete = getContentResolver().delete(uri2, "name=?", new String[]{"zhaoliu"});
                Toast.makeText(this, "删除了" + delete + "行", Toast.LENGTH_SHORT).show();
                break;
            case R.id.btn3: // update
                Uri uri3 = Uri.parse("content://com.example.myprivatedatabase.provider/update");
                ContentValues values1 = new ContentValues();
                values1.put("money", 0.00);
                int update = getContentResolver().update(uri3, values1, "name=?", new String[]{"zhaoliu"});
                Toast.makeText(this, "更新了" + update + "行", Toast.LENGTH_SHORT).show();
                break;
            case R.id.btn4: // find
                Uri uri4 = Uri.parse("content://com.example.myprivatedatabase.provider/query");// 路径和定义的路径一样
                Cursor cursor = getContentResolver().query(uri4, null, null, null, null);
                if (cursor.moveToFirst()) {
                    do {
                        String name = cursor.getString(cursor.getColumnIndex("name"));
                        String money = cursor.getString(cursor.getColumnIndex("money"));
                        Log.d(TAG, "第二个应用name" + name + "---money:" + money);
                    } while (cursor.moveToNext());
                }
                break;
        }
    }
}

创建内容观察者的逻辑如下(第三个程序):

import android.database.ContentObserver;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 注册内容观察者
        Uri uri = Uri.parse("content://com.example.myprivatedatabase.provider");
        // 如果第二个参数为true,那么uri只需要匹配前缀就可以,如果为false,
        // 则需要比如content://com.example.myprivatedatabase.provider/query
        getContentResolver().registerContentObserver(uri, true, new MyContentObserver(new Handler()));

    }

    // 定义一个内容观察者
    private class MyContentObserver extends ContentObserver {

        /**
         * Creates a content observer.
         *
         * @param handler The handler to run {@link #onChange} on, or null if none.
         */
        public MyContentObserver(Handler handler) {
            super(handler);
        }

        // 当内容发生改变时调用
        @Override
        public void onChange(boolean selfChange) {
            Log.d(TAG, "数据库的内容发生了改变");
            super.onChange(selfChange);
        }
    }
}

 点击add,delete,query按钮都会显示“数据库的内容发生改变”,点击update,只要更新不为0行,就会显示“数据库的内容发生改变”。

==========================Talk is cheap, show me the code======================== 

猜你喜欢

转载自blog.csdn.net/qq_34115899/article/details/82145998