【Android】安卓四大组件之内容提供者
【Android】安卓四大组件之内容提供者
1、关于内容提供者
1.1 什么是内容提供者
内容提供者就是contentProvider
,作用有如下:
- 给多个应用提供数据
- 类似一个接口
- 可以和多个应用分享数据
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.picgo
是authorities
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】安卓四大组件之内容提供者的更多相关文章
- 【Android】安卓四大组件之Activity(二)
[Android]安卓四大组件之Activity(二) 前言 在这篇文章之前,我已经写过了一篇有关Activity的内容,是关于activity之间的页面跳转和数据传递,而这篇文章着重强调的是Acti ...
- 【Android】安卓四大组件之Activity(一)
[Android]安卓四大组件之Activity(一) 前言 Activity是Android学习中的一个重要组件,想要对其进行系统的了解可以分为几块内容,这一大章节的内容是有关于activity之间 ...
- Android的四大组件之Activity
Android的四大组件之Activity Activity:是Android组件中最基本也是最为常见用的四大组件(Activity,Service服务,Content Provider内容提供者,B ...
- android中四大组件之间相互通信
好久没有写有关android有关的博客了,今天主要来谈一谈android中四大组件.首先,接触android的人,都应该知道android中有四大组件,activity,service,broadca ...
- Android开发四大组件概述
这个文章主要是讲Android开发的四大组件,本文主要分为 一.Activity具体解释 二.Service具体解释 三.Broadcast Receiver具体解释 四.Content Provid ...
- Android之四大组件、六大布局、五大存储 总结
Android之四大组件.六大布局.五大存储 一.四大组件:Android四大组件分别为activity.service.content provider.broadcast receiver. ...
- Android深入四大组件(八)广播的注册、发送和接收过程
前言 我们接着来学习Android四大组件中的BroadcastReceiver,广播主要就是分为注册.接收和发送过程.建议阅读此文前请先阅读Android深入理解四大组件系列的文章,知识重复的部分, ...
- Android深入四大组件(七)Service的绑定过程
前言 我们可以通过调用Context的startService来启动Service,也可以通过Context的bindService来绑定Service,建议阅读此篇文章前请阅读Android深入四大 ...
- Android 系统四大组件
Android 系统四大组件分别是活动(Activity).服务(Service).广播接收器(Broadcast Receiver)和内容提供器(Content Provider). 活动是所有 A ...
随机推荐
- ffmpeg、数字音频教程、JUCE、std::tuple
下载网易云音乐的视频 https://yunyinyue.iiilab.com/ 模板教程: https://github.com/wuye9036/CppTemplateTutorial/blob/ ...
- 【LeetCode】286. Walls and Gates 解题报告 (C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 BFS 日期 题目地址:https://leetcod ...
- 【九度OJ】题目1018:统计同成绩学生人数 解题报告
[九度OJ]题目1018:统计同成绩学生人数 解题报告 标签(空格分隔): 九度OJ [LeetCode] http://ac.jobdu.com/problem.php?pid=1018 题目描述: ...
- 【LeetCode】641. Design Circular Deque 解题报告(Python & C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址:https://leetcode.com/problems/design-ci ...
- 【LeetCode】849. Maximize Distance to Closest Person 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...
- CS5266代替AG9311|Type C转HDMI带PD3.0转换芯片|AG9311替代方案
ALGOLTEK AG9311是一款带PD3.0 Type C转HDMI的转换芯片,它主要用于usb Type-c拓展坞以及多功能usb Type-c转换器等产品设计当中,台湾瑞奇达新推出的CS526 ...
- CS5216 设计于DP转HDMI转换器|DP转HDMI 1080P中继器 电平转化器开关设计方案与线路图
CS5216是一款Displayport to hdmi 1080p音视频信号转换芯片,主要用于设计与开发DP转HDMI 转换器.中继器.电平转换器等产品当中.它支持交流和直流耦合TMDS信号高达1. ...
- 编写Java程序_连锁超市购物结算系统
目录 功能需求: 一.Use Case 1 显示商品信息列表: 二.Use Case 2 输入购买商品编号 三.Use Case 3 显示购物结算清单 需求分级: 实现代码: 功能需求: Soft f ...
- 从零开始学springboot-1.创建项目
新建一个项目 添加依赖 点击完成,项目结构如下 手动添加以下依赖 打开pom.xml文件,手动添加以下依赖,用于自动生成代码 <dependency> <groupId>com ...
- RabbitMQ基础教程系列
Ubuntu16.04下,erlang安装和rabbitmq安装步骤 Ubuntu16.04下,rabbimq集群搭建 C# .net 环境下使用rabbitmq消息队列 .net core使用rab ...