android實現(xiàn)通話自動錄音服務
本文實例為大家分享了android實現(xiàn)通話自動錄音服務的具體代碼,供大家參考,具體內(nèi)容如下
需求:
①:通話自動錄音;
②:無界面,只是一個service;
③:錄音自動壓縮上傳;
④:當用戶清理后臺的時候,要求service不可以被殺死;
⑤:穩(wěn)定性:1、無網(wǎng)絡的情況下;2、上傳失敗;3、服務報錯。
解決方案:
①:通話自動錄音
啟動一個service,監(jiān)聽用戶手機通話狀態(tài),當檢測到用戶處于通話狀態(tài)下,立即開始錄音,通話結束后,停止錄音,并保存文件。
此功能的前提條件:
1、錄音權限、讀寫存儲空間的權限、讀取通話狀態(tài)的權限;
2、Service不可以被停止,否則無法錄音。
3、開機啟動(不可以讓用戶每次開機都主動去打開服務)
②:無界面,只是一個service
方案①
普通的service,監(jiān)聽開機廣播,當用戶開機的時候,啟動service。但是,發(fā)現(xiàn)service并沒有啟動。想要啟動一個service,必須要有一個activity,即使你不打開這個activity。
在真正做項目的時候,PM會提出各種你不能理解的需求,比如說本系統(tǒng),PM要求本應用只是一個錄音服務,不可以有任何界面,也不可以在手機桌面上出現(xiàn)應用圖標。因此,方案①不可行。
方案②
Android手機在設置里面都一個輔助功能(個別手機也叫:無障礙),利用這個我們可以實現(xiàn)一些強大的功能,前提是用戶開啟我們的輔助功能,搶紅包軟件就是利用輔助功能實現(xiàn)的。

③:錄音自動壓縮上傳
我們只需要在上傳之前對文件進行壓縮處理,然后再上傳即可。
④:當用戶清理后臺的時候,要求service不可以被殺死
不會被殺死的服務,或許只有系統(tǒng)服務吧。當然類似于QQ、微信他們做的這種全家桶也可以做到。大公司是可以和廠商合作的,他們的應用可以不那么容易被殺死。當然也不提倡這樣做,這樣就是垃圾軟件,破壞了Android開發(fā)的美好環(huán)境。
其實,如果可以把服務設置成系統(tǒng)服務,那么只要用戶不主動在輔助功能頁面關掉服務,后臺是清理不掉改服務的。本人在小米手機上測試過,設置成系統(tǒng)級別的服務后,當清理后臺的時候,即使服務被殺死,也會非??斓闹匦聠印#ǜ信d趣的同學可以試一下)
⑤:穩(wěn)定性:1、無網(wǎng)絡的情況下;2、上傳失??;3、服務報錯
思路:
當無網(wǎng)絡的情況下,把錄音文件的地址保存下來(保存的方式有很多:Sqlite、Sharedpreferences等等),上傳失敗也一樣,失敗的原因可能有很多種:網(wǎng)絡斷開、接口報錯等,當網(wǎng)絡恢復的時候,可以重新上傳,這樣就不會丟失錄音文件。
代碼很簡單,注釋很詳細:
項目的結構:

