近期有个bug是mtk平台的手机,发送短信每次都会发送两条,两条是同样的内容。
看了下发送是只有一次的,是写数据库写了两次,先整理下短信数据库发送时候是咋写入的:
发送短信数据库写入
短信应用
短信应用中SmsMessageSender
private boolean queueMessage(long token) throws MmsException {
...
Sms.addMessageToUri(mSubId,
mContext.getContentResolver(),
Uri.parse("content://sms/queued"), mDests[i],
mMessageText, null, mTimestamp,
true /* read */,
requestDeliveryReport,
mThreadId);
...
}
发送前都会写入数据库
SMSDispatcher
frameworks/opt/telephony/src/java/com/android/internal/telephony/SMSDispatcher.java
private Uri persistSentMessageIfRequired(Context context, int messageType, int errorCode) {
if (!mIsText || !mPersistMessage ||
!SmsApplication.shouldWriteMessageForPackage(mAppInfo.packageName, context) ||
isFilterOutByPpl(context, mDestAddress, mFullMessageText)) {
return null;
}
...
try {
final Uri uri = resolver.insert(Telephony.Sms.Sent.CONTENT_URI, values);
...
}
该方法中也会插入数据库,打印日志看该方法也走了,所以会导致数据库中有两条记录。看下它被哪个方法调用:
private void persistOrUpdateMessage(Context context, int messageType, int errorCode) {
if (mMessageUri != null) {
updateMessageState(context, messageType, errorCode);
} else {
mMessageUri = persistSentMessageIfRequired(context, messageType, errorCode);
}
}
这里可以看出mMessageUri不为null的时候才会走persistSentMessageIfRequired,看到这里可能会有奇怪的地方,mMessageUri应该不为空啊,这个值应该是短信引用中传递进来呀。
private SmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
PendingIntent deliveryIntent, PackageInfo appInfo, String destAddr, String format,
AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
SmsHeader smsHeader, boolean isExpectMore, String fullMessageText, int subId,
boolean isText, boolean persistMessage) {
...
mMessageUri = messageUri;
...
}
SmsTracker构造方法中的确也会初始化mMessageUri, 再看SmsTracker初始化代码:
protected SmsTracker getSmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
PendingIntent deliveryIntent, String format, AtomicInteger unsentPartCount,
AtomicBoolean anyPartFailed, Uri messageUri, SmsHeader smsHeader,
boolean isExpectMore, String fullMessageText, boolean isText, boolean persistMessage) {
...
return new SmsTracker(data, sentIntent, deliveryIntent, appInfo, destAddr, format,
unsentPartCount, anyPartFailed, messageUri, smsHeader, isExpectMore,
fullMessageText, getSubId(), isText, persistMessage);
}
再往上回追溯到CdmaSMSDispatcher或者GsmSMSDispatcher,这里拿CdmaSMSDispatcher举例
frameworks/opt/telephony/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
protected void sendTextWithEncodingType(String destAddr, String scAddr, String text,
int encodingType, PendingIntent sentIntent, PendingIntent deliveryIntent,
Uri messageUri, String callingPkg, boolean persistMessage) {
...
SmsTracker tracker = getSmsTracker(map, sentIntent, deliveryIntent, getFormat(),
messageUri, false /*isExpectMore*/, text, true /*isText*/,
true);
...
}
继续往上
frameworks/opt/telephony/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
public void sendTextWithEncodingType(String callingPackage, String destAddr, String scAddr,
String text, int encodingType, PendingIntent sentIntent, PendingIntent deliveryIntent,
boolean persistMessage) {
mDispatcher.sendTextWithEncodingType(destAddr, scAddr, text, encodingType, sentIntent,
deliveryIntent, null/*messageUri*/, callingPackage, persistMessage);
}
追究到这里终于发现messageUri为null,那么意味着SMSDispatcher中的persistSentMessageIfRequired是必走的。
再来回顾其中代码:
if (!mIsText || !mPersistMessage ||
!SmsApplication.shouldWriteMessageForPackage(mAppInfo.packageName, context) ||
isFilterOutByPpl(context, mDestAddress, mFullMessageText)) {
return null;
}
四个条件:
1.是短信文本
2.需要存储短信
3.调用shouldWriteMessageForPackage方法判断是否要写数据库
4.还是判断是否要写数据库,这个是剔除某些运营商自注册的短信
其中1,2,4条件没啥异常的地方,出异常的地方就是第三个条件。
public static boolean shouldWriteMessageForPackage(String packageName, Context context) {
if (SmsManager.getDefault().getAutoPersisting()) {
return true;
}
return !isDefaultSmsApplication(context, packageName);
}
其中isDefaultSmsApplication就是和字面意思一样判断是否为默认短信应用,这个值返回为true才对,然后shouldWriteMessageForPackage返回false,最后会导致persistSentMessageIfRequired直接return。这是因为默认短信应用在app层中已经写入了短信,framework代码中无需再次写入。
加入打印日志后发现isDefaultSmsApplication居然返回了false!!!
真正的原因
isDefaultSmsApplication方法就是通过包名判断,打印包名发现居然是com.mediatek.omacp
搜索下这个应用是在vendor/mediatek/proprietary/packages/apps/Omacp
Omacp全称是OMA_Client_Provisioning ,详细见
点击打开链接,通过发送短信配置手机的一些设置项目,主要是apn项目。
这个应用只可能是收短信,不会发送短信,而且ps查看手机进程中根本无此进程。
protected SmsTracker getSmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
PendingIntent deliveryIntent, String format, AtomicInteger unsentPartCount,
AtomicBoolean anyPartFailed, Uri messageUri, SmsHeader smsHeader,
boolean isExpectMore, String fullMessageText, boolean isText, boolean persistMessage) {
// Get calling app package name via UID from Binder call
PackageManager pm = mContext.getPackageManager();
String[] packageNames = pm.getPackagesForUid(Binder.getCallingUid()); //首先依据uid获取包名数组
// Get package info via packagemanager
PackageInfo appInfo = null;
if (packageNames != null && packageNames.length > 0) {
try {
String packageName = getPackageNameViaProcessId(packageNames); //再依据pid获取具体的包名
if (packageName != null) {
packageNames[0] = packageName;
}
Rlog.d(TAG, "SmsTrackerFactory and get the package name via process id: " +
packageNames[0]);
appInfo = pm.getPackageInfo(packageNames[0], PackageManager.GET_SIGNATURES);
} catch (PackageManager.NameNotFoundException e) {
// error will be logged in sendRawPdu
}
}
String destAddr = PhoneNumberUtils.extractNetworkPortion((String) data.get("destAddr"));
return new SmsTracker(data, sentIntent, deliveryIntent, appInfo, destAddr, format,
unsentPartCount, anyPartFailed, messageUri, smsHeader, isExpectMore,
fullMessageText, getSubId(), isText, persistMessage);
}
Mms和omacp的uid是一样的:
android:sharedUserId="android.uid.mms"
那么问题就出在getPackageNameViaProcessId方法上
private String getPackageNameViaProcessId(String[] packageNames) {
String packageName = null;
if (packageNames.length == 1) {
packageName = packageNames[0];
} else if (packageNames.length > 1) {
int callingPid = Binder.getCallingPid();
Iterator index = null;
ActivityManager am = (ActivityManager) mContext.getSystemService(
Context.ACTIVITY_SERVICE);
List processList = am.getRunningAppProcesses(); //获取全部运行的进程
if (processList != null)
{
index = processList.iterator();
while (index.hasNext()) {
ActivityManager.RunningAppProcessInfo processInfo =
(ActivityManager.RunningAppProcessInfo) (index.next());
if (callingPid == processInfo.pid) { //判断pid来获取包名
packageName = processInfo.processName;
break;
}
}
}
}
return packageName;
}
逻辑很简单,不过ActivityManager的getRunningAppProcesses方法是在apiLevel 21之后就只能获取本进程的相关信息了,
API21开始getRunningAppProcesses只返回应用本身的进程信息。不过这个描述有点问题,其实不是本进程,而是和本进程同样uid的应用,同uid的应用可以是其它进程。
getPackageNameViaProcessId已经是在com.android.phone进程中,这样返回的全是是uid为phone的几个进程,当然获取不到短信进程,这样getPackageNameViaProcessId会返回null,导致getSmsTracker获取的包名是拥有短信uid的第一个程序包名,不巧的是第一个恰好是omacp。
这样导致短信发一次会插两次数据库。
解决方法
不过对手机开发厂商来说,可以有更简单的方法
在phone进程应用packages/services/Telephony中加入如下权限即可:
<uses-permission android:name="android.permission.REAL_GET_TASKS" />