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,"失敗 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; } }
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。