跳至主要內容

内容提供者ContentProvider


Android四大组件

内容提供者是四大组件之一,还记得Android的四大组件吗?

前面我们学习了三大组件了,对吧!

其中有:

Activityopen in new window

服务Serviceopen in new window

广播接收者open in new window

什么是内容提供者?

我们在学习一门新的东西的时候,我们都需要知道,是为什么,有什么用,怎么用!这是最基础的,当你会用以后,如果还有好奇心,可以去究其原理!

这篇文章需要的基础知识有,请看往回跟数据库相关的文章:

https://www.sunofbeach.net/c/1443881236287311874

后面的课程,我们会使用到系统上层应用的源码:

上面这个连接可以下载,后面我们需要分析系统的原码,才可以实现一些功能,大家先下载下来吧!

好,回到我们的内容提供者上面:ContentProvider

内容提供者,我们从字面上认识,就是一个提供内容的东西!

这东西常用吗?用得比较少,我做了四年android开发,只有一个项目用上了,就是蓝牙电话。为什么会用上呢?因为需要拿到联系人的数据!

内容提供者,就是向第三方暴露自己的数据库的!目前来说,只有google的短信/电话是这么做的,其他应用基本上不会这么做!所以呢,使用场景也少了!比如说微信/支付宝,在第一次使用的时候会询问你是否同意让它读取你的联系人/通讯录,就是通过内容提供者来读取的。

但是,这文章学是会详细地写给大家,还是值得一看的文章!

创建数据库

我们在学习之前,都是以Hello world的方式来学习!先是一个入门的例子!

现在,我们要做这样一件事情,在应用A里有数据库,和内容提供者。应用B通过内容提供者操作A的数据库。

数据库的创建:

比如说,我们有一个学生的成绩表,字段有:id、姓名、语文、英语、数学

首先,我们编写一个数据库的帮助类,StudentScoreDBHelper.java,继承自SQLiteOpenHelper

package com.sunofbeaches.providerdemo.db;

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

/**
 * Created by TrillGates on 18/7/5.
 * God bless my code!
 */
public class StudentScoreDBHelper extends SQLiteOpenHelper {
    //数据库名
    private static final String DB_NAME = "student.db";
    //数据库版本,如果数据库相关的不太懂,同学们可以去看数据库相关的视频课程
    private static final int DB_VERSION = 1;
    //表名,一般是前缀+表名
    public static final String TABLE_NAME = "sob_score";
    //字段,一般这么设计,我们这里只做演示
    public static final String ID = "_id";
    public static final String NAME = "name";
    public static final String SCORE_CHINESE = "scorechinese";
    public static final String SCORE_MATH = "scoremath";
    public static final String SCORE_ENGLISH = "scoreenglish";
    //数据库创建语句
    private static final String CREATE_TABLE_SQL = "create table " + TABLE_NAME +
            " (" + ID + " integer primary key autoincrement, " +
            NAME + " varchar(32), " +
            SCORE_CHINESE + " integer, " +
            SCORE_MATH + " integer, " +
            SCORE_ENGLISH + " integer" + " )";

    private static final String TAG = "StudentScoreDBHelper";

    public StudentScoreDBHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        Log.d(TAG, "create table...");
        //创建数据库
        sqLiteDatabase.execSQL(CREATE_TABLE_SQL);
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
        //升级数据库,具体的内容同学们去学习数据库相关的课程吧,前面有的
    }
}

还在DAO呢!我们暂时提供增删改查的接口,也就是CRUD

package com.sunofbeaches.providerdemo.db;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import com.sunofbeaches.providerdemo.domain.StudentScore;

/**
 * Created by TrillGates on 18/7/6.
 * God bless my code!
 */
public class StudentScoreDAO implements IStudentScoreDao {

    private final Context mContext;
    private final StudentScoreDBHelper mStudentScoreDBHelper;

