内容提供者就是contentProvider,作用有如下:
作为一个APP,自己的数据会在某些条件下提供给其他APP,但是,APP的数据是私有的。
例如,APP A的数据库内容是不可以被APP B进行读取的
这个时候,我们就需要一个内容提供者,将APP A中的数据信息提供给APP B。
就贴近生活一些吧,拿某宝、拼夕夕等购物软件举例子,下面几种场景你肯定见过:
联系人,申请好友。搜索记录,大数据计算,进行产品推送。预约信息写入手机备忘录首先,在我们提供内容的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,得有初始化后的数据库

dao是专门执行数据库操作的,有相关接口和实现类db文件夹是存放数据库helper的,它的作用就是初始化数据库,并且可以返回数据操作对象pojo就是从数据中转为对象的类provider就是我们要写的内容提供者的类utils中就是常用的工具类提完了数据库,我们继续说内容提供者
一个具有内容提供者的APP中必须得有如下的类:
ContentProvider,并且重写其中的方法(增删改查)UriMatcher对象的成员变量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; }}我们写其他的一个APP来调用上述的内容:
首先是查询:
top.woodwhale.picgo是authoritiesuser是表名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的时候就注册一个内容观察者,当我们内容提供者的数据发生改变的时候,就可以监听到,也就是,我们插入成功就可以监听到
@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,"用户数据发生变化"); } });}注册成功之后,我们调用插入方法,可以发现已经被监听了,并且我们查询数据库,确实添加了如上的信息

其实使用内容提供者非常的简单和便捷,但是又有多少APP敢将自身的数据提供给他人呢?
所以自己写的APP中的内容提供者少之又少,基本上都是同一厂商敢和自家APP联动剽取用户的信息共享。
但是在安卓手机中,有很多自带的APP,他们都具有内容提供者的对应接口,用来让常用的APP进行内容的增删改查,下面我们来进行常见内容提供者的学习!
在很多的情况下,我们会将一些事情写入到我们手机中的“日历”中,当到了预定的时间就会提醒,那么设置一个日历提醒事件怎么做到呢?——我们可以使用安卓开发给定的CalendarContract进行完成
CalendarContract是日历内容提供者和APP之间的一个合同,当我们的APP获取了读、写日历的权限之后,就可以对手机自带的这个"日历APP"进行添加事件的操作,我们通过下面的代码来认识一下!
在安卓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(); }}下面我们正式开始,在开始之前,我们来看看有关日历的class的各种作用:

在获取完权限之后,我们可以获取一个日历用户的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;}最后一步就是写入日历内容
我们通过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);}在上面完成了之后,我们发现其实并没有开启提示模式,也就是说,到了当前并没有闹钟或者信息的通知
我们可以通过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();}普通添加效果图如下:

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

可以发现,我们成功的将插入方法中的内容写入到了系统自带的“日历APP”中
某宝、某夕夕,经常会申请通讯录权限,然后帮你自动加好友。
有没有思考过一个问题,那就是,他们是如何读取你的通讯录的?
其实这个问题非常的简单,使用“通讯录”内容提供者就完全可以做到,只需要用户提供通讯录的权限即可。
使用的方法和“日历”非常类似,步骤都是一样的,看看源码或者市面上的教程都可以了解噢
我这里就直接放我写的一个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; }}然后就是常规操作了:
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(); }}效果如下:

目前,很多APP都会自动监听发送来的验证码,我们可以实现这样的效果嘛?
当然可以,而且还非常简单,只需要一个监听sms可以啦!
原理:
contentResolver.registerContentObserver的方法?首先是布局文件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(); }}最终效果如下:

首先我们需要知道Uri
MediaStore.Images.Media.EXTERNAL_CONTENT_URIMediaStore.Video.Media.EXTERNAL_CONTENT_URIMediaStore.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输出如下:

重要的数据有:
_data,这个是存放图片的位置信息_size,这个是图片的大小_display_name,就是图片的名称有了上面的使用内容提供者的前提知识,咱们可以实现从媒体库选择图片,效果如下:

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