Android 使用前台服务 获取通话状态和来电号码

本文介绍了如何在Android应用中,通过创建前台服务并注册通话状态监听器,来实现在来电或通话时获取并处理来电号码的需求。在获取到权限后,启动前台服务并在服务中监听电话状态,展示对话框显示来电信息。

产品提出一个需求:在来电或通话时获取来电号码(因为内部使用的是虚拟号,需要调接口查询对方的身份)并展示相关信息

先上个效果图
解决方案:在前台服务中注册通话状态的监听,在响铃和通话时可以获取到手机号码,做完相关的逻辑处理后,在前台服务中使用对话框显示
在这里插入图片描述
声明权限
监听手机的状态肯定是危险权限的 ,需要我们配置清单中声明后再运行中申请

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /><!--前台服务权限-->

    <uses-permission android:name="android.permission.READ_PHONE_STATE" /><!--手机通话状态权限-->
    <uses-permission android:name="android.permission.READ_CALL_LOG" /><!--获取来电号码权限-->

    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /><!--悬浮窗权限,6.0之前就有-->
    <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" /><!--悬浮窗权限,6.0之后额外添加-->

在activity中申请权限,申请权限使用的是郭霖大大的PermissionX

dependencies {
	...
    //PermissionX
    implementation 'com.guolindev.permissionx:permissionx:1.5.1'
}
        if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
            PermissionX.init(this)
                .permissions(Manifest.permission.READ_CALL_LOG,Manifest.permission.READ_CALL_LOG,Manifest.permission.SYSTEM_ALERT_WINDOW)
                .onExplainRequestReason { scope, deniedList ->
                    //先获取正常的权限,获取完后,走这里获取特殊权限(如悬浮窗等,必须要去系统页面手动设置)
                    val message = "需要您开启以下权限才能正常使用"
                    scope.showRequestReasonDialog(deniedList, message, "去开启", "拒绝")
                }
                .request { allGranted, _, deniedList ->
                    if (allGranted) {
                        initEvent()
                    } else {
                        toast("您拒绝了如下权限:$deniedList")
                    }
                }
        }else{
            //6.0之前不需要动态申请权限
            initEvent()
        }

成功获取所有权限后我们就去启动前台服务

    private fun initEvent(){
    	//判断服务是否已启动
        if (!MyService.live){
            //8.0前后启动前台服务的方法不同
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                startForegroundService(Intent(this,MyService::class.java))
            } else {
                startService(Intent(this,MyService::class.java))
            }
        }
    }

准备一个前台服务MyService
前台服务还是继承Service即可,不像IntentService需要继承IntentService。一个普通的Service只要通过startForeground设置一个常驻在状态栏通知栏的通知,就是前台服务了,他在内存回收机制中有较低的优先级,更不容易被杀死(类似网易云这种音乐app)

private const val NOTIFICATION_ID=9

class MyService : Service() {
    companion object{
    	//保存服务的开启状态
        var live=false
    }
	//不需要与其他组件交互的话直接返回null即可
    override fun onBind(intent: Intent): IBinder? {
        return null
    }

    override fun onCreate() {
        super.onCreate()
        startForeground(NOTIFICATION_ID, createForegroundNotification())
    }

    override fun onDestroy() {
        super.onDestroy()
        live=false
        stopForeground(true)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        live=true
        return super.onStartCommand(intent, flags, startId)
    }

    /**
     * 创建服务通知
     */
    private fun createForegroundNotification(): Notification {
        val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager

        // 唯一的通知通道的id.
        val notificationChannelId = "notification_channel_id_01"

        // Android8.0以上的系统,新建消息通道
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            //用户可见的通道名称
            val channelName = "Foreground Service Notification"
            //通道的重要程度
            val importance = NotificationManager.IMPORTANCE_HIGH
            val notificationChannel =
                NotificationChannel(notificationChannelId, channelName, importance)
            notificationChannel.description = "Channel description"

            //LED灯
            notificationChannel.enableLights(true)
            notificationChannel.lightColor = Color.RED
            //震动
            notificationChannel.vibrationPattern = longArrayOf(0)
            notificationChannel.enableVibration(false)
            notificationManager?.createNotificationChannel(notificationChannel)
        }
        val builder = NotificationCompat.Builder(this, notificationChannelId)
        //通知标题
        builder.setContentTitle("工作台运行中")
        builder.setSmallIcon(R.mipmap.sym_def_app_icon)
        builder.setDefaults(DEFAULT_SOUND)
        builder.priority = NotificationCompat.PRIORITY_HIGH
        //通知内容
        //builder.setContentText("ContentText")
        //设定通知显示的时间
        builder.setWhen(System.currentTimeMillis())
        //创建通知并返回
        return builder.build()
    }

}

加入监听通话状态和来电号码功能

class MyService : Service() {
    private lateinit var telephonyManager: TelephonyManager
    private lateinit var mPhoneListener: PhoneStateListener
    //...
    override fun onCreate() {
        super.onCreate()
        initEvent()
        startForeground(NOTIFICATION_ID, createForegroundNotification())
    }    
   // ...
    private fun initEvent(){
        telephonyManager= getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        //在注册监听的时候就会走一次回调,后面通话状态改变时也会走
        //如下面的代码,在启动服务时如果手机没有通话相关动作,就会直接走一次TelephonyManager.CALL_STATE_IDLE
        mPhoneListener=object :PhoneStateListener(){
            override fun onCallStateChanged(state: Int, phoneNumber: String?) {
                super.onCallStateChanged(state, phoneNumber)
                when(state){
                    //挂断
                    TelephonyManager.CALL_STATE_IDLE->{
                        //toast("挂断${phoneNumber}")
                        Log.i(TAG, "onCallStateChanged: 挂断${phoneNumber}")
                        onCallFinish()
                    }
                    //接听
                    TelephonyManager.CALL_STATE_OFFHOOK->{
                        toast("接听${phoneNumber}")
                        Log.i(TAG, "onCallStateChanged: 接听${phoneNumber}")
                    }
                    //响铃
                    TelephonyManager.CALL_STATE_RINGING->{
                        toast("响铃${phoneNumber}")
                        Log.i(TAG, "onCallStateChanged: 响铃${phoneNumber}")
                        onCalling(phoneNumber)

                    }
                }
            }
        }
        telephonyManager.listen(mPhoneListener,PhoneStateListener.LISTEN_CALL_STATE)
    }
	//结束通话
    private fun onCallFinish(){

    }
	//被呼叫
    private fun onCalling(phoneNumber:String?){

    }
    

这样就可以获取到通话状态和来电号码了,现在加上dialog
service中直接像activity中一样使用dialog的话会抛出异常,需要在调用show方法前把对话框设置为系统对话框

    private fun onCalling(phoneNumber:String?){
        //创建对话框
        val dialog = AlertDialog.Builder(this)
            .setTitle("标题")
            .setMessage(phoneNumber)
            .create()
        //在服务中显示dialog必须设置为系统对话框再调用show方法
        dialog.window?.run {
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
                setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY)
            }else{
                setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)
            }
            dialog.show()
        }
    }

完成,现在运行后,来电时就是开头截图中的效果了

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值