    private StudentScoreDAO(Context context) {
        this.mContext = context;
        mStudentScoreDBHelper = new StudentScoreDBHelper(context);
    }

    private static StudentScoreDAO sInstance = null;

    /**
     * 获取实例对象
     *
     * @param context 上下文
     * @return 返回学生成绩的DAO
     */
    public static IStudentScoreDao getInstance(Context context) {
        if (sInstance == null) {
            synchronized (StudentScoreDAO.class) {
                sInstance = new StudentScoreDAO(context);
            }
        }
        return sInstance;
    }

    @Override
    public void addStudentScore(String studentName, int chinese, int math, int english) {
        SQLiteDatabase writableDatabase = mStudentScoreDBHelper.getWritableDatabase();
        try {
            //
            ContentValues values = new ContentValues();
            values.put(StudentScoreDBHelper.NAME, studentName);
            values.put(StudentScoreDBHelper.SCORE_CHINESE, chinese);
            values.put(StudentScoreDBHelper.SCORE_MATH, math);
            values.put(StudentScoreDBHelper.SCORE_ENGLISH, english);
            //
            writableDatabase.beginTransaction();
            writableDatabase.insert(StudentScoreDBHelper.TABLE_NAME, null, values);
            writableDatabase.setTransactionSuccessful();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            writableDatabase.endTransaction();
            writableDatabase.close();
        }
    }

    @Override
    public StudentScore getStudentScoreByName(String name) {
        SQLiteDatabase readableDatabase = mStudentScoreDBHelper.getReadableDatabase();
        try {
            Cursor query = readableDatabase.query(StudentScoreDBHelper.TABLE_NAME,
                    new String[]{StudentScoreDBHelper.NAME},
                    null,
                    new String[]{name},
                    null,
                    null,
                    null);
            //
            readableDatabase.beginTransaction();
            StudentScore studentScore = new StudentScore();
            if (query.moveToNext()) {
                String studentName = query.getString(query.getColumnIndex(StudentScoreDBHelper.NAME));
                int chinese = query.getInt(query.getColumnIndex(StudentScoreDBHelper.SCORE_CHINESE));
                int math = query.getInt(query.getColumnIndex(StudentScoreDBHelper.SCORE_MATH));
                int english = query.getInt(query.getColumnIndex(StudentScoreDBHelper.SCORE_ENGLISH));
                studentScore.name = studentName;
                studentScore.scorechinese = chinese;
                studentScore.scoreenglish = english;
                studentScore.scoremath = math;
            }
            //
            readableDatabase.setTransactionSuccessful();
            return studentScore;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readableDatabase.endTransaction();
            readableDatabase.close();
        }
        return null;
    }

    @Override
    public int deleteStudentByName(String name) {
        SQLiteDatabase readableDatabase = mStudentScoreDBHelper.getReadableDatabase();
        try {
            readableDatabase.beginTransaction();
            int delete = readableDatabase.delete(StudentScoreDBHelper.TABLE_NAME,
                    "where " + StudentScoreDBHelper.NAME + " = ?",
                    new String[]{name});
            readableDatabase.setTransactionSuccessful();
            return delete;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readableDatabase.endTransaction();
            readableDatabase.close();
        }
        return 0;
    }

    @Override
    public int modifyStudentScore(String name, int chinese, int math, int english) {
        SQLiteDatabase writableDatabase = mStudentScoreDBHelper.getWritableDatabase();
        try {
            //
            ContentValues values = new ContentValues();
            values.put(StudentScoreDBHelper.SCORE_ENGLISH, english);
            values.put(StudentScoreDBHelper.SCORE_MATH, math);
            values.put(StudentScoreDBHelper.SCORE_CHINESE, chinese);
            //
            writableDatabase.beginTransaction();
            int update = writableDatabase.update(StudentScoreDBHelper.TABLE_NAME, values,
                    "where " + StudentScoreDBHelper.TABLE_NAME + " = ?", new String[]{name});
            writableDatabase.setTransactionSuccessful();
            return update;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            writableDatabase.endTransaction();
            writableDatabase.close();
        }
        return 0;
    }
}

