【Android】安卓四大组件之内容提供者

博客 动态
0 229
羽尘
羽尘 2022-01-26 20:54:34
悬赏:0 积分 收藏

【Android】安卓四大组件之内容提供者

【Android】安卓四大组件之内容提供者

1、关于内容提供者

1.1 什么是内容提供者

内容提供者就是contentProvider,作用有如下:

  1. 给多个应用提供数据
  2. 类似一个接口
  3. 可以和多个应用分享数据

1.2 为什么要有内容提供者

作为一个APP,自己的数据会在某些条件下提供给其他APP,但是,APP的数据是私有的。

例如,APP A的数据库内容是不可以被APP B进行读取的

这个时候,我们就需要一个内容提供者,将APP A中的数据信息提供给APP B。

1.3 使用场景

就贴近生活一些吧,拿某宝、拼夕夕等购物软件举例子,下面几种场景你肯定见过:

  • 获取通讯录中的联系人,申请好友。
  • 获取其他软件搜索记录,大数据计算,进行产品推送。
  • 预约直播,将预约信息写入手机备忘录

2、如何自定义内容提供者

2.1 写一个提供内容的APP

首先,在我们提供内容的APP中的manifest中,写入provider:

  • authorities可以是包名
  • name就是自己定义的名字
  • exported=true可以让其他的APP来访问自己提供的内容
<provider            android:authorities="top.woodwhale.picgo"            android:name=".test.contentprovider.provider.UserProvider"            android:exported="true"            android:enabled="true"            android:grantUriPermissions="true"/>

其次,我们操作的这个提供内容的APP,得有初始化后的数据库

  • 关于数据库,提几句:
  • 相关的内容,在之前的SQLite学习章节中说过了
  • 在一个项目中,该有的架构还是有的,如下图
  • image-20220125163457571
  • 其中dao是专门执行数据库操作的,有相关接口和实现类
  • db文件夹是存放数据库helper的,它的作用就是初始化数据库,并且可以返回数据操作对象
  • pojo就是从数据中转为对象的类
  • provider就是我们要写的内容提供者的类
  • utils中就是常用的工具类

提完了数据库,我们继续说内容提供者

一个具有内容提供者的APP中必须得有如下的类:

  • 该类继承ContentProvider,并且重写其中的方法(增删改查)
  • 赋予一个UriMatcher对象的成员变量
  • 进行一个Uri的匹配,authorities要和manifest中的一致,并且可以选择表进行内容共提供。这些都在静态代码块中实现,使用addURI方法即可
  • 重写增删改查方法,前提是Uri匹配!
public class UserProvider extends ContentProvider {    private static final String TAG = "UserProvider";    private UserDatabaseHelper dbh;    private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);    private static final int USER_MATCH_CODE = 1;    static {        uriMatcher.addURI("top.woodwhale.picgo","user",USER_MATCH_CODE);    }    @Override    public boolean onCreate() {        dbh = new UserDatabaseHelper(getContext());        return false;    }    @Nullable    @Override    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {        int res = uriMatcher.match(uri);        // 匹配规则        if (res == USER_MATCH_CODE) {            SQLiteDatabase db = dbh.getWritableDatabase();            return db.query(Constants.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);        } else {            throw new IllegalArgumentException("参数错误!");        }    }    @Nullable    @Override    public String getType(@NonNull Uri uri) {        return null;    }    @Nullable    @Override    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {        int res = uriMatcher.match(uri);        if (res == USER_MATCH_CODE) {            SQLiteDatabase db = dbh.getWritableDatabase();            long insertRes = db.insert(Constants.TABLE_NAME, null, values);            Uri resUri = Uri.parse("content://top.woodwhale.picgo/user/"+insertRes);            Log.d(TAG,"insertRes --> "+ insertRes);            // 插入数据成功,数据变化了,需要通知其他地方            getContext().getContentResolver().notifyChange(resUri,null);            return resUri;        } else {            throw new IllegalArgumentException("参数错误!");        }    }    @Override    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {        return 0;    }    @Override    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {        return 0;    }}

2.2 使用其他的APP来调用上述内容

我们写其他的一个APP来调用上述的内容:

首先是查询:

