目录
31、Android开发基础之内容提容者ContentProvider

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

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

其中有:

Activity

服务Service

广播接收者

什么是内容提供者?

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

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

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

相关的视频下载:

链接:https://pan.baidu.com/s/1qZ30FSS 密码:m2dg

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

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

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

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

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

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

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

创建数据库

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

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

数据库的创建:

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

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

java
复制代码
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

java
复制代码
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

插入10条记录:

Snip20180706_2.png

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

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

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

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

Snip20180715_28.png

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

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

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

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

内容提供者

创建内容提者的步骤:

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

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

java
复制代码
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

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

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

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

Snip20180715_29.png

provider最好加多一项:

android:exported=”true”

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

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

java
复制代码
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;
    }
}

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

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

java
复制代码
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

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上层应用源码下载

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

解压源码以后,找到:

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

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

Snip20180915_2.png

接着我们去看看代码:

Snip20180915_3.png

java
复制代码
    /** The authority for the contacts provider */
    public static final String AUTHORITY = "com.android.contacts";

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

Snip20180915_5.png

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

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

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

代码如下:

java
复制代码
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));
        }
    }
}

运行结果:

java
复制代码
contact_id -- > 1
contact_id -- > 2
contact_id -- > 3
contact_id -- > 4

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

java
复制代码
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();
        }

    }
}

运行结果如下:

shell
复制代码
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
    |-------------------------------|

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

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

java
复制代码
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);
        }

    }
}

输出结果:

shell
复制代码
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'}

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

xml
复制代码
<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

我们把数据库导出来

数据库是怎么样的呢?

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就是短信内容提供者的类了,同学们应该也看到了,需要读写的权限,需要在我们的清单文件里声明短信的读写权限

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

java
复制代码

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

运行结果:

shell
复制代码
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,到这里就可以获取到了 短信的内容了。

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

总结一下吧

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

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

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

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

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