测试一下吧,我们添加数据进去:

Snip20180706_1.png
Snip20180706_1.png

插入10条记录:

Snip20180706_2.png
Snip20180706_2.png

只有几个人及格,太让我失望了!

好啦,到此为止,我们的数据库部分就写完了,接下来我们到重点内容了,内容提供者!

为什么要使用内容提供者呢?

上面我们已经创建了数据库了,我们可以看出,数据库的权限是:

Snip20180715_28.png
Snip20180715_28.png

熟悉Linux的同学都知道,Linux的权限是这样分的,(-rw)前面个是用户的权限,(-rw)中间三个是同一个用户组的权限,(—)后面三个是其他用户的权限。

在android里面,每一个应用可以看做是一个应用,那么我们可以认为,其他应用是没办法访问到这个数据库的。有些情况下,需要访问别人的数据库,这个时候就需要我们的内容提供者了!

比如说今日头条需要访问淘宝的内容,根据淘宝的用户访问习惯来给使用今日头条的用户推荐定向广告,比如说我们开发一个蓝牙电话,需要向手机的联系人这个应用拿到手机的所有联系人,etc.

内容提供者,就是给别人暴露我们本应用里的数据库的,至于想暴露那些数据库,怎么样的其他应用才能访问,就看后面的内容吧!

内容提供者

创建内容提者的步骤:

第一步:编写一个类 继承自内容提供者(ContentProvider)这个例子的目的是让大家知道这个内容提供者的工作流程就够了!实际的使用我们后面再详细说明吧!

四大组件都是要继承自XXX的,总结到了吗?为什么呢?因为它要由系统去创建,生命周期由系统去管理呀!

package com.sunofbeaches.providerdemo.provider;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

/**
 * Created by TrillGates on 18/7/15.
 * God bless my code!
 */
public class StudentScoreProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
        return 0;
    }
}

第二步:注册一下呗!

我们四大组件都要注册,这个知道吧!所以我们需要在AndroidManifest.xml里进行注册!

taskpopout.gif
taskpopout.gif

我们可以发现,有两个参数必填的哦!name我们知道,是这填写空上类的全路径名称。

authorities是什么呢?其实就是令牌,口令,暗号!对得上,匹配得了的,才有权限来操作数据库。

一般来说,这个我们填写报名就OK了!如下:

Snip20180715_29.png
Snip20180715_29.png

provider最好加多一项:

android:exported=”true”

我们内容提供者的代码怎么写呢?

我们先暂时写一个查询的代码:

package com.sunofbeaches.providerdemo.provider;

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 com.sunofbeaches.providerdemo.db.StudentScoreDBHelper;

/**
 * Created by TrillGates on 18/7/15.
 * God bless my code!
 */
public class StudentScoreProvider extends ContentProvider {


    StudentScoreDBHelper mStudentScoreDBHelper;
    //定义一个Uri匹配器,参数表示不匹配的时候返回什么值,这里返回的是-1,也就是说-1表示不匹配
    private static UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    //这个表示匹配
    private static final int CODE_MATCH = 1;

    static {
        //添加匹配规则,前面是authority,这个其实就是我们在配置文件里配置的那个认证字符串
        //第二个参数是path,一般表示表名
        //第三个表示the code that is returned when a URI is matched,也就是说规则匹配则会返回后面那个code
        // 否则返回前面我们指定的默认 UriMatcher.NO_MATCH
        mUriMatcher.addURI("com.sunofbeaches.providerdemo","sob_score",CODE_MATCH);
    }

    @Override
    public boolean onCreate() {
        //注意,这里getContext()
        //Only available once
        //     * {@link #onCreate} has been called
        //里面的注释是:只有当onCreate方法被调用以后,getContext这个方法才可用。
        mStudentScoreDBHelper = new StudentScoreDBHelper(getContext());
        return false;
    }


