【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学习章节中说过了
  • 在一个项目中,该有的架构还是有的,如下图
  • 其中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,"=================================");
}

点击测试:

然后是插入:

还是非常简单的:

/**
* 添加数据
* @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的时候就注册一个内容观察者,当我们内容提供者的数据发生改变的时候,就可以监听到,也就是,我们插入成功就可以监听到

@Override
protected 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,"用户数据发生变化");
}
});
}

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

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()当前页面

@Override
public 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的各种作用:

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方法,将我们的键值对写入其中

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

  • 写入规则

我们在写入成功之后,得到的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设置:

@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 测试是否插入成功

普通添加效果图如下:

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

可以发现,我们成功的将插入方法中的内容写入到了系统自带的“日历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();
} }

效果如下:

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;
    }
    }
    }
    }
  • 当然,我们可以再使用权限申请的回调方法:

    // 内容观察者
    @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();
    }
    }
  • 如此一来,我们的权限就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();
}
}

最终效果如下:

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);
}
}
}
} // 权限申请回调方法
@Override
public 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输出如下:

重要的数据有:

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

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

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

【Android】安卓四大组件之内容提供者的更多相关文章

  1. 【Android】安卓四大组件之Activity(二)

    [Android]安卓四大组件之Activity(二) 前言 在这篇文章之前,我已经写过了一篇有关Activity的内容,是关于activity之间的页面跳转和数据传递,而这篇文章着重强调的是Acti ...

  2. 【Android】安卓四大组件之Activity(一)

    [Android]安卓四大组件之Activity(一) 前言 Activity是Android学习中的一个重要组件,想要对其进行系统的了解可以分为几块内容,这一大章节的内容是有关于activity之间 ...

  3. Android的四大组件之Activity

    Android的四大组件之Activity Activity:是Android组件中最基本也是最为常见用的四大组件(Activity,Service服务,Content Provider内容提供者,B ...

  4. android中四大组件之间相互通信

    好久没有写有关android有关的博客了,今天主要来谈一谈android中四大组件.首先,接触android的人,都应该知道android中有四大组件,activity,service,broadca ...

  5. Android开发四大组件概述

    这个文章主要是讲Android开发的四大组件,本文主要分为 一.Activity具体解释 二.Service具体解释 三.Broadcast Receiver具体解释 四.Content Provid ...

  6. Android之四大组件、六大布局、五大存储 总结

    Android之四大组件.六大布局.五大存储 一.四大组件:Android四大组件分别为activity.service.content provider.broadcast receiver.   ...

  7. Android深入四大组件(八)广播的注册、发送和接收过程

    前言 我们接着来学习Android四大组件中的BroadcastReceiver,广播主要就是分为注册.接收和发送过程.建议阅读此文前请先阅读Android深入理解四大组件系列的文章,知识重复的部分, ...

  8. Android深入四大组件(七)Service的绑定过程

    前言 我们可以通过调用Context的startService来启动Service,也可以通过Context的bindService来绑定Service,建议阅读此篇文章前请阅读Android深入四大 ...

  9. Android 系统四大组件

    Android 系统四大组件分别是活动(Activity).服务(Service).广播接收器(Broadcast Receiver)和内容提供器(Content Provider). 活动是所有 A ...

随机推荐

  1. 【LeetCode】1110. Delete Nodes And Return Forest 解题报告 (C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 递归 日期 题目地址:https://leetcode ...

  2. Win8/Win10 Ctrl+Alt+方向键 屏幕显示翻转解决办法

    状况 Win10系统下,在Android Studio中使用快捷键 Ctrl + Alt + ↓ 复制代码段的时候神奇的情况出现了,屏幕显示倒转了,我也只能是一脸懵逼,Win7下没有这个问题.经验判断 ...

  3. Fence(poj1821)

    Fence Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 4705   Accepted: 1489 Description ...

  4. Augmentation For GAN

    目录 概 主要内容 Differentiable Augmentation Adaptive Augmentation 代码 Zhao S., Liu Z., Lin J., Zhu J. and H ...

  5. Deep Linear Networks with Arbitrary Loss: All Local Minima Are Global

    目录 问题 假设和重要结果 证明 注 Laurent T, Von Brecht J H. Deep linear networks with arbitrary loss: All local mi ...

  6. C++string字符串截取其中元素 截取定位字符串

    #include <iostream> #include <string> using namespace std; /** * 截取str后的元素 * @param stre ...

  7. Spring练习,使用Properties类型注入方式,注入MySQL数据库连接的基本信息,然后使用JDBC方式连接数据库,模拟执行业务代码后释放资源,最后在控制台输出打印结果。

    相关 知识 >>> 相关 练习 >>> 实现要求: 使用Properties类型注入方式,注入MySQL数据库连接的基本信息,然后使用JDBC方式连接数据库,模拟执 ...

  8. Pycharm的界面修改与基本设置

    Pycharm的一些基本设置 设置字体大小与字体样式 首先我们打开Pycharm,点击左上角File,找到sitting 点击后就能进入设置界面 xdm,我将介绍两种更改字体的设置 方法一 设置界面找 ...

  9. SpringCloud发现服务代码(EurekaClient,DiscoveryClient)

    1.说明 本文介绍SpringCloud发现服务代码的开发, 通过使用EurekaClient,DiscoveryClient来发现注册中心的服务等, 从而可以自定义客户端对注册中心的高级用法. 2. ...

  10. VMware客户端vSphere Web Client新建虚拟机

    1.说明 vSphere Web Client是为管理员提供的一款通用的. 基于浏览器的VMware管理工具, 能够监控并管理VMware基础设施. 由于需要登录的宿主机安装的是ESXi-6.5.0, ...