1. 程式人生 > 程式設計 >android實現通話自動錄音服務

android實現通話自動錄音服務

本文例項為大家分享了android實現通話自動錄音服務的具體程式碼,供大家參考,具體內容如下

需求:

①:通話自動錄音;
②:無介面,只是一個service;
③:錄音自動壓縮上傳;
④:當用戶清理後臺的時候,要求service不可以被殺死;
⑤:穩定性:1、無網路的情況下;2、上傳失敗;3、服務報錯。

解決方案:

①:通話自動錄音

啟動一個service,監聽使用者手機通話狀態,當檢測到使用者處於通話狀態下,立即開始錄音,通話結束後,停止錄音,並儲存檔案。
此功能的前提條件:
1、錄音許可權、讀寫儲存空間的許可權、讀取通話狀態的許可權;
2、Service不可以被停止,否則無法錄音。
3、開機啟動(不可以讓使用者每次開機都主動去開啟服務)

②:無介面,只是一個service

方案①
普通的service,監聽開機廣播,當用戶開機的時候,啟動service。但是,發現service並沒有啟動。想要啟動一個service,必須要有一個activity,即使你不開啟這個activity。
在真正做專案的時候,PM會提出各種你不能理解的需求,比如說本系統,PM要求本應用只是一個錄音服務,不可以有任何介面,也不可以在手機桌面上出現應用圖示。因此,方案①不可行。
方案②
Android手機在設定裡面都一個輔助功能(個別手機也叫:無障礙),利用這個我們可以實現一些強大的功能,前提是使用者開啟我們的輔助功能,搶紅包軟體就是利用輔助功能實現的。

android實現通話自動錄音服務

③:錄音自動壓縮上傳

我們只需要在上傳之前對檔案進行壓縮處理,然後再上傳即可。

④:當用戶清理後臺的時候,要求service不可以被殺死

不會被殺死的服務,或許只有系統服務吧。當然類似於QQ、微信他們做的這種全家桶也可以做到。大公司是可以和廠商合作的,他們的應用可以不那麼容易被殺死。當然也不提倡這樣做,這樣就是垃圾軟體,破壞了Android開發的美好環境。
其實,如果可以把服務設定成系統服務,那麼只要使用者不主動在輔助功能頁面關掉服務,後臺是清理不掉改服務的。本人在小米手機上測試過,設定成系統級別的服務後,當清理後臺的時候,即使服務被殺死,也會非常快的重新啟動。(感興趣的同學可以試一下)

⑤:穩定性:1、無網路的情況下;2、上傳失敗;3、服務報錯

思路:

當無網路的情況下,把錄音檔案的地址儲存下來(儲存的方式有很多:Sqlite、Sharedpreferences等等),上傳失敗也一樣,失敗的原因可能有很多種:網路斷開、介面報錯等,當網路恢復的時候,可以重新上傳,這樣就不會丟失錄音檔案。
程式碼很簡單,註釋很詳細:

專案的結構:

android實現通話自動錄音服務

<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;
 }
}

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。