    @Override
    public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
        int match = mUriMatcher.match(uri);
        if (match==CODE_MATCH) {
            SQLiteDatabase readableDatabase = mStudentScoreDBHelper.getReadableDatabase();
            return readableDatabase.query(StudentScoreDBHelper.TABLE_NAME, strings, s, strings1, s1, null, null);
        }else{
            throw new IllegalArgumentException("Uri not matching.");
        }
    }


    @Override
    public String getType( Uri uri) {
        return null;
    }

    
    @Override
    public Uri insert( Uri uri,  ContentValues contentValues) {
        return null;
    }

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

    @Override
    public int update( Uri uri,  ContentValues contentValues,  String s,  String[] strings) {
        return 0;
    }
}

相关的细节已经在注释里了!

接下来,我们要写另外一个应用,通过内容提供者的方式来获取到当前应用的数据了。

package com.sunofbeaches.providerdemoteacher;

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

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 getScore(View view) {
        ContentResolver contentResolver = this.getContentResolver();
        Uri uri = Uri.parse("content://com.sunofbeaches.providerdemo/sob_score");
        Cursor cursor = contentResolver.query(uri, null, null, null, null);
        while (cursor.moveToNext()) {
            Log.d(TAG, "id is -- > " + cursor.getInt(0));
            Log.d(TAG, "name is -- > " + cursor.getString(1));
            Log.d(TAG, "Chinese is -- > " + cursor.getInt(2));
            Log.d(TAG, "Math is -- > " + cursor.getInt(3));
            Log.d(TAG, "English is -- > " + cursor.getInt(4));
        }
        cursor.close();
    }
}

执行结果怎么样的呢?

Untitled6.gif
Untitled6.gif

OK,到这里的话,我们成功地读取到了另外一个应用的数据库内容了。

我们稍微总结一下:

首先是有一个数据库,但是这个数据库是别人家的呀,但是这个别人家的数据库有一个内容提供者呢!

我们只要知道对应的认证就可以读取到数据了!

所以,接下来我们就要仿今日头条,仿腾讯QQ,微信这些应用获取到手机号码,也就是通讯录的内容。

获取到通讯录的电话号码

有些时候,我们的应用为了推广,所以希望把用户的通讯录手机号码拿到,自动向他推荐好友。

这个时候,我们就可以通过内容提供者来获取到手机通讯录的联系人了。

Snip20180915_4.png
Snip20180915_4.png

以上的内容保存在哪个数据库里呢?

我直接帖出来吧:

/data/data/com.android.providers.contacts/databases/contacts2.db

我们把这个数据库导出来以后,用一些工具,或者用AS自带的工具就可以查看数据库了。

Snip20180915_1.png
Snip20180915_1.png

其实重要的表有三张:raw_contacts,data,mimetypes.

row_contacts主要是记录联系人的id,data记录数据,各种数据,包括邮箱呀,联系人名称呀,号码之类的,而mimeytypes用于记录data里的数据类型。因为data里同一个id同一个字段会有多条数据,比如说,id=1的联系人,data1字段,可能有联系人名称,联系人号码,邮箱等等,但是一条记录里会有mimetype的id,这样子就可以知道这条记录是联系人名称还是联系人号码了,具体请看下面的文章吧.

代码步骤如下:

1、获取到内容提供者:

通过context来获取到,也就是

ContentResolver contentResolver = getContentResolver();

2、我们需要Uri,但是不知道是什么,对吧!

但是我们知道,android是开源的呢!所以我们可以去看上层应用的源码:

Android上层应用源码下载open in new window

下载下来以后,我们去查看一下源码

解压源码以后,找到:

packages/providers/ContactsProvider/src/com/android/providers/contacts

下面的ContactsProvider2.java这个类,你问我怎么知道是这个类的呢?当然是看AndroidManifest.xml这个配置文件啦,你看一下就知道在哪里了:

