内容提容者ContentProvider

# Android四大组件

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

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

其中有:

Activity (opens new window)

服务Service (opens new window)

广播接收者 (opens 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) {
        //升级数据库,具体的内容同学们去学习数据库相关的课程吧,前面有的
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

还在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;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141

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

Snip20180706_1.png

插入10条记录:

Snip20180706_2.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;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

第二步:注册一下呗!

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

taskpopout.gif

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

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

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

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;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

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

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

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();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

执行结果怎么样的呢?

Untitled6.gif

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

我们稍微总结一下:

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

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

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

# 获取到通讯录的电话号码

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

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

Snip20180915_4.png

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

我直接帖出来吧:

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

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

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上层应用源码下载 (opens new window)

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

解压源码以后,找到:

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

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

Snip20180915_2.png

接着我们去看看代码:

Snip20180915_3.png

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

1
2
3

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

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));
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

运行结果:

contact_id -- > 1
contact_id -- > 2
contact_id -- > 3
contact_id -- > 4
1
2
3
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();
        }

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

运行结果如下:

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
    |-------------------------------|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

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

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

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);
        }

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

输出结果:

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'}
1
2
3
4

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

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

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

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

# 短信相关的内容提供者!

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

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

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

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

Snip20180916_1.png

我们把数据库导出来

数据库是怎么样的呢?

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

Snip20180916_3.png

data,也就是时间

Snip20180916_4.png

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

Snip20180916_6.png

body表示内容

Snip20180916_7.png

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

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

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

packages/providers/TelephonyProvider

先看清单文件

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);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

运行结果:

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 -- > 这是一条来自模拟器的短信

1
2
3

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

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

# 总结一下吧

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

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

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

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

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

上次更新: 2021/10/22, 23:03:57