美文网首页
android 通话自动录音服务

android 通话自动录音服务

作者: 傲子 | 来源:发表于2017-02-27 14:31 被阅读0次

    需求:
    ①:通话自动录音;
    ②:无界面,只是一个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        缓冲区
         *                      URL:http://www.bianceng.cn/OS/extra/201609/50420.htm
         * @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 通话自动录音服务

          本文链接:https://www.haomeiwen.com/subject/vwyzwttx.html