  • 注意Uri.parse("content://top.woodwhale.picgo/user")是我们在上面APP中写的:
    • top.woodwhale.picgoauthorities
    • user是表名
    • 对应上面的uriMatcher.addURI("top.woodwhale.picgo","user",USER_MATCH_CODE)
/**     * 测试通过内容提供者,获取picgo项目中搞得数据库内容     * @param view this activity     */public void getContent(View view) {    ContentResolver contentResolver = this.getContentResolver();    Uri uri = Uri.parse("content://top.woodwhale.picgo/user");    @SuppressLint("Recycle") Cursor cursor = contentResolver.query(uri, null, null, null, null);    String[] columnNames = cursor.getColumnNames();    Log.d(TAG, "columnNames.length --> "+String.valueOf(columnNames.length));    Log.d(TAG,"=================================");    while (cursor.moveToNext()) {        for (String columnName : columnNames) {            @SuppressLint("Range") String cursorString = cursor.getString(cursor.getColumnIndex(columnName));            Log.d(TAG,"cursorString --> " + cursorString);        }    }    Log.d(TAG,"=================================");}

点击测试:

MessageCenterUI_gjIgBUEiWt

然后是插入:

还是非常简单的:

/**     * 添加数据     * @param view this activity     */public void insertContent(View view) {    ContentResolver contentResolver = this.getContentResolver();    Uri uri = Uri.parse("content://top.woodwhale.picgo/user");    ContentValues values = new ContentValues();    values.put(Constants.FIELD_USERNAME,"wyh");    values.put(Constants.FIELD_PASSWORD,"114514");    values.put(Constants.FIELD_AGE,3);    values.put(Constants.FIELD_SEX,"男");    contentResolver.insert(uri,values);}

我们可以在onCreate的时候就注册一个内容观察者,当我们内容提供者的数据发生改变的时候,就可以监听到,也就是,我们插入成功就可以监听到

@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    Uri uri = Uri.parse("content://top.woodwhale.picgo/user");    ContentResolver contentResolver = getContentResolver();    contentResolver.registerContentObserver(uri, true, new ContentObserver(new Handler()) {        @Override        public void onChange(boolean selfChange) {            super.onChange(selfChange);            Log.d(TAG,"用户数据发生变化");        }    });}

注册成功之后,我们调用插入方法,可以发现已经被监听了,并且我们查询数据库,确实添加了如上的信息

MessageCenterUI_KoyYrWPhNU

2.3 内容提供者的小结

其实使用内容提供者非常的简单和便捷,但是又有多少APP敢将自身的数据提供给他人呢?

所以自己写的APP中的内容提供者少之又少,基本上都是同一厂商敢和自家APP联动剽取用户的信息共享。

但是在安卓手机中,有很多自带的APP,他们都具有内容提供者的对应接口,用来让常用的APP进行内容的增删改查,下面我们来进行常见内容提供者的学习!

3、使用“日历”内容提供者

在很多的情况下,我们会将一些事情写入到我们手机中的“日历”中,当到了预定的时间就会提醒,那么设置一个日历提醒事件怎么做到呢?——我们可以使用安卓开发给定的CalendarContract进行完成

CalendarContract是日历内容提供者和APP之间的一个合同,当我们的APP获取了读、写日历的权限之后,就可以对手机自带的这个"日历APP"进行添加事件的操作,我们通过下面的代码来认识一下!

3.1 获取日历权限

在安卓6.0,也就是SDK>=23的版本后,我们的APP权限需要动态申请,首先在manifest中申请日历的读写权限

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

然后我们在activity中写一个方法来动态的获取权限

// 成员变量private static final int PERMISSION_REQUEST_CODE = 1;private void initPermission() {    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {        String[] reqPermissions = new String[]{Manifest.permission.READ_CALENDAR,Manifest.permission.WRITE_CALENDAR};        for (String reqPermission : reqPermissions) {            if (checkSelfPermission(reqPermission) != PackageManager.PERMISSION_GRANTED) {                // 如果有没有授权的,就去提醒授权                requestPermissions(reqPermissions,PERMISSION_REQUEST_CODE);                break;            }        }    }}

同时我们还可以重写一个回调方法onRequestPermissionsResult,也就是权限获取结果的回调,如果拒绝了我们的权限申请,那么久finish()当前页面