<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" /> <!-- 要存儲文件或者創(chuàng)建文件夾的話還需要以下兩個權限 --> <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"/> <!--允許讀取網(wǎng)絡狀態(tài)--> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <!--允許讀取wifi網(wǎng)絡狀態(tài)--> <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 = "手機通話狀態(tài)";
/**
* 音頻錄制
*/
private MediaRecorder recorder;
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 監(jiān)聽撥號廣播,以便獲取用戶撥出的電話號碼
*/
private OutCallReceiver outCallReceiver;
private IntentFilter intentFilter;
/**
* 網(wǎng)絡狀態(tài)改變廣播,當網(wǎng)絡暢通的狀態(tài)下,把用戶未上傳的錄音文件都上傳掉
*/
private NetworkConnectChangedReceiver networkConnectChangedReceiver;
private IntentFilter intentFilter2;
/**
* 當前通話對象的電話號碼
*/
private String currentCallNum = "";
/**
* 區(qū)分來電和去電
*/
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);
// 監(jiān)聽電話狀態(tài)
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();
//設置網(wǎng)絡狀態(tài)改變廣播過濾
intentFilter2.addAction("android.net.conn.CONNECTIVITY_CHANGE");
intentFilter2.addAction("android.net.wifi.WIFI_STATE_CHANGED");
intentFilter2.addAction("android.net.wifi.STATE_CHANGE");
//注冊網(wǎng)絡狀態(tài)改變廣播接收器
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(), "進程被關閉,無法繼續(xù)錄音,請打開錄音服務", 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, "空閑狀態(tài)" + 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);
}
}
/**
* 當錄音結束后,自動上傳錄音文件。
* ①網(wǎng)絡可用:直接上傳;
* ②網(wǎng)絡不可用:保存文件路徑,待網(wǎng)絡可用的時候再進行上傳;
* ③上傳失敗的文件,也保存文件路徑,或者重新上傳。
*/
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();
}
/**
* 上傳因為網(wǎng)絡或者其他原因,暫未上傳或者上傳失敗的文件,重新上傳
*/
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, "有電話,快接聽電話");
}
}
}
/**
* 網(wǎng)絡狀態(tài)change廣播接收器
*/
public class NetworkConnectChangedReceiver extends BroadcastReceiver {
private static final String TAG = "network status";
@Override
public void onReceive(Context context, Intent intent) {
/**
* 這個監(jiān)聽網(wǎng)絡連接的設置,包括wifi和移動數(shù)據(jù)的打開和關閉。.
* 最好用的還是這個監(jiān)聽。wifi如果打開,關閉,以及連接上可用的連接都會接到監(jiān)聽。見log
* 這個廣播的最大弊端是比上邊兩個廣播的反應要慢,如果只是要監(jiān)聽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()) {
//當前網(wǎng)絡可用
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, "當前移動網(wǎng)絡連接可用 ");
}
uploadUnUploadedFiles();
} else {
Log.e(TAG, "當前沒有網(wǎng)絡連接,請確保你已經(jīng)打開網(wǎng)絡 ");
}
} else { // not connected to the internet
Log.e(TAG, "當前沒有網(wǎng)絡連接,請確保你已經(jīng)打開網(wǎng)絡 ");
}
}
}
}
}
/**
* 獲取網(wǎng)絡連接狀態(tài)工具類
* Created by wang.ao in 2017/2/24.
*/
public class NetWorkUtils {
/**
* 判斷是否有網(wǎng)絡連接
* @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網(wǎng)絡是否可用
* @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網(wǎng)絡是否可用
* @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;
}
/**
* 獲取當前網(wǎng)絡連接的類型信息
* @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;
}
/**
* 獲取當前的網(wǎng)絡狀態(tài) :沒有網(wǎng)絡0:WIFI網(wǎng)絡1:3G網(wǎng)絡2:2G網(wǎng)絡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 緩沖區(qū)
*
* @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 只要解壓過程中發(fā)生錯誤,就立即停止并返回(等價于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;
}
}
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
- Android音頻錄制MediaRecorder之簡易的錄音軟件實現(xiàn)代碼
- Android簡單的利用MediaRecorder進行錄音的實例代碼
- Android應用開發(fā):電話監(jiān)聽和錄音代碼示例
- Android App調(diào)用MediaRecorder實現(xiàn)錄音功能的實例
- Android實現(xiàn)錄音方法(仿微信語音、麥克風錄音、發(fā)送語音、解決5.0以上BUG)
- Android實現(xiàn)錄音功能實現(xiàn)實例(MediaRecorder)
- Android實現(xiàn)語音播放與錄音功能
- Android實現(xiàn)通話自動錄音
- android MediaRecorder實現(xiàn)錄屏時帶錄音功能
- Android仿微信語音對講錄音功能
相關文章
Android開發(fā)中用Kotlin編寫LiveData組件教程
LiveData是Jetpack組件的一部分,更多的時候是搭配ViewModel來使用,相對于Observable,LiveData的最大優(yōu)勢是其具有生命感知的,換句話說,LiveData可以保證只有在組件( Activity、Fragment、Service)處于活動生命周期狀態(tài)的時候才會更新數(shù)據(jù)2022-12-12
Android日期選擇器實現(xiàn)年月日三級聯(lián)動
這篇文章主要為大家詳細介紹了Android日期選擇器實現(xiàn)年月日三級聯(lián)動,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-01-01
解決android viewmodel 數(shù)據(jù)刷新異常的問題
這篇文章主要介紹了解決android viewmodel 數(shù)據(jù)刷新異常的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03
android在連拍菜單中增加連拍張數(shù)選項功能實現(xiàn)代碼
想要增加連拍張數(shù)選項需要在entries, entryvalues中添加兩項,同時在mtk_strings.xml中添加相應的字符串,具體如下,感興趣的朋友可以參考下哈2013-06-06

