IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 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()
        }
    }

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

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章           查看所有文章
加:2021-09-26 10:17:52  更:2021-09-26 10:20:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 20:07:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码