android实现通话自动录音服务
本文实例为大家分享了android实现通话自动录音服务的具体代码,供大家参考,具体内容如下
需求:
①:通话自动录音;
②:无界面,只是一个service;
③:录音自动压缩上传;
④:当用户清理后台的时候,要求service不可以被杀死;
⑤:稳定性:1、无网络的情况下;2、上传失败;3、服务报错。
解决方案:
①:通话自动录音
启动一个service,监听用户手机通话状态,当检测到用户处于通话状态下,立即开始录音,通话结束后,停止录音,并保存文件。
此功能的前提条件:
1、录音权限、读写存储空间的权限、读取通话状态的权限;
2、Service不可以被停止,否则无法录音。
3、开机启动(不可以让用户每次开机都主动去打开服务)
②:无界面,只是一个service
方案①
普通的service,监听开机广播,当用户开机的时候,启动service。但是,发现service并没有启动。想要启动一个service,必须要有一个activity,即使你不打开这个activity。
在真正做项目的时候,PM会提出各种你不能理解的需求,比如说本系统,PM要求本应用只是一个录音服务,不可以有任何界面,也不可以在手机桌面上出现应用图标。因此,方案①不可行。
方案②
Android手机在设置里面都一个辅助功能(个别手机也叫:无障碍),利用这个我们可以实现一些强大的功能,前提是用户开启我们的辅助功能,抢红包软件就是利用辅助功能实现的。
③:录音自动压缩上传
我们只需要在上传之前对文件进行压缩处理,然后再上传即可。
④:当用户清理后台的时候,要求service不可以被杀死
不会被杀死的服务,或许只有系统服务吧。当然类似于QQ、微信他们做的这种全家桶也可以做到。大公司是可以和厂商合作的,他们的应用可以不那么容易被杀死。当然也不提倡这样做,这样就是垃圾软件,破坏了Android开发的美好环境。
其实,如果可以把服务设置成系统服务,那么只要用户不主动在辅助功能页面关掉服务,后台是清理不掉改服务的。本人在小米手机上测试过,设置成系统级别的服务后,当清理后台的时候,即使服务被杀死,也会非常快的重新启动。(感兴趣的同学可以试一下)
⑤:稳定性:1、无网络的情况下;2、上传失败;3、服务报错
思路:
当无网络的情况下,把录音文件的地址保存下来(保存的方式有很多:Sqlite、Sharedpreferences等等),上传失败也一样,失败的原因可能有很多种:网络断开、接口报错等,当网络恢复的时候,可以重新上传,这样就不会丢失录音文件。
代码很简单,注释很详细:
项目的结构:
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <!-- 要存储文件或者创建文件夹的话还需要以下两个权限 --> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.INTERNET"/> <!--允许读取网络状态--> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <!--允许读取wifi网络状态--> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<service android:name=".service.RecorderService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessible_service_config" /> </service>
/** * 电话自动录音辅助服务(去电、来电自动录音并上传)。 * Created by wang.ao in 2017/2/24. */ public class RecorderService extends AccessibilityService { private static final String TAG = "RecorderService"; private static final String TAG1 = "手机通话状态"; /** * 音频录制 */ private MediaRecorder recorder; private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /** * 监听拨号广播,以便获取用户拨出的电话号码 */ private OutCallReceiver outCallReceiver; private IntentFilter intentFilter; /** * 网络状态改变广播,当网络畅通的状态下,把用户未上传的录音文件都上传掉 */ private NetworkConnectChangedReceiver networkConnectChangedReceiver; private IntentFilter intentFilter2; /** * 当前通话对象的电话号码 */ private String currentCallNum = ""; /** * 区分来电和去电 */ private int previousStats = 0; /** * 当前正在录制的文件 */ private String currentFile = ""; /** * 保存未上传的录音文件 */ private SharedPreferences unUploadFile; private String dirPath = ""; private boolean isRecording = false; @Override protected void onServiceConnected() { Log.i(TAG, "onServiceConnected"); Toast.makeText(getApplicationContext(), "自动录音服务已启动", Toast.LENGTH_LONG).show(); } @Override public void onAccessibilityEvent(AccessibilityEvent event) { // TODO Auto-generated method stub Log.i(TAG, "eventType " + event.getEventType()); } @Override public void onInterrupt() { // TODO Auto-generated method stub Log.i(TAG, "onServiceConnected"); } @Override public boolean onUnbind(Intent intent) { return super.onUnbind(intent); } @Override public void onCreate() { super.onCreate(); TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); // 监听电话状态 tm.listen(new MyListener(), PhoneStateListener.LISTEN_CALL_STATE); outCallReceiver = new OutCallReceiver(); intentFilter = new IntentFilter(); //设置拨号广播过滤 intentFilter.addAction("android.intent.action.NEW_OUTGOING_CALL"); registerReceiver(outCallReceiver, intentFilter); //注册拨号广播接收器 networkConnectChangedReceiver = new NetworkConnectChangedReceiver(); intentFilter2 = new IntentFilter(); //设置网络状态改变广播过滤 intentFilter2.addAction("android.net.conn.CONNECTIVITY_CHANGE"); intentFilter2.addAction("android.net.wifi.WIFI_STATE_CHANGED"); intentFilter2.addAction("android.net.wifi.STATE_CHANGE"); //注册网络状态改变广播接收器 registerReceiver(networkConnectChangedReceiver, intentFilter2); unUploadFile = getSharedPreferences("un_upload_file", 0); unUploadFile.edit().putString("description", "未上传的录音文件存放路径").commit(); dirPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/com.ct.phonerecorder/"; } @Override public void onDestroy() { super.onDestroy(); Toast.makeText(getApplicationContext(), "进程被关闭,无法继续录音,请打开录音服务", Toast.LENGTH_LONG).show(); if (outCallReceiver != null) { unregisterReceiver(outCallReceiver); } if (networkConnectChangedReceiver != null) { unregisterReceiver(networkConnectChangedReceiver); } } class MyListener extends PhoneStateListener { @Override public void onCallStateChanged(int state, String incomingNumber) { // TODO Auto-generated method stub Log.d(TAG1, "空闲状态" + incomingNumber); switch (state) { case TelephonyManager.CALL_STATE_IDLE: Log.d(TAG1, "空闲"); if (recorder != null && isRecording) { recorder.stop();// 停止录音 recorder.release(); recorder = null; Log.d("电话", "通话结束,停止录音"); uploadFile(currentFile); } isRecording = false; break; case TelephonyManager.CALL_STATE_RINGING: Log.d(TAG1, "来电响铃" + incomingNumber); // 进行初始化 break; case TelephonyManager.CALL_STATE_OFFHOOK: Log.d(TAG1, "摘机" + (!incomingNumber.equals("") ? incomingNumber : currentCallNum)); initRecord(!incomingNumber.equals("") ? incomingNumber : currentCallNum); // 开始录音 if (recorder != null) { recorder.start(); isRecording = true; } default: break; } super.onCallStateChanged(state, incomingNumber); } } /** * 当录音结束后,自动上传录音文件。 * ①网络可用:直接上传; * ②网络不可用:保存文件路径,待网络可用的时候再进行上传; * ③上传失败的文件,也保存文件路径,或者重新上传。 */ public void uploadFile(String file) { ZipUtils.zipFile(dirPath + file, dirPath + file + ".zip"); if (NetWorkUtils.isNetworkConnected(getApplicationContext())) { //上传文件 // OkHttpUtils.postFile() } else { saveUnUploadFIles(dirPath + file + ".zip"); } } /** * 保存未上传的录音文件 * * @param file 未上传的录音文件路径 */ private void saveUnUploadFIles(String file) { String files = unUploadFile.getString("unUploadFile", ""); if (files.equals("")) { files = file; } else { StringBuilder sb = new StringBuilder(files); files = sb.append(";").append(file).toString(); } unUploadFile.edit().putString("unUploadFile", files).commit(); } /** * 上传因为网络或者其他原因,暂未上传或者上传失败的文件,重新上传 */ public void uploadUnUploadedFiles() { //获取当前还未上传的文件,并把这些文件上传 String files = unUploadFile.getString("unUploadFile", ""); unUploadFile.edit().putString("unUploadFile", "").commit(); if (files.equals("")) { return; } String[] fileArry = files.split(";"); int len = fileArry.length; for (String file : fileArry) { upload(file); } } /** * 文件上传 * * @param file 要上传的文件 */ public void upload(final String file) { File file1 = new File(file); if (file1 == null || !file1.exists()) { //文件不存在 return; } if (!NetWorkUtils.isNetworkConnected(getApplicationContext())) { saveUnUploadFIles(file); return; } Map<String, String> map = new HashMap<String, String>(); map.put("type", "1"); final String url = "http://192.168.1.158:8082/uploader"; OkHttpUtils.post()// .addFile("mFile", file1.getName(), file1)// .url(url)// .params(map).build()// .execute(new StringCallback() { @Override public void onResponse(String response, int id) { Log.e(TAG, "成功 response=" + response); } @Override public void onError(Call call, Exception e, int id) { Log.e(TAG, "失败 response=" + e.toString()); saveUnUploadFIles(file); } }); } /** * 初始化录音机,并给录音文件重命名 * * @param incomingNumber 通话号码 */ private void initRecord(String incomingNumber) { previousStats = TelephonyManager.CALL_STATE_RINGING; recorder = new MediaRecorder(); recorder.setAudioSource(MediaRecorder.AudioSource.MIC);// Microphone recorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);// 设置输出3gp格式 File out = new File(dirPath); if (!out.exists()) { out.mkdirs(); } recorder.setOutputFile(dirPath + getFileName((previousStats == TelephonyManager.CALL_STATE_RINGING ? incomingNumber : currentCallNum)) ); recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);// 设置音频编码格式 try { recorder.prepare();// 做好准备 } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 获取录音文件的名称 * * @param incomingNumber 通话号码 * @return 获取录音文件的名称 */ private String getFileName(String incomingNumber) { Date date = new Date(System.currentTimeMillis()); currentFile = incomingNumber + " " + dateFormat.format(date) + ".mp3"; return currentFile; } /** * 拨号广播接收器,并获取拨号号码 */ public class OutCallReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Log.d(TAG1, "当前手机拨打了电话:" + currentCallNum); if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) { currentCallNum = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); Log.d(TAG1, "当前手机拨打了电话:" + currentCallNum); } else { Log.d(TAG1, "有电话,快接听电话"); } } } /** * 网络状态change广播接收器 */ public class NetworkConnectChangedReceiver extends BroadcastReceiver { private static final String TAG = "network status"; @Override public void onReceive(Context context, Intent intent) { /** * 这个监听网络连接的设置,包括wifi和移动数据的打开和关闭。. * 最好用的还是这个监听。wifi如果打开,关闭,以及连接上可用的连接都会接到监听。见log * 这个广播的最大弊端是比上边两个广播的反应要慢,如果只是要监听wifi,我觉得还是用上边两个配合比较合适 */ if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { ConnectivityManager manager = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); Log.i(TAG, "CONNECTIVITY_ACTION"); NetworkInfo activeNetwork = manager.getActiveNetworkInfo(); if (activeNetwork != null) { // connected to the internet if (activeNetwork.isConnected()) { //当前网络可用 if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) { // connected to wifi Log.e(TAG, "当前WiFi连接可用 "); } else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) { // connected to the mobile provider's data plan Log.e(TAG, "当前移动网络连接可用 "); } uploadUnUploadedFiles(); } else { Log.e(TAG, "当前没有网络连接,请确保你已经打开网络 "); } } else { // not connected to the internet Log.e(TAG, "当前没有网络连接,请确保你已经打开网络 "); } } } } }
/** * 获取网络连接状态工具类 * Created by wang.ao in 2017/2/24. */ public class NetWorkUtils { /** * 判断是否有网络连接 * @param context * @return */ public static boolean isNetworkConnected(Context context) { if (context != null) { ConnectivityManager mConnectivityManager = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo(); if (mNetworkInfo != null) { return mNetworkInfo.isAvailable(); } } return false; } /** * 判断WIFI网络是否可用 * @param context * @return */ public static boolean isWifiConnected(Context context) { if (context != null) { ConnectivityManager mConnectivityManager = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo mWiFiNetworkInfo = mConnectivityManager .getNetworkInfo(ConnectivityManager.TYPE_WIFI); if (mWiFiNetworkInfo != null) { return mWiFiNetworkInfo.isAvailable(); } } return false; } /** * 判断MOBILE网络是否可用 * @param context * @return */ public static boolean isMobileConnected(Context context) { if (context != null) { ConnectivityManager mConnectivityManager = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo mMobileNetworkInfo = mConnectivityManager .getNetworkInfo(ConnectivityManager.TYPE_MOBILE); if (mMobileNetworkInfo != null) { return mMobileNetworkInfo.isAvailable(); } } return false; } /** * 获取当前网络连接的类型信息 * @param context * @return */ public static int getConnectedType(Context context) { if (context != null) { ConnectivityManager mConnectivityManager = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo(); if (mNetworkInfo != null && mNetworkInfo.isAvailable()) { return mNetworkInfo.getType(); } } return -1; } /** * 获取当前的网络状态 :没有网络0:WIFI网络1:3G网络2:2G网络3 * * @param context * @return */ public static int getAPNType(Context context) { int netType = 0; ConnectivityManager connMgr = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); if (networkInfo == null) { return netType; } int nType = networkInfo.getType(); if (nType == ConnectivityManager.TYPE_WIFI) { netType = 1;// wifi } else if (nType == ConnectivityManager.TYPE_MOBILE) { int nSubType = networkInfo.getSubtype(); TelephonyManager mTelephony = (TelephonyManager) context .getSystemService(Context.TELEPHONY_SERVICE); if (nSubType == TelephonyManager.NETWORK_TYPE_UMTS && !mTelephony.isNetworkRoaming()) { netType = 2;// 3G } else { netType = 3;// 2G } } return netType; } }
public class ZipUtils { private static final int BUFF_SIZE = 1024; /** * @param zos 压缩流 * @param parentDirName 父目录 * @param file 待压缩文件 * @param buffer 缓冲区 * * @return 只要目录中有一个文件压缩失败,就停止并返回 */ private static boolean zipFile(ZipOutputStream zos, String parentDirName, File file, byte[] buffer) { String zipFilePath = parentDirName + file.getName(); if (file.isDirectory()) { zipFilePath += File.separator; for (File f : file.listFiles()) { if (!zipFile(zos, zipFilePath, f, buffer)) { return false; } } return true; } else { try { BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); ZipEntry zipEntry = new ZipEntry(zipFilePath); zipEntry.setSize(file.length()); zos.putNextEntry(zipEntry); while (bis.read(buffer) != -1) { zos.write(buffer); } bis.close(); return true; } catch (FileNotFoundException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } return false; } } /** * @param srcPath 待压缩的文件或目录 * @param dstPath 压缩后的zip文件 * @return 只要待压缩的文件有一个压缩失败就停止压缩并返回(等价于windows上直接进行压缩) */ public static boolean zipFile(String srcPath, String dstPath) { File srcFile = new File(srcPath); if (!srcFile.exists()) { return false; } byte[] buffer = new byte[BUFF_SIZE]; try { ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dstPath)); boolean result = zipFile(zos, "", srcFile, buffer); zos.close(); return result; } catch (FileNotFoundException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } return false; } /** * @param srcPath 待解压的zip文件 * @param dstPath zip解压后待存放的目录 * @return 只要解压过程中发生错误,就立即停止并返回(等价于windows上直接进行解压) */ public static boolean unzipFile(String srcPath, String dstPath) { if (TextUtils.isEmpty(srcPath) || TextUtils.isEmpty(dstPath)) { return false; } File srcFile = new File(srcPath); if (!srcFile.exists() || !srcFile.getName().toLowerCase(Locale.getDefault()).endsWith("zip")) { return false; } File dstFile = new File(dstPath); if (!dstFile.exists() || !dstFile.isDirectory()) { dstFile.mkdirs(); } try { ZipInputStream zis = new ZipInputStream(new FileInputStream(srcFile)); BufferedInputStream bis = new BufferedInputStream(zis); ZipEntry zipEntry = null; byte[] buffer = new byte[BUFF_SIZE]; if (!dstPath.endsWith(File.separator)) { dstPath += File.separator; } while ((zipEntry = zis.getNextEntry()) != null) { String fileName = dstPath + zipEntry.getName(); File file = new File(fileName); File parentDir = file.getParentFile(); if (!parentDir.exists()) { parentDir.mkdirs(); } FileOutputStream fos = new FileOutputStream(file); while (bis.read(buffer) != -1) { fos.write(buffer); } fos.close(); } bis.close(); zis.close(); return true; } catch (FileNotFoundException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } return false; } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
相关文章
Android AutoCompleteTextView自动提示文本框实例代码
这篇文章主要介绍了Android AutoCompleteTextView自动提示文本框实例代码的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下2016-07-07将替代ListView的RecyclerView 的使用详解(一)
这篇文章主要介绍了将替代ListView的RecyclerView 的使用详解(一)的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下2016-07-07Android Studio不能获取远程依赖包的完美解决方法
这篇文章主要介绍了Android Studio不能获取远程依赖包的解决方法,非常不错,具有参考借鉴价值,需要的朋友可以参考下2017-11-11
最新评论