android實(shí)現(xiàn)通話自動(dòng)錄音服務(wù)
本文實(shí)例為大家分享了android實(shí)現(xiàn)通話自動(dòng)錄音服務(wù)的具體代碼,供大家參考,具體內(nèi)容如下
需求:
①:通話自動(dòng)錄音;
②:無界面,只是一個(gè)service;
③:錄音自動(dòng)壓縮上傳;
④:當(dāng)用戶清理后臺(tái)的時(shí)候,要求service不可以被殺死;
⑤:穩(wěn)定性:1、無網(wǎng)絡(luò)的情況下;2、上傳失敗;3、服務(wù)報(bào)錯(cuò)。
解決方案:
①:通話自動(dòng)錄音
啟動(dòng)一個(gè)service,監(jiān)聽用戶手機(jī)通話狀態(tài),當(dāng)檢測(cè)到用戶處于通話狀態(tài)下,立即開始錄音,通話結(jié)束后,停止錄音,并保存文件。
此功能的前提條件:
1、錄音權(quán)限、讀寫存儲(chǔ)空間的權(quán)限、讀取通話狀態(tài)的權(quán)限;
2、Service不可以被停止,否則無法錄音。
3、開機(jī)啟動(dòng)(不可以讓用戶每次開機(jī)都主動(dòng)去打開服務(wù))
②:無界面,只是一個(gè)service
方案①
普通的service,監(jiān)聽開機(jī)廣播,當(dāng)用戶開機(jī)的時(shí)候,啟動(dòng)service。但是,發(fā)現(xiàn)service并沒有啟動(dòng)。想要啟動(dòng)一個(gè)service,必須要有一個(gè)activity,即使你不打開這個(gè)activity。
在真正做項(xiàng)目的時(shí)候,PM會(huì)提出各種你不能理解的需求,比如說本系統(tǒng),PM要求本應(yīng)用只是一個(gè)錄音服務(wù),不可以有任何界面,也不可以在手機(jī)桌面上出現(xiàn)應(yīng)用圖標(biāo)。因此,方案①不可行。
方案②
Android手機(jī)在設(shè)置里面都一個(gè)輔助功能(個(gè)別手機(jī)也叫:無障礙),利用這個(gè)我們可以實(shí)現(xiàn)一些強(qiáng)大的功能,前提是用戶開啟我們的輔助功能,搶紅包軟件就是利用輔助功能實(shí)現(xiàn)的。
③:錄音自動(dòng)壓縮上傳
我們只需要在上傳之前對(duì)文件進(jìn)行壓縮處理,然后再上傳即可。
④:當(dāng)用戶清理后臺(tái)的時(shí)候,要求service不可以被殺死
不會(huì)被殺死的服務(wù),或許只有系統(tǒng)服務(wù)吧。當(dāng)然類似于QQ、微信他們做的這種全家桶也可以做到。大公司是可以和廠商合作的,他們的應(yīng)用可以不那么容易被殺死。當(dāng)然也不提倡這樣做,這樣就是垃圾軟件,破壞了Android開發(fā)的美好環(huán)境。
其實(shí),如果可以把服務(wù)設(shè)置成系統(tǒng)服務(wù),那么只要用戶不主動(dòng)在輔助功能頁面關(guān)掉服務(wù),后臺(tái)是清理不掉改服務(wù)的。本人在小米手機(jī)上測(cè)試過,設(shè)置成系統(tǒng)級(jí)別的服務(wù)后,當(dāng)清理后臺(tái)的時(shí)候,即使服務(wù)被殺死,也會(huì)非??斓闹匦聠?dòng)。(感興趣的同學(xué)可以試一下)
⑤:穩(wěn)定性:1、無網(wǎng)絡(luò)的情況下;2、上傳失敗;3、服務(wù)報(bào)錯(cuò)
思路:
當(dāng)無網(wǎng)絡(luò)的情況下,把錄音文件的地址保存下來(保存的方式有很多:Sqlite、Sharedpreferences等等),上傳失敗也一樣,失敗的原因可能有很多種:網(wǎng)絡(luò)斷開、接口報(bào)錯(cuò)等,當(dāng)網(wǎng)絡(luò)恢復(fù)的時(shí)候,可以重新上傳,這樣就不會(huì)丟失錄音文件。
代碼很簡(jiǎn)單,注釋很詳細(xì):
項(xiàng)目的結(jié)構(gòu):
<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" /> <!-- 要存儲(chǔ)文件或者創(chuàng)建文件夾的話還需要以下兩個(gè)權(quán)限 --> <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)絡(luò)狀態(tài)--> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <!--允許讀取wifi網(wǎng)絡(luò)狀態(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>
/** * 電話自動(dòng)錄音輔助服務(wù)(去電、來電自動(dòng)錄音并上傳)。 * Created by wang.ao in 2017/2/24. */ public class RecorderService extends AccessibilityService { private static final String TAG = "RecorderService"; private static final String TAG1 = "手機(jī)通話狀態(tài)"; /** * 音頻錄制 */ private MediaRecorder recorder; private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /** * 監(jiān)聽撥號(hào)廣播,以便獲取用戶撥出的電話號(hào)碼 */ private OutCallReceiver outCallReceiver; private IntentFilter intentFilter; /** * 網(wǎng)絡(luò)狀態(tài)改變廣播,當(dāng)網(wǎng)絡(luò)暢通的狀態(tài)下,把用戶未上傳的錄音文件都上傳掉 */ private NetworkConnectChangedReceiver networkConnectChangedReceiver; private IntentFilter intentFilter2; /** * 當(dāng)前通話對(duì)象的電話號(hào)碼 */ private String currentCallNum = ""; /** * 區(qū)分來電和去電 */ private int previousStats = 0; /** * 當(dāng)前正在錄制的文件 */ private String currentFile = ""; /** * 保存未上傳的錄音文件 */ private SharedPreferences unUploadFile; private String dirPath = ""; private boolean isRecording = false; @Override protected void onServiceConnected() { Log.i(TAG, "onServiceConnected"); Toast.makeText(getApplicationContext(), "自動(dòng)錄音服務(wù)已啟動(dòng)", 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(); //設(shè)置撥號(hào)廣播過濾 intentFilter.addAction("android.intent.action.NEW_OUTGOING_CALL"); registerReceiver(outCallReceiver, intentFilter); //注冊(cè)撥號(hào)廣播接收器 networkConnectChangedReceiver = new NetworkConnectChangedReceiver(); intentFilter2 = new IntentFilter(); //設(shè)置網(wǎng)絡(luò)狀態(tài)改變廣播過濾 intentFilter2.addAction("android.net.conn.CONNECTIVITY_CHANGE"); intentFilter2.addAction("android.net.wifi.WIFI_STATE_CHANGED"); intentFilter2.addAction("android.net.wifi.STATE_CHANGE"); //注冊(cè)網(wǎng)絡(luò)狀態(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(), "進(jìn)程被關(guān)閉,無法繼續(xù)錄音,請(qǐng)打開錄音服務(wù)", 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("電話", "通話結(jié)束,停止錄音"); uploadFile(currentFile); } isRecording = false; break; case TelephonyManager.CALL_STATE_RINGING: Log.d(TAG1, "來電響鈴" + incomingNumber); // 進(jìn)行初始化 break; case TelephonyManager.CALL_STATE_OFFHOOK: Log.d(TAG1, "摘機(jī)" + (!incomingNumber.equals("") ? incomingNumber : currentCallNum)); initRecord(!incomingNumber.equals("") ? incomingNumber : currentCallNum); // 開始錄音 if (recorder != null) { recorder.start(); isRecording = true; } default: break; } super.onCallStateChanged(state, incomingNumber); } } /** * 當(dāng)錄音結(jié)束后,自動(dòng)上傳錄音文件。 * ①網(wǎng)絡(luò)可用:直接上傳; * ②網(wǎng)絡(luò)不可用:保存文件路徑,待網(wǎng)絡(luò)可用的時(shí)候再進(jìn)行上傳; * ③上傳失敗的文件,也保存文件路徑,或者重新上傳。 */ 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(); } /** * 上傳因?yàn)榫W(wǎng)絡(luò)或者其他原因,暫未上傳或者上傳失敗的文件,重新上傳 */ public void uploadUnUploadedFiles() { //獲取當(dāng)前還未上傳的文件,并把這些文件上傳 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); } }); } /** * 初始化錄音機(jī),并給錄音文件重命名 * * @param incomingNumber 通話號(hào)碼 */ 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);// 設(shè)置輸出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);// 設(shè)置音頻編碼格式 try { recorder.prepare();// 做好準(zhǔn)備 } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 獲取錄音文件的名稱 * * @param incomingNumber 通話號(hào)碼 * @return 獲取錄音文件的名稱 */ private String getFileName(String incomingNumber) { Date date = new Date(System.currentTimeMillis()); currentFile = incomingNumber + " " + dateFormat.format(date) + ".mp3"; return currentFile; } /** * 撥號(hào)廣播接收器,并獲取撥號(hào)號(hào)碼 */ public class OutCallReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Log.d(TAG1, "當(dāng)前手機(jī)撥打了電話:" + currentCallNum); if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) { currentCallNum = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); Log.d(TAG1, "當(dāng)前手機(jī)撥打了電話:" + currentCallNum); } else { Log.d(TAG1, "有電話,快接聽電話"); } } } /** * 網(wǎng)絡(luò)狀態(tài)change廣播接收器 */ public class NetworkConnectChangedReceiver extends BroadcastReceiver { private static final String TAG = "network status"; @Override public void onReceive(Context context, Intent intent) { /** * 這個(gè)監(jiān)聽網(wǎng)絡(luò)連接的設(shè)置,包括wifi和移動(dòng)數(shù)據(jù)的打開和關(guān)閉。. * 最好用的還是這個(gè)監(jiān)聽。wifi如果打開,關(guān)閉,以及連接上可用的連接都會(huì)接到監(jiān)聽。見log * 這個(gè)廣播的最大弊端是比上邊兩個(gè)廣播的反應(yīng)要慢,如果只是要監(jiān)聽wifi,我覺得還是用上邊兩個(gè)配合比較合適 */ 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()) { //當(dāng)前網(wǎng)絡(luò)可用 if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) { // connected to wifi Log.e(TAG, "當(dāng)前WiFi連接可用 "); } else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) { // connected to the mobile provider's data plan Log.e(TAG, "當(dāng)前移動(dòng)網(wǎng)絡(luò)連接可用 "); } uploadUnUploadedFiles(); } else { Log.e(TAG, "當(dāng)前沒有網(wǎng)絡(luò)連接,請(qǐng)確保你已經(jīng)打開網(wǎng)絡(luò) "); } } else { // not connected to the internet Log.e(TAG, "當(dāng)前沒有網(wǎng)絡(luò)連接,請(qǐng)確保你已經(jīng)打開網(wǎng)絡(luò) "); } } } } }
/** * 獲取網(wǎng)絡(luò)連接狀態(tài)工具類 * Created by wang.ao in 2017/2/24. */ public class NetWorkUtils { /** * 判斷是否有網(wǎng)絡(luò)連接 * @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)絡(luò)是否可用 * @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)絡(luò)是否可用 * @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; } /** * 獲取當(dāng)前網(wǎng)絡(luò)連接的類型信息 * @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; } /** * 獲取當(dāng)前的網(wǎng)絡(luò)狀態(tài) :沒有網(wǎng)絡(luò)0:WIFI網(wǎng)絡(luò)1:3G網(wǎng)絡(luò)2:2G網(wǎng)絡(luò)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 只要目錄中有一個(gè)文件壓縮失敗,就停止并返回 */ 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 只要待壓縮的文件有一個(gè)壓縮失敗就停止壓縮并返回(等價(jià)于windows上直接進(jìn)行壓縮) */ 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ā)生錯(cuò)誤,就立即停止并返回(等價(jià)于windows上直接進(jìn)行解壓) */ 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; } }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android音頻錄制MediaRecorder之簡(jiǎn)易的錄音軟件實(shí)現(xiàn)代碼
- Android簡(jiǎn)單的利用MediaRecorder進(jìn)行錄音的實(shí)例代碼
- Android應(yīng)用開發(fā):電話監(jiān)聽和錄音代碼示例
- Android App調(diào)用MediaRecorder實(shí)現(xiàn)錄音功能的實(shí)例
- Android實(shí)現(xiàn)錄音方法(仿微信語音、麥克風(fēng)錄音、發(fā)送語音、解決5.0以上BUG)
- Android實(shí)現(xiàn)錄音功能實(shí)現(xiàn)實(shí)例(MediaRecorder)
- Android實(shí)現(xiàn)語音播放與錄音功能
- Android實(shí)現(xiàn)通話自動(dòng)錄音
- android MediaRecorder實(shí)現(xiàn)錄屏?xí)r帶錄音功能
- Android仿微信語音對(duì)講錄音功能
相關(guān)文章
Android 應(yīng)用中跳轉(zhuǎn)到應(yīng)用市場(chǎng)評(píng)分示例
本篇文章主要介紹了Android 應(yīng)用中跳轉(zhuǎn)到應(yīng)用市場(chǎng)評(píng)分示例,非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2017-02-02Android實(shí)現(xiàn)調(diào)用系統(tǒng)相冊(cè)和拍照的Demo示例
這篇文章主要介紹了Android實(shí)現(xiàn)調(diào)用系統(tǒng)相冊(cè)和拍照的Demo示例,實(shí)例分析了Android調(diào)用系統(tǒng)相冊(cè)及拍照的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10Android項(xiàng)目開發(fā) 教你實(shí)現(xiàn)Periscope點(diǎn)贊效果
這篇文章主要為大家分享了Android項(xiàng)目開發(fā),一步一步教你實(shí)現(xiàn)Periscope點(diǎn)贊效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2015-12-12Android使用socket進(jìn)行二進(jìn)制流數(shù)據(jù)傳輸
這篇文章主要介紹了Android使用socket進(jìn)行二進(jìn)制流數(shù)據(jù)傳輸,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-04-04Android AutoCompleteTextView自動(dòng)提示文本框?qū)嵗a
這篇文章主要介紹了Android AutoCompleteTextView自動(dòng)提示文本框?qū)嵗a的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07Android自定義View實(shí)現(xiàn)拖拽效果
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)拖拽效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11快速了解Android?Room使用細(xì)則進(jìn)階
這篇文章主要為大家介紹了快速了解Android?Room使用細(xì)則進(jìn)階,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03將替代ListView的RecyclerView 的使用詳解(一)
這篇文章主要介紹了將替代ListView的RecyclerView 的使用詳解(一)的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07Android Studio不能獲取遠(yuǎn)程依賴包的完美解決方法
這篇文章主要介紹了Android Studio不能獲取遠(yuǎn)程依賴包的解決方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-11-11