本文由 拉大锯 原创发布于 阳光沙滩 , 未经作者授权,禁止转载
评论
0 / 1024
武哥有话说
哥 数据库创建那个部分 你接口IStudentScoreDao代码贴一下呗 不知道里边有啥信息
2020-12-08 08:35回复
JeffChen
过了一年才看到这课程,可惜了没早点看到
2020-08-30 13:42回复
成功从此刻开始
楼主辛苦了,看完笔记,我要准备开始学习了 哈哈哈
2019-12-19 12:19回复
回复@成功从此刻开始嗯,好好学吧。遇到问题可以在问答模块里发帖子。
2019-12-20 02:15回复
芒果儿
写的很详细,一会试试
2019-10-25 00:43回复
回复@芒果儿近期我会把视频补上,感谢支持。
2019-10-25 03:34回复
推荐文章
大模型推理参数解码:温度、Top-p 与核采样如何塑造生成文本的灵魂
探索大语言模型的采样参数如何塑造生成内容,从温度到Top-p,从惩罚机制到熵控制,揭示背后的信息论原理与实践策略。了解如何通过参数调优,让AI既保持知识的严谨,又拥有创造力的自由。
记从0使用Claude code写代码
本文介绍了如何使用Claude Code搭配DeepSeek进行编程,详细讲解了安装Node.js、全局安装Claude、配置模型以及使用方法。对于开发者来说,这是一份实用的技术教程,尤其适合对AI编程工具感兴趣的读者。
Android-Studio-Gradle同步失败-Library为null的通用排查指南
遇到 Android Studio Gradle Sync 失败时,不要轻易删除全局缓存。本文详细解析了错误原因,并提供了一系列精准的排查步骤和解决方案,帮助开发者快速定位并解决问题,提升开发效率。
PHP实现密码加密
本文详细介绍了Bcrypt密码加密技术及其在PHP中的应用,帮助开发者提升用户密码的安全性。通过实际代码示例,展示了如何使用password_hash和password_verify函数进行密码加密与验证,是学习网络安全知识的实用指南。
WordCloud效果,滚动标签
本文详细介绍了如何在Vue项目中集成WordCloud组件,包括依赖安装、属性配置和使用方法。适合开发者学习如何实现数据可视化功能,提升项目交互体验。
从0使用WordPress搭建一个优美的网站
本文详细介绍了如何使用WordPress搭建一个美观的网站,从安装到主题配置和功能拓展,为读者提供了实用的操作指南。无论是初学者还是有一定经验的开发者,都能从中获得有价值的参考。
JS实现在网站底部添加运行时间
想知道如何在网站底部显示运行时间?本文详细讲解了通过JavaScript实现这一功能的方法,包括时间计算逻辑和代码实现。适合前端开发者学习参考,轻松为网站添加实用功能。
在Vercel上部署Hexo博客
本文详细介绍了如何在Vercel上快速部署Hexo博客,无需后端服务即可实现高效发布。相比传统方式,省去了手动生成和上传静态文件的步骤,更加便捷。适合想要搭建个人博客的开发者参考。
从0搭建一个Hexo博客
本文详细介绍了Hexo博客框架的使用方法,从安装到部署全流程讲解,适合想快速搭建个人博客的技术爱好者。内容清晰易懂,是入门Hexo的理想指南。
离线设备激活方案:古老的 Windows 光盘激活,离线算法授权等
本文深入解析了离线激活的核心原理与实现方式,从历史案例到现代技术,全面剖析了如何在无网络环境下确保软件授权的安全性。通过设备指纹、授权文件校验等手段,为开发者提供了可复用的架构设计思路,适用于工业、医疗等对网络依赖较低的场景。
Hexo实现生成站点地图
想为你的Hexo博客添加站点地图功能吗?本文详细介绍了如何通过安装插件和配置文件来实现,适合没有内置该功能的新主题或自定义主题的用户。简单步骤助你提升搜索引擎优化效果。
Hexo实现代码压缩
本文分享了如何通过Hexo插件优化博客性能,详细介绍了安装和配置过程,帮助提升网页加载速度。适合对网站优化感兴趣的开发者阅读。
记一次 GitHub 幽灵协作者大清洗:强制重写 Git 历史与穿透 CDN 缓存实践
本文详细讲解了如何解决GitHub上出现的‘幽灵协作者’问题,通过重写Git历史和穿透CDN缓存,彻底清理错误提交记录。适合开发者学习如何高效管理项目历史与优化仓库信息。
从一行 `native` 堆栈追到 InstallReferrer:一次主线程 ANR 的排查全过程
本文详细记录了一次主线程ANR的排查过程,从一行native堆栈追踪到InstallReferrer服务调用。通过分析堆栈、源码和系统调用,揭示了归因SDK在主线程同步调用Play商店服务导致的ANR问题,并提供了多维度的解决方案。对于Android开发者来说,是一篇深入浅出的技术实践指南。
Linux从 HelloWorld 到数据库服务注册
本文详细解析了Linux系统中服务注册的概念与实现,通过一个简单的Hello World示例,帮助读者理解如何将程序注册为systemd服务,并掌握相关操作命令。无论是数据库还是其他应用,了解服务注册机制都是系统管理的重要基础。
学习虚拟机的笔记
linux ps 命令详解,跟着敲一次就掌握了
深入了解 Linux 中最常用的进程查看命令 `ps`,掌握其各种用法和参数,适用于系统管理和故障排查。从基础到高级,全面解析 `ps` 的使用技巧,帮助您提升 Linux 运维技能。
ObjectMapper 入门:Java 对象与 JSON 之间的「翻译官」
了解ObjectMapper在Java中如何实现对象与JSON的转换,掌握其在Spring Boot项目中的应用及常见使用场景。本文详细解析了序列化/反序列化过程、API用法、与Spring MVC的关系以及与其他JSON库的对比,适合开发者快速上手和深入理解。
服务器一次中病毒的记录
本文详细描述了一次服务器异常流量的排查过程,发现大量外部IP与内部服务建立连接,疑似存在恶意程序。通过分析日志和图片,确认为恶意程序导致带宽占用过高,最终通过备份和删除操作解决问题。文章提供了技术排查思路和解决方案,对系统维护具有参考价值。
JavaWeb微服务脚手架搭建
本文介绍了构建微服务架构时常用的开发模板和核心组件,涵盖技术选型、依赖配置及版本差异分析。通过合理选择 Java 和 Spring Boot 版本,可以显著提升开发效率和系统性能,是开发者不可错过的实践指南。
面向 Java 程序员的 MinIO 入门教程
本文为Java程序员提供了一份详细的MinIO入门教程,涵盖MinIO的部署方法和Java SDK的集成使用。通过本文,您将学习如何在Java项目中高效管理桶和对象,快速上手MinIO这一高性能对象存储服务。
你知道:气和汽的区别吗?
了解‘气’和‘汽’的区别,掌握它们在不同语境下的含义与用法,帮助你更准确地使用中文。无论是日常交流还是写作,这对提升语言能力都大有裨益。
wsl update 下载不下来怎么办呀?
遇到Docker Desktop提示需要更新但无法解决?本文教你如何通过GitHub下载并安装WSL,轻松解决更新问题,适合使用x64芯片的用户。
今日经验:重置虚拟机的密码
本文详细记录了在KVM虚拟化环境中,如何通过virt-rescue工具重置遗忘的root密码。对于需要维护和管理虚拟机的IT人员来说,这是一份实用的排障指南,涵盖了从环境准备到密码修改的完整流程,帮助快速恢复系统访问权限。
今日工作:Android Health Connect 接入记录
本文详细讲解了如何将 Android Health Connect 接入到健康或运动类应用中,涵盖从配置、代码实现到测试验收的完整流程。适合希望统一健康数据管理、提升用户隐私合规性的开发者阅读。
Skill从入门到出家
探索AI Agent的核心能力——Skill,了解其模块化设计、渐进式披露机制和实际应用场景。从基础概念到高级实战,掌握如何构建可复用、可移植的AI技能,提升Agent处理复杂任务的能力。
Docker,Docker Compose,kubectl最近遇到的版本问题
本文分享了在使用Docker、Docker Compose和kubectl时遇到的版本问题及解决方法,适合需要更新或管理Linux系统中相关工具的开发者参考。
Google上架App退回
Google Play Console 抛出 16KB 内存页面大小合规性错误,导致应用无法上架。本文详细分析了错误原因,并提供了解决方案,帮助开发者适配 Android 15 的新要求。
国内常用的 npm 镜像源整理
在使用 npm 安装依赖时,国内开发者常常遇到速度慢的问题。本文整理了多个稳定且常用的国内 npm 镜像源,帮助提升依赖安装效率。还介绍了如何通过 nrm 工具快速切换镜像,非常适合需要优化开发环境的开发者。
列表项排序设计:分数索引思想与实践
本文介绍了分数索引思想在列表排序中的应用,通过实数轴上的插空方式实现高效插入与拖拽排序。适用于课程章节、导航菜单、看板列等多种场景,提供创建和更新时的业务规则及边界处理策略,帮助开发者优化排序性能并提升用户体验。