【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
,就是圖片的名稱
有了上面的使用內容提供者的前提知識,我們們可以實現從媒體庫選擇圖片,效果如下:
需要的程式碼量很多,所以就不在這裡細說了,主要實現的就是從媒體庫中讀取圖片檔案,然後通過陣列形式返回路徑,再在前臺渲染選擇到的圖片。