Snip20180915_2.png
Snip20180915_2.png

接着我们去看看代码:

Snip20180915_3.png
Snip20180915_3.png
    /** The authority for the contacts provider */
    public static final String AUTHORITY = "com.android.contacts";

但是我们的URI,是不是要加上协议呢,也就是前面那部分:content://

Snip20180915_5.png
Snip20180915_5.png

在ContactsContract这个类里,我们看到有一个AUTHORITY_URI常量,也就是说,这个可以用,前面部分的:content://com.android.contacts部分就有了!但是我们还要path的内容呀!

前面我们说了,一般来说,path指的是表名,我们的思路是查询到联系人的id,再通过id去查询号码。

那么我们的URI就有了:AUTHORITY_URI+”/raw_contacts”

代码如下:

package com.sunofbeaches.providerdemoteacher;

import android.content.ContentResolver;
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 static android.provider.ContactsContract.AUTHORITY_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);
    }

    public void getScore(View view) {
        ContentResolver contentResolver = getContentResolver();
        Cursor cursor = contentResolver.query(Uri.parse(AUTHORITY_URI+"/raw_contacts"),//uri
                new String[]{"contact_id"},//要查的内容
                null,//条件
                null,//条件参数
                null,//排序
                null);
        while (cursor.moveToNext()) {
            Log.d(TAG, "contact_id -- > " + cursor.getInt(0));
        }
    }
}

运行结果:

contact_id -- > 1
contact_id -- > 2
contact_id -- > 3
contact_id -- > 4

有了id,以后,我们再去查询对应的号码之类的数据

package com.sunofbeaches.providerdemoteacher;

import android.content.ContentResolver;
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 java.util.ArrayList;
import java.util.List;

import static android.provider.ContactsContract.AUTHORITY_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);
    }


    public void getScore(View view) {
        List<Integer> contactIds = new ArrayList<>();
        ContentResolver contentResolver = getContentResolver();
        Cursor cursor = contentResolver.query(Uri.parse(AUTHORITY_URI + "/raw_contacts"),//uri
                new String[]{"contact_id"},//要查的内容
                null,//条件
                null,//条件参数
                null,//排序
                null);
        while (cursor.moveToNext()) {
            Log.d(TAG, "contact_id -- > " + cursor.getInt(0));
            contactIds.add(cursor.getInt(0));
        }
        cursor.close();

        for (Integer contactId : contactIds) {
            Cursor dataCursor = contentResolver.query(Uri.parse(AUTHORITY_URI + "/data"),
                    new String[]{"mimetype", "data1", "raw_contact_id"},
                    "raw_contact_id=?",
                    new String[]{contactId + ""},
                    null);
            while (dataCursor.moveToNext()) {
                Log.d(TAG, "contact info is -- > " +
                        "mimetype -- >  " + dataCursor.getString(dataCursor.getColumnIndex("mimetype")) +
                        "\ndata1 -- >  " + dataCursor.getString(dataCursor.getColumnIndex("data1")) +
                        "\nraw_contact_id -- >  " + dataCursor.getString(dataCursor.getColumnIndex("raw_contact_id")));
            }
            Log.d(TAG, "\n|-------------------------------|");
            dataCursor.close();
        }

    }
}

运行结果如下:

09-16 01:13:22.814 6309-6309/com.sunofbeaches.providerdemoteacher D/MainActivity: contact_id -- > 1
    contact_id -- > 2
    contact_id -- > 3
    contact_id -- > 4
09-16 01:13:22.818 6309-6309/com.sunofbeaches.providerdemoteacher D/MainActivity: contact info is -- > mimetype -- >  vnd.android.cursor.item/phone_v2
    data1 -- >  1 234-567-8901
    raw_contact_id -- >  1
    contact info is -- > mimetype -- >  vnd.android.cursor.item/name
    data1 -- >  Zhangsan
    raw_contact_id -- >  1
    |-------------------------------|