@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {    if (requestCode == PERMISSION_REQUEST_CODE) {        for (int grantResult : grantResults) {            if (grantResult != PackageManager.PERMISSION_GRANTED) {                Log.d(TAG,"somePermissionsWereNotGranted");                finish();                break;            }        }        Log.d(TAG,"allPermissionsHaveBeenGiven...");        Toast.makeText(this, "allPermissionsHaveBeenGiven...", Toast.LENGTH_SHORT).show();    }}

3.2 有关日历的各种class和属性

下面我们正式开始,在开始之前,我们来看看有关日历的class的各种作用:

20191104_184557.png

3.3 获取一个日历用户

在获取完权限之后,我们可以获取一个日历用户的ID(前提是,日历程序中有这个用户)

我们调用contentResolver的query()方法,获得一个cursor,再查询其中的CalendarContract.Calendars._ID,这个id就是我们的用户ID

@SuppressLint("Range")private int getCalendarID() {    Log.d(TAG,"getCalendarUserId...");    ContentResolver contentResolver = this.getContentResolver();    Uri uri = CalendarContract.Calendars.CONTENT_URI;    Cursor cursor = contentResolver.query(uri, null, null, null, null, null);    cursor.moveToFirst();    int id = cursor.getInt(cursor.getColumnIndex(CalendarContract.Calendars._ID));    Log.d(TAG,"anInt --> " + id);    cursor.close();    return id;}

3.4 将内容写入日历

最后一步就是写入日历内容

  • 我们通过ContentValues对象的put方法,将我们的键值对写入其中

  • 有什么常量可以写入呢?

    20191105_184333.png

  • 写入规则

    image-20220125160204152

我们在写入成功之后,得到的Uri可以进行一个intent隐式意图的跳转,直接查看我们写入的事件

