Android簡訊傳送:考慮動態許可權和相容性問題
小結:剛開始覺得簡訊傳送很簡單啊,不就是這樣這樣,再那樣一下就好了嘛。但其實內容聽繁瑣的。坑不多,但需要判斷的東西挺多。
首先需要判斷有無SIM卡
TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
String simState = tm.getSubscriberId();
if (TextUtils.isEmpty(simState)) {
Toast.makeText (MobileVerifed.this, "未檢測到Sim卡", Toast.LENGTH_SHORT).show();
return;
}
其實這個getSubscriberId在後面也會用到。如果有卡的話,會獲得到運營商的IMSI
判斷是否雙卡【選擇哪一個卡去傳送簡訊】
List<Integer> idList=new ArrayList<Integer>();
SubscriptionManager subscriptionManager = SubscriptionManager.from (MobileVerifed.this);
List<SubscriptionInfo> infoList = subscriptionManager.getActiveSubscriptionInfoList();
for (SubscriptionInfo info : infoList) {
idList.add(info.getSubscriptionId());
}
SmsManager.getSmsManagerForSubscriptionId(idList.get (0))
.sendTextMessage("目的電話號碼",null,"簡訊訊息",null,null);
SmsManager.getSmsManagerForSubscriptionId(idList.get(1))
.sendTextMessage("目的電話號碼",null,"簡訊訊息",null,null);
我的手機是雙卡,可以看到infoList 值為:
idList值為:
其中如果選擇一個變數區分兩個手機卡,應選擇,iccid。每個sim卡都有自己的iccid。
ps. SubscriptionManager 是谷歌官方5.0以上正式支援雙卡雙待,所以會有相關類。以前是各大廠商自己加的。如果試圖相容5.0以下必須用反射,而且必須考慮運營商的不同。
判斷是否開通了傳送簡訊許可權【詢問、允許】
這兒有點複雜,和相容性相關
有人會問,檢測這個許可權幹什麼呀。檢測許可權的主要目的是為了告訴使用者去開通,或者提示使用者不是軟體卡了,是你沒開通。
6.0以下,一般在初始的時候就會把許可權就要到,所以不需要檢視是否開通了傳送簡訊許可權。但如果查詢的話,也有一個相容方法 ContextCompat.checkSelfPermission()
但6.0以下也存在一些系統,比如我試的這臺魅藍2,使用的是YunOS系統,盜版的Android系統,haha,可以使用動態許可權。但使用者在設定中禁止後,上面檢測的方法檢測不到許可權是否開通。也就是使用者點選了之後,就沒反應了,體驗不好。但是目前沒有找到方法。
6.0以上,不同團隊的targetSdkVersion不同,還分為兩種情況
首先是targetSdkVersion<23時,如果使用checkSelfPermission()方法,累死也檢測不出來。即檢測結果都是PERMISSION_GRANTED。
targetSdkVersion指的是編譯時期的版本,按照我的理解:APK 在呼叫系統 的時候,系統首先會查一下呼叫的 APK 的 targetSdkVersion 資訊,如果小於 23,就還是按照老的行為,否者執行新的行為。
解決辦法:使用PermissionChecker.checkSelfPermission()
6.0以上,且targetSdkVersion>=23,就一切好說啦,直接使用checkSelfPermission()就行。
檢測targetSdkVersion 版本值
info = MobileVerifed.this.getPackageManager().getPackageInfo(
MobileVerifed.this.getPackageName(), 0);
int targetSdkVersion = info.applicationInfo.targetSdkVersion;
許可權不再提醒
許可權檢測的返回值有兩種錯誤。
一種是PERMISSION_DENIED,出現在動態授權時 ,“拒絕一次”
一種是SIGNATURE_SECOND_NOT_SIGNED,出現在 設定中直接禁止傳送簡訊,或者拒絕是選上不再提醒
那麼怎麼檢測第二種呢?
ActivityCompat.shouldShowRequestPermissionRationale()
返回值為false時,代表使用者設定中禁止傳送簡訊,或者不再提醒
發簡訊
這個就直接上程式碼啦
IntentFilter mFilter01;
mFilter01 = new IntentFilter(SENT_SMS_ACTION);
sendReceiver = new SmsSendReceiver();
registerReceiver(sendReceiver, mFilter01);
mFilter01 = new IntentFilter(DELIVERED_SMS_ACTION);
receiveReceiver = new SmsSendReceiver();
registerReceiver(receiveReceiver, mFilter01);
以上是註冊廣播時的程式碼,主要是兩種,【簡訊已傳送】會傳一個廣播,而接收方接收到簡訊,會回傳給運營商一個訊號,這時候會收到【簡訊已接收】的廣播。
主要,這些廣播註冊一定要在onCreate()裡,否則會出現傳送簡訊一次多次廣播回撥的現象。就是,點選第一次,廣播回撥兩次【傳送,接收】;點選兩次,廣播回撥四冊… ….
註冊了廣播,就別忘了解綁啊
if (sendReceiver != null && receiveReceiver != null) {
unregisterReceiver(sendReceiver);
unregisterReceiver(receiveReceiver);
}
傳送簡訊:
Intent sendIntent = new Intent(SENT_SMS_ACTION);
sendIntent.putExtra("key", "sms_send");
PendingIntent sendPI = PendingIntent.getBroadcast(MobileVerifed.this, (int) System.currentTimeMillis(),
sendIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Intent receiveIntent = new Intent(DELIVERED_SMS_ACTION);
receiveIntent.putExtra("key", "sms_receive");
PendingIntent deliverPI = PendingIntent.getBroadcast(MobileVerifed.this, (int) System.currentTimeMillis(),
receiveIntent, PendingIntent.FLAG_UPDATE_CURRENT);
String telNum = "****";//目的電話號碼
SmsManager manager = SmsManager.getDefault();
ArrayList<String> smsList = manager.divideMessage("傳送簡訊test");
for (String s : smsList) {
manager.sendTextMessage(telNum, null, s, sendPI, deliverPI);
}
以上PendingIntent的第二個和第四個引數要當心哦
接收廣播:
@Override
public void onReceive(Context _context, Intent _intent) {
String key = _intent.getStringExtra("key");
if (key.equals("sms_send") || key.equals("sms_receive")) {
String flag = key.equals("sms_send") ? "簡訊傳送" : "簡訊接收";
switch (getResultCode()) {
case Activity.RESULT_OK:
Toast.makeText(MobileVerifed.this,
flag + "成功", Toast.LENGTH_SHORT)
.show();
// todo 請求後臺
break;
case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
case SmsManager.RESULT_ERROR_RADIO_OFF:
case SmsManager.RESULT_ERROR_NULL_PDU:
default:
Toast.makeText(_context, flag + "失敗", Toast.LENGTH_SHORT).show();
break;
}
}
}
沒話費
沒話費…,會走廣播,返回值是 SmsManager.RESULT_ERROR_GENERIC_FAILURE
跳轉到許可權設定頁面
Intent localIntent = new Intent();
localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= 9) {
localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
localIntent.setData(Uri.fromParts("package", getPackageName(), null));
} else if (Build.VERSION.SDK_INT <= 8) {
localIntent.setAction(Intent.ACTION_VIEW);
localIntent.setClassName("com.android.settings","com.android.settings.InstalledAppDetails");
localIntent.putExtra("com.android.settings.ApplicationPkgName", getPackageName());
}
startActivity(localIntent);
這個巨坑啊。因為要跳轉到不同廠商的應用許可權,我們這兒小米手機測試的時候,出現了以上方法,在8.0以上的手機許可權頁面,處於只讀狀態,而非可讀可寫。然後就是更改許可權不成功。
然後就去網上查資料,發現不同MIUI系統,他們許可權頁面甚至包名不同,甚至activity命名都不同 QAQ
後來專門針對小米,進行了許可權頁面跳轉的修改。
首先判斷是MIUI系統,其次判斷是MIUI5/6/7/8
MIUI5使用一般的跳轉方法【如上面的】即可。MIUI6和MIUI7跳轉方式一樣。MIUI8跳轉也得專門挑出來
程式碼如下:
private void jumpPermissionActivity() {
if (Build.MANUFACTURER.equals("Xiaomi")) {
String miuiVersion = Util.getMiuiVersion();
String permissionDetailActivityNameV6 = "com.miui.permcenter.permissions.AppPermissionsEditorActivity";
String permissionDetailActivityNameV8 = "com.miui.permcenter.permissions.PermissionsEditorActivity";
if (!TextUtils.isEmpty(miuiVersion)) {
switch (miuiVersion) {
case "V5":
jumpOriginalPermissionActivity();
break;
case "V6":
case "V7":
jumpXimiPermissionActivity(permissionDetailActivityNameV6);
break;
case "V8":
jumpXimiPermissionActivity(permissionDetailActivityNameV8);
break;
default:
jumpOriginalPermissionActivity();
break;
}
} else {
Toast.makeText(DzhApplication.getContext(), "應用許可權頁跳轉失敗,請手動在設定中進行更改", Toast.LENGTH_SHORT).show();
}
} else {
jumpOriginalPermissionActivity();
}
}
/**
* 根據MIUI系統,判斷需要跳轉的頁面
*
* @param permissionDetailActivityName
*/
private void jumpXimiPermissionActivity(String permissionDetailActivityName) {
try {
Intent i = new Intent("miui.intent.action.APP_PERM_EDITOR");
ComponentName componentName = new ComponentName("com.miui.securitycenter", permissionDetailActivityName);
i.setComponent(componentName);
i.putExtra("extra_pkgname", getPackageName());
startActivity(i);
} catch (Exception e) {
jumpOriginalPermissionActivity();
}
}
/**
* 普通Android手機通用的許可權設定頁面跳轉
*/
private void jumpOriginalPermissionActivity() {
Intent localIntent = new Intent();
localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= 9) {
localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
localIntent.setData(Uri.fromParts("package", getPackageName(), null));
} else if (Build.VERSION.SDK_INT <= 8) {
localIntent.setAction(Intent.ACTION_VIEW);
localIntent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails");
localIntent.putExtra("com.android.settings.ApplicationPkgName", getPackageName());
}
startActivity(localIntent);
}
pssssssssss. AS的Gradle外掛預設會啟用Manifest Merger Tool,若Library專案中也定義了與主專案相同的屬性(例如預設生成的Android:icon和android:theme),則此時會合並失敗,並報上面的錯誤。
在Manifest.xml的application標籤下新增tools:replace=”android:icon, android:theme”(多個屬性用,隔開,並且記住在manifest根標籤上加入xmlns:tools=”http://schemas.android.com/tools”,否則會找不到namespace哦)
————————— the end —————————