我在我的应用中使用以下代码进行通话记录,但是一旦“'服务”出现问题,它就会崩溃。被称为!
BroadcastReceiver
用于检测呼叫,并启动AudioService
是:
class PhoneStateReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val state = intent.getStringExtra(TelephonyManager.EXTRA_STATE)
val incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER)
when(state){
TelephonyManager.EXTRA_STATE_RINGING -> {
Toast.makeText(context, "Ringing $incomingNumber", Toast.LENGTH_LONG).show()
}
TelephonyManager.EXTRA_STATE_OFFHOOK -> {
Toast.makeText(context, "On Call $incomingNumber", Toast.LENGTH_LONG).show()
context.startService(Intent(context, AudioService::class.java))
}
TelephonyManager.EXTRA_STATE_IDLE -> {
Toast.makeText(context, "IDLE", Toast.LENGTH_LONG).show()
context.stopService(Intent(context, AudioService::class.java))
}
}
}
}
用于记录通话的AudioService
,但不断崩溃:
class AudioService : Service(), MediaRecorder.OnInfoListener {
lateinit var context: Context
private var mRecorder: MediaRecorder? = null
//setting maximum file size to be recorded
private val Audio_MAX_FILE_SIZE: Long = 1000000//1Mb
private var mOutputFile: File? = null
private var mStartTime: Long = 0
private val outputFile: File
get() {
val dateFormat = SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US)
return File(context.filesDir, //Environment.getExternalStorageDirectory(Environment.DIRECTORY_DOWNLOADS)
// .absolutePath.toString()
"RECORDING_" // "/Voice Recorder/RECORDING_"
+ dateFormat.format(Date())
+ ".m4a")
}
override fun onInfo(mr: MediaRecorder?, what: Int, extra: Int) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
stopRecording(true)
}
}
override fun onBind(intent: Intent?): IBinder? {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
return null
}
override fun onCreate() {
context = this
// Toast.makeText(context,"created", Toast.LENGTH_LONG).show()
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Toast.makeText(context,"started recording", Toast.LENGTH_LONG).show()
mRecorder = MediaRecorder()
mRecorder!!.setOnInfoListener(this)
mRecorder!!.apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setMaxFileSize(Audio_MAX_FILE_SIZE)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC)
setAudioEncodingBitRate(48000)
setAudioSamplingRate(16000)
}
mOutputFile = outputFile
mOutputFile!!.parentFile.mkdirs()
mRecorder!!.setOutputFile(mOutputFile!!.absolutePath)
try {
mRecorder!!.apply {
prepare()
start()
}
mStartTime = SystemClock.elapsedRealtime()
} catch (e: IOException) {
}
return Service.START_STICKY
}
private fun stopRecording(saveFile: Boolean) {
Toast.makeText(context,"stopped recording ", Toast.LENGTH_LONG).show()
mRecorder!!.apply {
stop()
release()
}
mRecorder = null
mStartTime = 0
if (!saveFile && mOutputFile != null) {
mOutputFile!!.delete()
}
// to stop the service by itself
stopSelf()
}
override fun onDestroy() {
super.onDestroy()
stopRecording(true)
}
}
在AndroidManifest
我添加了以下内容:
<receiver android:name=".broadcasts.PhoneStateReceiver">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
<action android:name="android.intent.action.READ_PHONE_STATE" />
</intent-filter>
</receiver>
<service android:name=".Services.AudioService" />
我已经在MainActivity
中授予了android.Manifest.permission.READ_PHONE_STATE
所需的运行时权限
更新
我在调试器中遇到了这个错误:
java.lang.RuntimeException:setAudioSource failed
所以,参考this我添加了Manifest.permission.RECORD_AUDIO
的运行时权限,但现在又出现了另一个错误,即:
java.lang.RuntimeException:start failed。
答案 0 :(得分:1)
需要添加以下权限:
uses-permission android:name="android.permission.RECORD_AUDIO"
此外,这是一个危险的&#34;权限,因此必须由用户直接授予: https://developer.android.com/training/permissions/requesting
答案 1 :(得分:1)
我发现原因是由于Android O API的变化。关于前台服务,并通知用户媒体录制。
我的Manifest
是:
<receiver android:name=".broadcasts.PhoneStateReceiver">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
<action android:name="android.intent.action.READ_PHONE_STATE" />
</intent-filter>
</receiver>
<service android:name=".Services.AudioService"
android:enabled="true"
android:exported="true"/>
我的Broadcast Receiver
是:
class PhoneStateReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
prefs = context.getSharedPreferences(PREFS_FILENAME, 0)
val record_calls = prefs!!.getBoolean("recordCalls", false)
val service = Intent(context, AudioService::class.java)
val state = intent.getStringExtra(TelephonyManager.EXTRA_STATE)
val incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER)
when(state){
TelephonyManager.EXTRA_STATE_RINGING -> {
// Toast.makeText(context, "Ringing $incomingNumber", Toast.LENGTH_LONG).show()
}
TelephonyManager.EXTRA_STATE_OFFHOOK -> {
// Toast.makeText(context, "IS_SERVICE_RUNNING $IS_SERVICE_RUNNING", Toast.LENGTH_LONG).show()
if (record_calls) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(service)
} else context.startService(service)
IS_SERVICE_RUNNING = true
}
}
TelephonyManager.EXTRA_STATE_IDLE -> if (IS_SERVICE_RUNNING) context.stopService(service)
else -> Toast.makeText(context, "ERROR", Toast.LENGTH_LONG).show()
}
}
}
我的Service
是:
var IS_SERVICE_RUNNING = false
class AudioService : Service(), MediaRecorder.OnInfoListener {
lateinit var context: Context
private var mRecorder: MediaRecorder? = null
//setting maximum file size to be recorded
private val Audio_MAX_FILE_SIZE: Long = 1000000//1Mb
private var mOutputFile: File? = null
private var mStartTime: Long = 0
private val outputFile: File
get() {
val dateFormat = SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US)
return File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), // Environment.DIRECTORY_DOCUMENTS, // context.filesDir, //
// .absolutePath.toString()
"call_" // "/Voice Recorder/RECORDING_"
+ dateFormat.format(Date())
+ ".m4a")
}
override fun onInfo(mr: MediaRecorder?, what: Int, extra: Int) {
if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
stopRecording(true)
}
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onCreate() {
super.onCreate()
context = this
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Toast.makeText(context,"started recording", Toast.LENGTH_LONG).show()
val intent = Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
val nManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notification = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder(context, NotificationService.CHANNEL_ID)
} else {
Notification.Builder(context)
}.apply {
setContentIntent(pendingIntent)
setSmallIcon(R.drawable.ic_error_black_24dp)
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
setAutoCancel(true)
setContentTitle(resources.getString(R.string.recording_title))
setStyle(Notification.BigTextStyle() .bigText(resources.getString(R.string.recording_body)))
setContentText(resources.getString(R.string.recording_body))
}.build()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
startForeground(1, notification);
} else {
nManager.notify(1, notification)
}
mRecorder = MediaRecorder().apply {
// reset()
}
mRecorder!!.setOnInfoListener(this)
mOutputFile = outputFile
mOutputFile!!.parentFile.mkdirs()
mRecorder = MediaRecorder()
mRecorder!!.apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
// setMaxFileSize(Audio_MAX_FILE_SIZE)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
// setOutputFile(mFileName)
setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC)
setAudioEncodingBitRate(48000)
setAudioSamplingRate(16000)
setOutputFile(mOutputFile!!.absolutePath)
}
try {
mRecorder!!.prepare()
mRecorder!!.start()
} catch (ise: IllegalStateException) {
Toast.makeText(context,"Error 1 $ise ", Toast.LENGTH_LONG).show()
} catch (ioe: IOException) {
Toast.makeText(context,"Error 2 $ioe ", Toast.LENGTH_LONG).show()
}
return Service.START_STICKY
}
private fun stopRecording(saveFile: Boolean) {
Toast.makeText(context,"stopped recording ", Toast.LENGTH_LONG).show()
mRecorder!!.apply {
stop()
reset()
release()
}
mRecorder = null
mStartTime = 0
if (!saveFile && mOutputFile != null) {
mOutputFile!!.delete()
}
// to stop the service by itself
stopSelf()
}
override fun onDestroy() {
super.onDestroy()
// Toast.makeText(context,"service destroyed ", Toast.LENGTH_LONG).show()
stopRecording(true)
}
}
我的NotificationUtility
是:
@RequiresApi(Build.VERSION_CODES.O)
class NotificationUtils(base: Context) : ContextWrapper(base) {
val nManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
init {
createChannels()
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createChannels() {
val myChannel = NotificationChannel(CHANNEL_ID,
CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT).apply {
enableLights(true)
enableVibration(true)
lightColor = Color.GREEN
lockscreenVisibility = Notification.VISIBILITY_PRIVATE
}
nManager.createNotificationChannel(myChannel)
}
companion object {
const val CHANNEL_ID = "my.CHANNEL_ID"
const val CHANNEL_NAME = "my.Notification"
}
}
对于运行时权限,我创建了Context Extensions
:
fun Context.toast(message: CharSequence) =
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
fun Context.arePermissionsGranted(permissions: Array<String>): Boolean {
permissions.forEach { it ->
if(ActivityCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED)
return false
}
return true
}
fun Context.isPermissionGranted(permission: String) =
ActivityCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
fun Context.batchRequestPermissions(permissions: Array<String>, requestId: Int) =
ActivityCompat.requestPermissions(this as Activity, permissions, requestId)
fun Context.requestPermission(permission: String, requestId: Int) =
ActivityCompat.requestPermissions(this as Activity, arrayOf(permission), requestId)
在MainActivity
我&#t; ve:
val CALL_PERMISSIONS =
arrayOf(READ_PHONE_STATE, RECORD_AUDIO, WRITE_EXTERNAL_STORAGE)
const val CALL_RECORD_PERMISSION_REQUEST_ALL = 10
class MainActivity : AppCompatActivity() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) NotificationUtils(this)
val self = this as Context
val dialog = AlertDialog.Builder(this).apply {
setTitle(R.string.permissions_required)
setIcon(R.drawable.ic_done_all_black_24dp)
setMessage(R.string.grant_permissions_required)
setPositiveButton("Confirm", { dialog, i ->
self.batchRequestPermissions(CALL_PERMISSIONS, CALL_RECORD_PERMISSION_REQUEST_ALL)
})
}
}