@RequiresApi(api = Build.VERSION_CODES.N)public void writeCalendarEvent(View view) {    long calID = getCalendarID();    if (calID == -1) {        // 如果没有账户,那么就终止这个方法        return;    }    // 设置开始时间,注意 month、date 从 0 开始    Calendar beginTime = Calendar.getInstance();    beginTime.set(2022,0,30,0,0);    long beginTimeTimeInMillis = beginTime.getTimeInMillis();    // 设置结束时间    Calendar endTime = Calendar.getInstance();    endTime.set(2022,0,30,23,59);    long endTimeTimeInMillis = endTime.getTimeInMillis();    // 设置内容values    String timeZone = TimeZone.getDefault().getID();    ContentValues values = new ContentValues();    values.put(CalendarContract.Events.DTSTART,beginTimeTimeInMillis);    values.put(CalendarContract.Events.DTEND,endTimeTimeInMillis);    values.put(CalendarContract.Events.CALENDAR_ID, calID);    values.put(CalendarContract.Events.EVENT_TIMEZONE,timeZone);    values.put(CalendarContract.Events.TITLE,"准备过年!");    values.put(CalendarContract.Events.DESCRIPTION,"冲就完了!");    values.put(CalendarContract.Events.EVENT_LOCATION,"九江");    // 插入数据    Uri uri = CalendarContract.Events.CONTENT_URI;    ContentResolver contentResolver = getContentResolver();    Uri res = contentResolver.insert(uri, values);    Log.d(TAG,"uriRes --> " + res);    gotoCalendar(res);}private void gotoCalendar(Uri res) {    Intent intent = new Intent(Intent.ACTION_VIEW)        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)        .setData(res);    startActivity(intent);}

3.5 设置日历事件提醒

在上面完成了之后,我们发现其实并没有开启提示模式,也就是说,到了当前并没有闹钟或者信息的通知

我们可以通过CalendarContract.Reminders来设置提醒

需要注意需要如下的常量value设置:

20191105_223511.png

@RequiresApi(api = Build.VERSION_CODES.N)public void writeCalendarEvent(View view) {    long calID = getCalendarID();    if (calID == -1) {        // 如果没有账户,那么就终止这个方法        return;    }    // 设置开始时间,注意 month、date 从 0 开始    Calendar beginTime = Calendar.getInstance();    beginTime.set(2022,0,30,0,0);    long beginTimeTimeInMillis = beginTime.getTimeInMillis();    // 设置结束时间    Calendar endTime = Calendar.getInstance();    endTime.set(2022,0,30,23,59);    long endTimeTimeInMillis = endTime.getTimeInMillis();    // 设置内容values    String timeZone = TimeZone.getDefault().getID();    ContentValues eventValues = new ContentValues();    eventValues.put(CalendarContract.Events.DTSTART,beginTimeTimeInMillis);    eventValues.put(CalendarContract.Events.DTEND,endTimeTimeInMillis);    eventValues.put(CalendarContract.Events.CALENDAR_ID, calID);    eventValues.put(CalendarContract.Events.EVENT_TIMEZONE,timeZone);    eventValues.put(CalendarContract.Events.TITLE,"准备过年!");    eventValues.put(CalendarContract.Events.DESCRIPTION,"冲就完了!");    eventValues.put(CalendarContract.Events.EVENT_LOCATION,"九江");    // 插入数据    Uri eventUri = CalendarContract.Events.CONTENT_URI;    ContentResolver contentResolver = getContentResolver();    Uri eventRes = contentResolver.insert(eventUri, eventValues);    Log.d(TAG,"eventRes --> " + eventRes);    // METHOD_ALERT提醒    String eventID = eventRes.getLastPathSegment();    ContentValues reminderValues = new ContentValues();    reminderValues.put(CalendarContract.Reminders.EVENT_ID,eventID);    reminderValues.put(CalendarContract.Reminders.MINUTES,15);    reminderValues.put(CalendarContract.Reminders.METHOD,CalendarContract.Reminders.METHOD_ALERT);    Uri reminderUri = CalendarContract.Reminders.CONTENT_URI;    Uri reminderRes = contentResolver.insert(reminderUri, reminderValues);    Log.d(TAG,"reminderRes --> " + reminderRes);    Toast.makeText(this, "信息通知已开启", Toast.LENGTH_SHORT).show();}

3.6 测试是否插入成功

普通添加效果图如下:

qemu-system-x86_64_1apbuq0Gyc

添加了提醒方法后的效果如下:

qemu-system-x86_64_ZEzUYMJuEX

可以发现,我们成功的将插入方法中的内容写入到了系统自带的“日历APP”中

4、使用“通讯录”内容提供者

某宝、某夕夕,经常会申请通讯录权限,然后帮你自动加好友。

有没有思考过一个问题,那就是,他们是如何读取你的通讯录的?

其实这个问题非常的简单,使用“通讯录”内容提供者就完全可以做到,只需要用户提供通讯录的权限即可。

使用的方法和“日历”非常类似,步骤都是一样的,看看源码或者市面上的教程都可以了解噢

我这里就直接放我写的一个testDemo了,如果需要查看更多的通讯录细则,建议阅读一下源码噢!

先写一个User类,其中封装了联系人的数据

package top.woodwhale.providertest.contactsResolver;import androidx.annotation.NonNull;public class User {    private int id;    private String phoneNumber;    private String name;    public User(int id, String phoneNumber, String name) {        this.id = id;        this.phoneNumber = phoneNumber;        this.name = name;    }    @NonNull    @Override    public String toString() {        return "User{" +            "id=" + id +            ", phoneNumber='" + phoneNumber + '\'' +            ", name='" + name + '\'' +            '}';    }    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getPhoneNumber() {        return phoneNumber;    }    public void setPhoneNumber(String phoneNumber) {        this.phoneNumber = phoneNumber;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }}

然后就是常规操作了:

  • 首先SDK>=23需要动态申请权限
  • 其次就是使用ContactsContract.Contacts来进行各种查询!
  • 如下代码是获取联系人id、name、phoneNumber
public class ThirdActivity extends Activity {    private static final String TAG = "ThirdActivity";    private static final int PERMISSION_REQUEST_CODE = 1;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_third);        // 获取权限        initPermission();    }    // 初始化权限    public void initPermission() {        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {            String[] reqPermissions = new String[]{Manifest.permission.READ_CONTACTS,Manifest.permission.WRITE_CONTACTS};            for (String reqPermission : reqPermissions) {                if (checkSelfPermission(reqPermission) != PackageManager.PERMISSION_GRANTED) {                    // 如果有没有授权的,就去提醒授权                    requestPermissions(reqPermissions,PERMISSION_REQUEST_CODE);                    break;                }            }        }    }    @Override    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {        if (requestCode == PERMISSION_REQUEST_CODE) {            String res = "allPermissionsHaveBeenGiven";            for (int grantResult : grantResults) {                if (grantResult != PackageManager.PERMISSION_GRANTED) {                    Log.d(TAG,"somePermissionsWereNotGranted");                    res = "somePermissionsWereNotGranted";                    finish();                    break;                }            }            Log.d(TAG,"allPermissionsHaveBeenGiven...");            Toast.makeText(this, res, Toast.LENGTH_SHORT).show();        }    }    @SuppressLint("Range")    public void getContactData(View view) {        ContentResolver contentResolver = getContentResolver();        Uri rawContactUri = ContactsContract.Contacts.CONTENT_URI;        Cursor cursor = contentResolver.query(rawContactUri, null, null, null, null, null);        Log.d(TAG,"cursorGetCount --> "+cursor.getCount());        Toast.makeText(this,"count == " + cursor.getCount(), Toast.LENGTH_SHORT).show();        while (cursor.moveToNext()) {            // 联系人ID            int id = cursor.getInt(cursor.getColumnIndex(ContactsContract.Contacts._ID));            // 联系人name            String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));            // 联系人号码个数            int numCount=cursor.getInt(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER));            // 联系人phoneNumber            String phoneNumber = null;            if (numCount > 0){                Cursor phoneCursor=contentResolver.query(                    ContactsContract.CommonDataKinds.Phone.CONTENT_URI,                    null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID+"=?",                    new String[]{Integer.toString(id)}, null);                if(phoneCursor.moveToFirst()){     //仅读取第一个电话号码                    phoneNumber = phoneCursor.getString(phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));                }                phoneCursor.close();            }            // 新建对象,进行封装处理            User user = new User(id,phoneNumber,name);            Log.d(TAG,"userInfo --> " + user.toString());        }        cursor.close();    }}

效果如下:

MessageCenterUI_KZ1BNrtK7F

5、使用“短信”内容提供者

目前,很多APP都会自动监听发送来的验证码,我们可以实现这样的效果嘛?

当然可以,而且还非常简单,只需要一个监听sms可以啦!

原理:

  • 记不记得在写自定义的内容提供者的时候,我们使用了contentResolver.registerContentObserver的方法?
  • 这个方法就是注册了一个内容观察者,如果我们将这个观察者来观察我们的短信,获取短信内容,再通过正则匹配获取,最后setText一下,是不是就解决了这个问题呢?
  • 原理非常简单,下面来写一写如何实现!

首先是布局文件xml

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:orientation="vertical"    android:layout_height="match_parent">    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:orientation="horizontal"        android:layout_marginTop="10dp">        <EditText            android:hint="请输入手机号"            android:inputType="number"            android:layout_weight="1"            android:layout_width="match_parent"            android:layout_height="50dp"            android:id="@+id/et_phoneNumber"/>        <Button            android:layout_width="110dp"            android:layout_height="50dp"            android:text="获取验证码"            android:id="@+id/bt_getVerificationCode"            android:onClick="getVerificationCode"/>    </LinearLayout>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:orientation="horizontal"        android:layout_marginTop="10dp">        <EditText            android:hint="请输入验证码"            android:inputType="number"            android:layout_weight="1"            android:layout_width="match_parent"            android:layout_height="50dp"            android:id="@+id/et_verificationCode"/>        <Button            android:layout_width="110dp"            android:layout_height="50dp"            android:text="提交验证码"            android:id="@+id/bt_submit"            android:onClick="getVerificationCode"/>    </LinearLayout></LinearLayout>

然后是最基本的动态获取权限,如果我们需要读取短信,首先必须得有一个READ_SMS的权限,仍然是通过动态申请获取,步骤还是老样子:

  • 现在manifest中声明:

    <uses-permission android:name="android.permission.READ_SMS"/>
  • 然后在activity的onCreate()方法中调用如下的动态申请代码:

    // 初始化权限public void initPermission() {    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {        String[] reqPermissions = new String[]{Manifest.permission.READ_SMS};        for (String reqPermission : reqPermissions) {            if (checkSelfPermission(reqPermission) != PackageManager.PERMISSION_GRANTED) {                // 如果有没有授权的,就去提醒授权                requestPermissions(reqPermissions,PERMISSION_REQUEST_CODE);                break;            }        }    }}
  • 当然,我们可以再使用权限申请的回调方法:

    // 内容观察者@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {    if (requestCode == PERMISSION_REQUEST_CODE) {        String res = "allPermissionsHaveBeenGiven";        for (int grantResult : grantResults) {            if (grantResult != PackageManager.PERMISSION_GRANTED) {                Log.d(TAG,"somePermissionsWereNotGranted");                res = "somePermissionsWereNotGranted";                finish();                break;            }        }        Log.d(TAG,"allPermissionsHaveBeenGiven...");        Toast.makeText(this, res, Toast.LENGTH_SHORT).show();    }}
  • 如此一来,我们的权限就get到了

然后就是注册一个内容观察者,来监听sms

  • 首先,我们需要写一个UriMatcher来匹配短信的Uri

    public static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);private static final int MATCH_CODE = 11;// Uri匹配器static {    uriMatcher.addURI("sms","inbox/#",MATCH_CODE);}
  • 之所以这么写,是因为发送来的短信Uri提示如下uri --> content://sms/inbox/20,所以我们可以来匹配inbox表中的所有数字,可以用#来完成

  • 然后我们可以写一个方法来注册内容观察者,然后在匹配接收短信的uri中,查询body字段,就可以得到短信内容

    // 内容观察者private void initContentObserver() {    ContentResolver contentResolver = getContentResolver();    // 匹配短信的内容    contentResolver.registerContentObserver(Uri.parse("content://sms/"), true, new ContentObserver(new Handler()) {        @SuppressLint("Range")        @Override        public void onChange(boolean selfChange, @Nullable Uri uri) {            int match = uriMatcher.match(uri);            Log.d(TAG, "uri --> " + uri + " match --> " + match);            if (match == MATCH_CODE) {                Cursor cursor = contentResolver.query(uri, null, null, null, null, null);                String body = null;                if (cursor.moveToNext()) {                    body = cursor.getString(cursor.getColumnIndex("body"));                }                cursor.close();                Log.d(TAG,"body --> " + body);                handleBody(body);            }        }    });}
  • 而handleBody()这个方法就是最简单的正则匹配一个短信的验证码:

    // 处理验证码匹配,并且自动填充private void handleBody(String body) {    if(body != null && body.startsWith("【picgoTest】")) {        // 截取4位数字        Pattern p = Pattern.compile("(?<![0-9])([0-9]{4})(?![0-9])");        Matcher matcher = p.matcher(body);        boolean contain = matcher.find();        if (contain) {            Log.d(TAG,"verifyCode -- > " + matcher.group());            verificationCodeEt.setText(matcher.group());        }    }}

那么到此,其实就差不多构建完了,完整代码如下:(有一个小细节就是验证码发送之后,按钮会进行倒计时)

public class FourthActivity extends Activity {    private static final String TAG = "FourthActivity";    private static final int PERMISSION_REQUEST_CODE = 1;    public static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);    private static final int MATCH_CODE = 11;    // Uri匹配器    static {        uriMatcher.addURI("sms","inbox/#",MATCH_CODE);    }    private Button verificationCodeBtn;    private EditText verificationCodeEt;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_fourth);        // 获取组件        initView();        // 获取权限        initPermission();        // 创建时候就注册一个Observer        initContentObserver();    }    // 初始化组件    private void initView() {        verificationCodeBtn = findViewById(R.id.bt_getVerificationCode);        verificationCodeEt = findViewById(R.id.et_verificationCode);    }    // 内容观察者    private void initContentObserver() {        ContentResolver contentResolver = getContentResolver();        // 匹配短信的内容        contentResolver.registerContentObserver(Uri.parse("content://sms/"), true, new ContentObserver(new Handler()) {            @SuppressLint("Range")            @Override            public void onChange(boolean selfChange, @Nullable Uri uri) {                int match = uriMatcher.match(uri);                Log.d(TAG, "uri --> " + uri + " match --> " + match);                if (match == MATCH_CODE) {                    Cursor cursor = contentResolver.query(uri, null, null, null, null, null);                    String body = null;                    if (cursor.moveToNext()) {                        body = cursor.getString(cursor.getColumnIndex("body"));                    }                    cursor.close();                    Log.d(TAG,"body --> " + body);                    handleBody(body);                }            }        });    }    // 处理验证码匹配,并且自动填充    private void handleBody(String body) {        if(body != null && body.startsWith("【picgoTest】")) {            // 截取4位数字            Pattern p = Pattern.compile("(?<![0-9])([0-9]{4})(?![0-9])");            Matcher matcher = p.matcher(body);            boolean contain = matcher.find();            if (contain) {                Log.d(TAG,"verifyCode -- > " + matcher.group());                verificationCodeEt.setText(matcher.group());            }        }    }    // 初始化权限    public void initPermission() {        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {            String[] reqPermissions = new String[]{Manifest.permission.READ_SMS};            for (String reqPermission : reqPermissions) {                if (checkSelfPermission(reqPermission) != PackageManager.PERMISSION_GRANTED) {                    // 如果有没有授权的,就去提醒授权                    requestPermissions(reqPermissions,PERMISSION_REQUEST_CODE);                    break;                }            }        }    }    @Override    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {        if (requestCode == PERMISSION_REQUEST_CODE) {            String res = "allPermissionsHaveBeenGiven";            for (int grantResult : grantResults) {                if (grantResult != PackageManager.PERMISSION_GRANTED) {                    Log.d(TAG,"somePermissionsWereNotGranted");                    res = "somePermissionsWereNotGranted";                    finish();                    break;                }            }            Log.d(TAG,"allPermissionsHaveBeenGiven...");            Toast.makeText(this, res, Toast.LENGTH_SHORT).show();        }    }    // 计时器(带有每秒调用和最终回调)    private final CountDownTimer countDownTimer = new CountDownTimer(60*1000,1000) {        @SuppressLint("SetTextI18n")        @Override        public void onTick(long millisUntilFinished) {            verificationCodeBtn.setText("重新获取("+millisUntilFinished/1000+")");            verificationCodeBtn.setEnabled(false);        }        @Override        public void onFinish() {            verificationCodeBtn.setText("获取验证码");            verificationCodeBtn.setEnabled(true);        }    };    // 点击获取验证码    public void getVerificationCode(View view) {        countDownTimer.start();    }}

最终效果如下:

MessageCenterUI_AT7CyHXIFy

6、使用“媒体库”内容提供者

首先我们需要知道Uri

  • 图片的Url
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
  • 视频的Url
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
  • 音频的
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI

有了Uri之后,我们就可以通过contentResolver.query去查询数据啦

但是在query之前,我们还得动态申请权限:

// 动态申请权限private void initPermission() {    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {        String[] permissions = new String[] {Manifest.permission.READ_EXTERNAL_STORAGE};        for (String permission : permissions) {            if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {                requestPermissions(permissions,REQUEST_PERMISSION_CODE);            }        }    }}// 权限申请回调方法@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {    if (requestCode == REQUEST_PERMISSION_CODE) {        String info = "授予权限成功!";        for (int res : grantResults) {            if (res != PackageManager.PERMISSION_GRANTED) {                info = "授予权限失败,退出程序!";                finish();                break;            }        }        Toast.makeText(this, info, Toast.LENGTH_SHORT).show();    }}

申请完了之后,我们去查询试试:

ContentResolver contentResolver = getContentResolver();Uri imageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;Cursor cursor = contentResolver.query(imageUri, null, null, null, null);String[] columnNames = cursor.getColumnNames();Log.d(TAG,"count --> " + cursor.getCount());while (cursor.moveToNext()) {    Log.d(TAG,"====================================");    for (String columnName : columnNames) {        String info = cursor.getString(cursor.getColumnIndex(columnName));        Log.d(TAG,columnName + " --> " + info);        Log.d(TAG,"====================================");    }}cursor.close();

我的图库中一共两张图片,部分logcat输出如下:

image-20220126112707138

重要的数据有:

  • _data,这个是存放图片的位置信息
  • _size,这个是图片的大小
  • _display_name,就是图片的名称

有了上面的使用内容提供者的前提知识,咱们可以实现从媒体库选择图片,效果如下:

qemu-system-x86_64_XuBI3s6H6P

需要的代码量很多,所以就不在这里细说了,主要实现的就是从媒体库中读取图片文件,然后通过数组形式返回路径,再在前台渲染选择到的图片。

posted @ 2022-01-26 19:06 woodwhale 阅读(1) 评论(0) 编辑 收藏 举报
回帖
    羽尘

    羽尘 (王者 段位)

    2335 积分 (2)粉丝 (11)源码

     

    温馨提示

    亦奇源码

    最新会员