来电识别
公司办公app,有自己的通讯录。有时其他人打电话过来,手机中没有存此联系人的联系方式,又怕接了之后是骚扰电话。于是,想增加一个本公司的来电标记,也就是来电识别功能。
当本公司的同事打进电话时,手机上会弹出来电识别页面,显示头像,姓名,部门信息。可以一目了然,减少接听骚扰电话的频次。同时,这个也成为本公司的一张明信片,只有本公司的同事电话(手机,座机)才会触发来电识别功能。
文章目录
- 来电识别
- 难点
- 一、原理分析
- 二、效果样式
- 三、代码实现
难点
众所周知,android系统随着版本的升级,对三方app的限制越来越厉害,app进入后台之后很快就会被杀掉,网络请求也会被冻结。所以,当你的app被杀掉的时候,后台执行的任务是没办法完成的。来电识别功能需要app存活在后台并且网络请求不被冻结才能完成。因此进程保活是一个需要考虑的难点。
一、原理分析
手机来电时,获取来电号码,传到办公后台进行查询,查到返回头像,姓名,部门信息,弹出悬浮窗覆盖到来电界面进行展示;没有查询到就不展示。 当接通电话的时候消掉悬浮窗即可。
二、效果样式
设计图插曲: 这个卡片的样式经过很多次讨论,位置,背景色,什么情况下展现等等,其实大家最后都没达成一致,因为考虑的场景太多了,没有一个能完全满足所有场景,最后只能妥协折中。
三、代码实现
注册广播接收器,处理广播逻辑,网上大部分都是用的telephoneManager.Listen(phoneStateListener) 方式,经测试部分手机双卡的情况下会出现回调错乱,系统处理逻辑有bug,所以本文没有采用系统接口。而是监听广播自行处理。
部分主要代码如下:
@Override
public void onReceive(Context context, Intent intent) {
mcontext = context;
if (hanler == null) {
hanler = new Handler();
}
String action = intent.getAction();
inflate = LayoutInflater.from(context);
if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
Log.e("TAG", "拨出");
} else {
Log.e("TAG", "来电 ");
// 获取当前电话状态
String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
String number = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
Log.d("TAG", " onReceive state: " + state + " number==" + number);
if ("RINGING".equals(state)) {
Log.e("TAG", "来电 RINGING ");
String incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
if(!TextUtils.isEmpty(number)){
Log.e("TAG", "来电 RINGING incomingNumber=="+incomingNumber);
queryNumber(incomingNumber);
}
}else if ("OFFHOOK".equals(state)) {
try{
Log.d("TAG", "来电 OFFHOOK ");
if (wm != null && phoneView != null && phoneView.getParent() != null) { //目前策略 接通消掉
if (animator != null) {
animator.cancel();
}
wm.removeViewImmediate(phoneView);
hanler.removeCallbacks(runnable);
}
}catch (Exception e){
e.printStackTrace();
}
}else if ("IDLE".equals(state)) {
try {
Log.d("TAG", "来电 IDLE ");
if (wm != null && phoneView != null && phoneView.getParent() != null) {
if (animator != null) {
animator.cancel();
}
wm.removeViewImmediate(phoneView);
hanler.removeCallbacks(runnable);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
仅仅采用receiver方式的情况下,即使应用在后台可能没有被杀,但是网络请求可能会被冻结,导致无法查询号码,当你再次打开你的请用的时候,查询请求才会继续,结果才返回来,导致悬浮窗盖到应用上面,造成bug.
为了尽量避免这种情况,采用保活方式,并且在ListenInCallService 中进行广播注册。
@Override
public void onCreate() {
super.onCreate();
if (mScreenOnOffReceiver == null) {
mScreenOnOffReceiver = new ScreenOnOffReceiver();
}
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.intent.action.SCREEN_OFF");
intentFilter.addAction("android.intent.action.SCREEN_ON");
intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
registerReceiver(mScreenOnOffReceiver, intentFilter);
if (phoneReceiver == null) {
phoneReceiver = new PhoneReceiver();
}
IntentFilter intentFilter1 = new IntentFilter();
intentFilter1.addAction("android.intent.action.PHONE_STATE");
intentFilter1.addAction("android.intent.action.PHONE_STATE_2");
intentFilter1.addAction("android.intent.action.NEW_OUTGOING_CALL");
intentFilter1.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
registerReceiver(phoneReceiver, intentFilter1);
......
}
最后结合保活方案,尽可能地使应用一直存活,ListenInCallService 存活,并且提升进程优先级,防止网络请求被冻结。最后,别忘了各种权限。