09-16 01:13:22.823 6309-6309/com.sunofbeaches.providerdemoteacher D/MainActivity: contact info is -- > mimetype -- >  vnd.android.cursor.item/phone_v2
    data1 -- >  1180-974-6573
    raw_contact_id -- >  2
    contact info is -- > mimetype -- >  vnd.android.cursor.item/name
    data1 -- >  Lisi
    raw_contact_id -- >  2
    |-------------------------------|
09-16 01:13:22.826 6309-6309/com.sunofbeaches.providerdemoteacher D/MainActivity: contact info is -- > mimetype -- >  vnd.android.cursor.item/phone_v2
    data1 -- >  668-9
    raw_contact_id -- >  3
    contact info is -- > mimetype -- >  vnd.android.cursor.item/name
    data1 -- >  HuangDachui
    raw_contact_id -- >  3
    |-------------------------------|
09-16 01:13:22.829 6309-6309/com.sunofbeaches.providerdemoteacher D/MainActivity: contact info is -- > mimetype -- >  vnd.android.cursor.item/phone_v2
    data1 -- >  1 353-232-3332
    raw_contact_id -- >  4
    contact info is -- > mimetype -- >  vnd.android.cursor.item/name
    data1 -- >  Chenzao
    raw_contact_id -- >  4
    |-------------------------------|

这样子,我们就可以获取到了联系人的数据了。

接着我们整理一下,就可以得到联系人名称和号码了,如果以后你还需要邮箱地址的话,获取方式也是一样的。

package com.sunofbeaches.providerdemoteacher;

import android.content.ContentResolver;
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 java.util.ArrayList;
import java.util.List;

import static android.provider.ContactsContract.AUTHORITY_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);
    }

    class ContactInfo {
        public int id;
        public String name;
        public String phoneNum;

        @Override
        public String toString() {
            return "ContactInfo{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", phoneNum='" + phoneNum + '\'' +
                    '}';
        }
    }


    public void getScore(View view) {
        List<ContactInfo> contactInfos = new ArrayList<>();
        List<Integer> contactIds = new ArrayList<>();
        ContentResolver contentResolver = getContentResolver();
        Cursor cursor = contentResolver.query(Uri.parse(AUTHORITY_URI + "/raw_contacts"),//uri
                new String[]{"contact_id"},//要查的内容
                null,//条件
                null,//条件参数
                null,//排序
                null);
        while (cursor.moveToNext()) {
            //Log.d(TAG, "contact_id -- > " + cursor.getInt(0));
            contactIds.add(cursor.getInt(0));
        }
        cursor.close();

        for (Integer contactId : contactIds) {
            ContactInfo contactInfo = new ContactInfo();
            contactInfo.id = contactId;
            Cursor dataCursor = contentResolver.query(Uri.parse(AUTHORITY_URI + "/data"),
                    new String[]{"mimetype", "data1", "raw_contact_id"},
                    "raw_contact_id=?",
                    new String[]{contactId + ""},
                    null);
            while (dataCursor.moveToNext()) {
//                Log.d(TAG, "contact info is -- > " +
//                        "mimetype -- >  " + dataCursor.getString(dataCursor.getColumnIndex("mimetype")) +
//                        "\ndata1 -- >  " + dataCursor.getString(dataCursor.getColumnIndex("data1")) +
//                        "\nraw_contact_id -- >  " + dataCursor.getString(dataCursor.getColumnIndex("raw_contact_id")));

                String mimetype = dataCursor.getString(dataCursor.getColumnIndex("mimetype"));
                if ("vnd.android.cursor.item/phone_v2".equals(mimetype)) {
                    String phoneNum = dataCursor.getString(dataCursor.getColumnIndex("data1"));
                    contactInfo.phoneNum = phoneNum;
                }

                if ("vnd.android.cursor.item/name".equals(mimetype)) {
                    String name = dataCursor.getString(dataCursor.getColumnIndex("data1"));
                    contactInfo.name = name;
                }

            }
            //Log.d(TAG, "\n|-------------------------------|");
            dataCursor.close();
            contactInfos.add(contactInfo);
        }

        //查询完成:输出结果
        for (ContactInfo contactInfo : contactInfos) {
            Log.d(TAG, "contactInfo -- > " + contactInfo);
        }

    }
}

