来电识别

公司办公app,有自己的通讯录。有时其他人打电话过来,手机中没有存此联系人的联系方式,又怕接了之后是骚扰电话。于是,想增加一个本公司的来电标记,也就是来电识别功能。
当本公司的同事打进电话时,手机上会弹出来电识别页面,显示头像,姓名,部门信息。可以一目了然,减少接听骚扰电话的频次。同时,这个也成为本公司的一张明信片,只有本公司的同事电话(手机,座机)才会触发来电识别功能。



文章目录

  • 来电识别
  • 难点
  • 一、原理分析
  • 二、效果样式
  • 三、代码实现



难点

众所周知,android系统随着版本的升级,对三方app的限制越来越厉害,app进入后台之后很快就会被杀掉,网络请求也会被冻结。所以,当你的app被杀掉的时候,后台执行的任务是没办法完成的。来电识别功能需要app存活在后台并且网络请求不被冻结才能完成。因此进程保活是一个需要考虑的难点。


一、原理分析

手机来电时,获取来电号码,传到办公后台进行查询,查到返回头像,姓名,部门信息,弹出悬浮窗覆盖到来电界面进行展示;没有查询到就不展示。 当接通电话的时候消掉悬浮窗即可。

二、效果样式

android 来电界面判断 安卓手机来电识别设置_移动开发

设计图插曲: 这个卡片的样式经过很多次讨论,位置,背景色,什么情况下展现等等,其实大家最后都没达成一致,因为考虑的场景太多了,没有一个能完全满足所有场景,最后只能妥协折中。

三、代码实现

注册广播接收器,处理广播逻辑,网上大部分都是用的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 存活,并且提升进程优先级,防止网络请求被冻结。最后,别忘了各种权限。