В моем приложении есть приведенный ниже код для записи звонков, но он продолжает вылетать после вызова «службы»!
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" />
Я уже предоставил необходимое разрешение на выполнение android.Manifest.permission.READ_PHONE_STATE
в MainActivity
ОБНОВИТЬ
У меня в отладчике такая ошибка:
java.lang.RuntimeException: setAudioSource failed
Итак, обращаясь к это, я добавил разрешение времени выполнения Manifest.permission.RECORD_AUDIO
, но теперь получаю другую ошибку, а именно:
java.lang.RuntimeException: start failed.
Пожалуйста, используйте отладчик (входит в состав студии Android), чтобы получить журналы сбоя. Специально ищите журнал ошибок. Это подскажет, почему приложение вылетает из-за определенной ошибки и местоположения. Если вы все еще не можете это исправить, вы можете задать здесь конкретную ошибку, с которой столкнулись.
@XXZ дает java.lang.RuntimeException: setAudioSource failed
@Yashovardhan дает java.lang.RuntimeException: setAudioSource failed
Необходимо добавить следующее разрешение:
uses-permission android:name = "android.permission.RECORD_AUDIO"
Кроме того, это «опасное» разрешение, поэтому оно должно быть предоставлено непосредственно пользователем: https://developer.android.com/training/permissions/requesting
Спасибо, я получил еще одну ошибку и обновил свой вопрос.
В какой строке происходит «java.lang.RuntimeException: start failed»?
`попробуйте {mRecorder !!. apply {prepare () start ()}`
java.io.IOException: нет допустимого выходного файла
да, оно есть: mRecorder!!.setOutputFile(mOutputFile!!.absolutePath)
Позвольте нам продолжить обсуждение в чате.
Я обнаружил, что причина в изменениях в 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
есть:
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)
})
}
}
Не могли бы вы выложить журнал сбоев?