输出结果:

contactInfo -- > ContactInfo{id=1, name='Zhangsan', phoneNum='1 234-567-8901'}
contactInfo -- > ContactInfo{id=2, name='Lisi', phoneNum='1180-974-6573'}
contactInfo -- > ContactInfo{id=3, name='HuangDachui', phoneNum='668-9'}
contactInfo -- > ContactInfo{id=4, name='Chenzao', phoneNum='1 353-232-3332'}

到此,我们就拿到了手机联系人的地址了。不过要注意的是,这需要权限呢:

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

读取联系人的权限,有了这些知识,是不是可以去做手机联系人备份了呢?

我们获取到了联系人,接下来,是不是要操作短信呢?

短信相关的内容提供者!

跟前面一样,我们首先要知道,短信是存在哪个数据库里的,存在什么表里的。这样子我们都知道怎么样去操作它。

/data/data/com.android.providers.telephony/databases/mmssms.db

在这个路径下就可以找到信息相关的内容了

但是我们没有短信内容,就创建一些吧

Snip20180916_1.png
Snip20180916_1.png

我们把数据库导出来

数据库是怎么样的呢?

address,目的发送的号码,比如说我要发送给

Snip20180916_3.png
Snip20180916_3.png

data,也就是时间

Snip20180916_4.png
Snip20180916_4.png

read表示的是是否已经读过,1表示已经读了,0表示未读

Snip20180916_6.png
Snip20180916_6.png

body表示内容

Snip20180916_7.png
Snip20180916_7.png

当然啦,还有其他的字段,比如说表示状态的,这个大家可以自己去看看啦!

接下来,我们就要写代码了,模拟给自己的手机发送一条短信。

首先,我们知道uri吧,怎么办?看代码呗

packages/providers/TelephonyProvider

先看清单文件

Snip20180916_8.png
Snip20180916_8.png

这一个Provider就是短信内容提供者的类了,同学们应该也看到了,需要读写的权限,需要在我们的清单文件里声明短信的读写权限

我们尝试一下读取短信的内容:


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 sendMsm(View view) {

        //获取到内容提供者解析器
        ContentResolver contentResolver = this.getContentResolver();
        Uri uri = Uri.parse("content://sms/");
        Cursor query = contentResolver.query(uri, null, null, null, null);
        while (query.moveToNext()) {
            String body = query.getString(query.getColumnIndex("body"));
            Log.d(TAG, "body -- > " + body);
        }
    }
}

运行结果:

09-17 14:39:43.601 3334-3334/? D/MainActivity: body -- > Your phone has been lost 
09-17 14:39:43.601 3334-3334/? D/MainActivity: body -- > 这是一条来自模拟器的短信

OK,到这里就可以获取到了 短信的内容了。

这样是不是有一个应用场景,监听到有短信来的广播,然后去读取短信,自动获取到验证码,自动填充到你的应用里呢。

总结一下吧

我们当我们需要暴露数据给第三方使用时,就需要知道前面的内容提供者怎么写了。

如果是读取别人的内容提供者内容,就需要后面的知识了。

学习内容提供者的话,顺便也要把数据库的知识巩固一下。

内容提供者的使用场景比较少,能列举出来就那么几个了。我只有在蓝牙电话上使用过内容提供者,当然啦,在我们的平时开发中,如果做社交软件的话,也需要获取到用户的联系人列表

然后保存到后台去,向用户推荐对应的好友用户。