編輯:關於Android編程
前些天分析了一下FM的流程以及主要類,接下來我們分析一下FM的錄音功能;
首先看下流程圖:
Fm錄音時,當點擊了錄音按鈕,會發一個廣播出去,源碼在FMRadioService.java中
public void startRecording() { Log.d(LOGTAG, "In startRecording of Recorder"); if ((true == mSingleRecordingInstanceSupported) && (true == mOverA2DP )) { Toast.makeText( this, "playback on BT in progress,can't record now", Toast.LENGTH_SHORT).show(); return; } sendRecordIntent(RECORD_START); }
State狀態控制FMRecordingService.java類service啟動與關閉
if (state == 1) {
Log.d(TAG,"FM ONintent received");
startService = true;
context.startService(in);
} elseif(state == 0){
Log.d(TAG,"FM OFFintent received");
startService = false;
context.stopService(in);
}
Fm廣播接收的action publicstatic final StringACTION_FM = "codeaurora.intent.action.FM";
當FMRadioservice類的private void sendRecordServiceIntent(int action)方法發送一個廣播並附帶一個開關錄音的狀態int值
private void sendRecordServiceIntent(int action) { Intent intent = new Intent(ACTION_FM); intent.putExtra("state", action); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); Log.d(LOGTAG, "Sending Recording intent for = " +action); getApplicationContext().sendBroadcast(intent); }
State狀態控制FMRecordingService.java類service啟動與關閉
public class FMRecordingReceiver extends BroadcastReceiver { private static final String TAG = "FMRecordingReceiver"; public static final String ACTION_FM = "codeaurora.intent.action.FM"; @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); Log.d(TAG, "Received intent: " + action); if((action != null) && action.equals(ACTION_FM)) { Log.d(TAG, "FM intent received"); Intent in = new Intent(); in.putExtras(intent); in.setClass(context, FMRecordingService.class); int state = intent.getIntExtra("state", 0); boolean startService = true; if (state == 1) {Log.d(TAG, "FM ON intent received"); startService = true; context.startService(in); } else if(state == 0){ Log.d(TAG, "FM OFF intent received"); startService = false; context.stopService(in); } } } }
Fm接收廣播的action
public static final String ACTION_FM_RECORDING = "codeaurora.intent.action.FM_Recording"; public static final String ACTION_FM_RECORDING_STATUS = "codeaurora.intent.action.FM.Recording.Status";
onCreat()方法裡注冊廣播接受機制,一個廣播是錄音狀態,一個是關閉fm狀態
public void onCreate() { super.onCreate(); Log.d(TAG, "FMRecording Service onCreate"); registerRecordingListner(); registerShutdownListner(); registerStorageMediaListener(); }
onDestroy()方法裡寫了卸載注冊停止錄音
public void onDestroy() { Log.d(TAG, "FMRecording Service onDestroy"); if (mFmRecordingOn == true) { Log.d(TAG, "Still recording on progress, Stoping it"); stopRecord(); } unregisterBroadCastReceiver(mFmRecordingReceiver); unregisterBroadCastReceiver(mFmShutdownReceiver); unregisterBroadCastReceiver(mSdcardUnmountReceiver); super.onDestroy(); }
stopRecord();停止錄音
private void stopRecord() { Log.d(TAG, "Enter stopRecord"); mFmRecordingOn = false; if (mRecorder == null) return; try { mRecorder.stop(); mRecorder.reset(); mRecorder.release(); mRecorder = null; } catch(Exception e) { e.printStackTrace(); } sendRecordingStatusIntent(STOP); saveFile(); stopForeground(true); stopClientStatusCheck(); }
registerShutdownListner();注冊接受方法中停止fm錄音
private void registerShutdownListner() { if (mFmShutdownReceiver == null) { mFmShutdownReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "Received intent " +intent); String action = intent.getAction(); Log.d(TAG, " action = " +action); if (action.equals("android.intent.action.ACTION_SHUTDOWN")) { Log.d(TAG, "android.intent.action.ACTION_SHUTDOWN Intent received"); stopRecord(); } } }; IntentFilter iFilter = new IntentFilter(); iFilter.addAction("android.intent.action.ACTION_SHUTDOWN"); registerReceiver(mFmShutdownReceiver, iFilter); } }
獲取sd卡有效空間
private static long getAvailableSpace() { String state = Environment.getExternalStorageState(); Log.d(TAG, "External storage state=" + state); if (Environment.MEDIA_CHECKING.equals(state)) { return PREPARING; } if (!Environment.MEDIA_MOUNTED.equals(state)) { return UNAVAILABLE; } try { File sampleDir = Environment.getExternalStorageDirectory(); StatFs stat = new StatFs(sampleDir.getAbsolutePath()); return stat.getAvailableBlocks() * (long) stat.getBlockSize(); } catch (Exception e) { Log.i(TAG, "Fail to access external storage", e); } return UNKNOWN_SIZE; }
主要通過以下方法實現:
FilesampleDir = Environment.getExternalStorageDirectory();
StatFs stat = newStatFs(sampleDir.getAbsolutePath());
return stat.getAvailableBlocks() *(long) stat.getBlockSize();
有四種值:
Environment.MEDIA_CHECKING檢查sd卡准備讀取
Environment.MEDIA_MOUNTED沒有sd卡
UNKNOWN_SIZE sd卡有效空間等於UNKNOWN_SIZE值
LOW_STORAGE_THRESHOLD低於存儲空間極限值
更新顯示存儲判斷提示方法
private boolean updateAndShowStorageHint() { mStorageSpace = getAvailableSpace(); return showStorageHint(); }
發送一個錄音狀態廣播
private void sendRecordingStatusIntent(int status) { Intent intent = new Intent(ACTION_FM_RECORDING_STATUS); intent.putExtra("state", status); Log.d(TAG, "posting intent for FM Recording status as = " +status); getApplicationContext().sendBroadcastAsUser(intent, UserHandle.ALL); }
getApplicationContext().sendBroadcastAsUser(intent,UserHandle.ALL);
UserHandle.ALL一個類的對象,USER_ALL= -1返回的一個字符串UserHandle{-1}
回調方法,去fmradioservice.java回調
mCallbacks.onRecordingStarted();方法
mCallbacks.onRecordingStopped();方法
public void registerFMRecordingStatus()
啟動錄音
private boolean startRecord() { Log.d(TAG, "Enter startRecord"); if (mRecorder != null) { /* Stop existing recording if any */ Log.d(TAG, "Stopping existing record"); try { mRecorder.stop(); mRecorder.reset(); mRecorder.release(); mRecorder = null; } catch(Exception e) { e.printStackTrace(); } } if (!updateAndShowStorageHint()) return false; long maxFileSize = mStorageSpace - LOW_STORAGE_THRESHOLD; mRecorder = new MediaRecorder(); try { mRecorder.setMaxFileSize(maxFileSize); if(mRecordDuration >= 0) mRecorder.setMaxDuration(mRecordDuration); } catch (RuntimeException exception) { } mSampleFile = null; File sampleDir; if((Environment.getExternalSDStorageState(this).equals(Environment.MEDIA_MOUNTED))){ sampleDir = new File(Environment.getExternalSDStorageDirectory(), "/FMRecording"); }else{ sampleDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() +"/FMRecording"); } if(!(sampleDir.mkdirs() || sampleDir.isDirectory())) return false; try { mSampleFile = File.createTempFile("FMRecording", ".3gpp", sampleDir); } catch (IOException e) { Log.e(TAG, "Not able to access SD Card"); Toast.makeText(this, "Not able to access SD Card", Toast.LENGTH_SHORT).show(); } try { Log.d(TAG, "AudioSource.FM_RX" +MediaRecorder.AudioSource.FM_RX); mRecorder.setAudioSource(MediaRecorder.AudioSource.FM_RX); mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); mAudioType = "audio/3gpp"; } catch (RuntimeException exception) { Log.d(TAG, "RuntimeException while settings"); mRecorder.reset(); mRecorder.release(); mRecorder = null; return false; } Log.d(TAG, "setOutputFile"); mRecorder.setOutputFile(mSampleFile.getAbsolutePath()); try {mRecorder.prepare(); Log.d(TAG, "start"); mRecorder.start(); } catch (IOException e) { Log.d(TAG, "IOException while start"); mRecorder.reset(); mRecorder.release(); mRecorder = null; return false; } catch (RuntimeException e) { Log.d(TAG, "RuntimeException while start"); mRecorder.reset(); mRecorder.release(); mRecorder = null; return false; } mFmRecordingOn = true; Log.d(TAG, "mSampleFile.getAbsolutePath() " +mSampleFile.getAbsolutePath()); mRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() { public void onInfo(MediaRecorder mr, int what, int extra) { if ((what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) || (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED)) { if (mFmRecordingOn) { Log.d(TAG, "Maximum file size/duration reached, stopping the recording"); stopRecord(); } if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) { // Show the toast. Toast.makeText(FMRecordingService.this, R.string.FMRecording_reach_size_limit, Toast.LENGTH_LONG).show(); } } } // from MediaRecorder.OnErrorListenerpublic void onError(MediaRecorder mr, int what, int extra) { Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra); if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) { // We may have run out of space on the sdcard. if (mFmRecordingOn) { stopRecord(); } updateAndShowStorageHint(); } } }); mSampleStart = System.currentTimeMillis(); sendRecordingStatusIntent(START); startNotification(); return true; }
錄音不為空先設置為停止錄音從新設置釋放資源
mRecorder!= null
mRecorder.stop();
mRecorder.reset();
mRecorder.release();
mRecorder = null;
錄音判斷存儲空間夠用不
if (!updateAndShowStorageHint())
return false;
錄音最大值為當前值前去低於存儲極限值。
longmaxFileSize = mStorageSpace - LOW_STORAGE_THRESHOLD;
設置錄音最大值
mRecorder.setMaxFileSize(maxFileSize);
設置錄音持續
mRecorder.setMaxDuration(mRecordDuration);
設置錄音來源,放置的位置,錄音audio格式,audio寫入音源編碼格式
mRecorder.setAudioSource(MediaRecorder.AudioSource.FM_RX);
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mRecorder.setOutputFile(mSampleFile.getAbsolutePath());
錄音監聽mediaRecorder監聽
mRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener()
what ==MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED
what ==MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED
mFmRecordingOn 為true 停止錄音
stopRecord();
錄音過程報錯停止錄音
stopRecord();彈出提示
updateAndShowStorageHint();
獲取當前時間,發送錄音狀態廣播,啟動通知
mSampleStart= System.currentTimeMillis();
sendRecordingStatusIntent(START);
startNotification();
發送通知,設置遠程控制,startForeground(102,status);設置sevice至於前台
private void startNotification() { RemoteViews views = new RemoteViews(getPackageName(), R.layout.record_status_bar); Notification status = new Notification(); status.contentView = views; status.flags |= Notification.FLAG_ONGOING_EVENT; status.icon = R.drawable.ic_menu_record; startForeground(102, status); }
停止錄音,保存錄音文件,停止錄音之前台,停止狀體切換
private void stopRecord()
sendRecordingStatusIntent(STOP);
saveFile();
stopForeground(true);
stopClientStatusCheck();
保存錄音方法(錄音一開錄,就在往默認內置T中寫數據以字節方式寫入數據)
private void saveFile() { int sampleLength = (int)((System.currentTimeMillis() - mSampleStart)/1000 ); Log.d(TAG, "Enter saveFile"); if (sampleLength == 0) return; String state = Environment.getExternalStorageState(); Log.d(TAG, "storage state is " + state); if (Environment.MEDIA_MOUNTED.equals(state)) { try { this.addToMediaDB(mSampleFile); Toast.makeText(this,getString(R.string.save_record_file, mSampleFile.getAbsolutePath( )), Toast.LENGTH_LONG).show(); } catch(Exception e) { e.printStackTrace(); } } else { Log.e(TAG, "SD card must have removed during recording. "); Toast.makeText(this, "Recording aborted", Toast.LENGTH_SHORT).show(); } return; }
獲取當時減去錄音起始時間如果為零就跳出保存方法不保存數據
intsampleLength = (int)((System.currentTimeMillis() - mSampleStart)/1000 );
Environment.MEDIA_MOUNTED.equals(state)sd卡可用就將錄音路徑添加到多媒體數據庫中
this.addToMediaDB(mSampleFile);
將信息添加到多媒體數據庫,音頻的audio格式存入數據庫中
private Uri addToMediaDB(File file) { Log.d(TAG, "In addToMediaDB"); Resources res = getResources(); ContentValues cv = new ContentValues(); long current = System.currentTimeMillis(); long modDate = file.lastModified(); Date date = new Date(current); SimpleDateFormat formatter = new SimpleDateFormat( res.getString(R.string.audio_db_title_format)); String title = formatter.format(date); // Lets label the recorded audio file as NON-MUSIC so that the file // won't be displayed automatically, except for in the playlist. cv.put(MediaStore.Audio.Media.IS_MUSIC, "1");cv.put(MediaStore.Audio.Media.TITLE, title); cv.put(MediaStore.Audio.Media.DATA, file.getAbsolutePath()); cv.put(MediaStore.Audio.Media.DATE_ADDED, (int) (current / 1000)); cv.put(MediaStore.Audio.Media.DATE_MODIFIED, (int) (modDate / 1000)); cv.put(MediaStore.Audio.Media.MIME_TYPE, mAudioType); cv.put(MediaStore.Audio.Media.ARTIST, res.getString(R.string.audio_db_artist_name)); cv.put(MediaStore.Audio.Media.ALBUM, res.getString(R.string.audio_db_album_name)); Log.d(TAG, "Inserting audio record: " + cv.toString());ContentResolver resolver = getContentResolver(); Uri base = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; Log.d(TAG, "ContentURI: " + base); Uri result = resolver.insert(base, cv); if (result == null) { Toast.makeText(this, R.string.unable_to_store, Toast.LENGTH_SHORT).show(); return null; } if (getPlaylistId(res) == -1) { createPlaylist(res, resolver); } int audioId = Integer.valueOf(result.getLastPathSegment()); addToPlaylist(resolver, audioId, getPlaylistId(res)); // Notify those applications such as Music listening to the // scanner events that a recorded audio file just created. sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result)); return result; }
獲取audio的uri
Uri base =MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
添加到播放列表
addToPlaylist(resolver,audioId, getPlaylistId(res));
可以存入music數據
cv.put(MediaStore.Audio.Media.IS_MUSIC,"1")
發送廣播掃描
sendBroadcast(newIntent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result));
privatevoid registerRecordingListner()廣播接收者
private void registerRecordingListner() { if (mFmRecordingReceiver == null) { mFmRecordingReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "Received intent " +intent); String action = intent.getAction(); Log.d(TAG, " action = " +action); if (action.equals(ACTION_FM_RECORDING)) { int state = intent.getIntExtra("state", STOP); Log.d(TAG, "ACTION_FM_RECORDING Intent received" + state); if (state == START) { Log.d(TAG, "Recording start"); mRecordDuration = intent.getIntExtra("record_duration", mRecordDuration); if(startRecord()) { clientProcessName = intent.getStringExtra("process_name"); clientPid = intent.getIntExtra("process_id", -1); startClientStatusCheck(); }} else if (state == STOP) { Log.d(TAG, "Stop recording"); stopRecord(); } } } }; IntentFilter iFilter = new IntentFilter(); iFilter.addAction(ACTION_FM_RECORDING); registerReceiver(mFmRecordingReceiver, iFilter); } }
廣播狀態為1的時候就開始錄音並檢查線程
private void startClientStatusCheck()
獲取客戶端所有進程信息進行匹配如果啟動的app沒有被殺死就繼續錄音否則停止
privateboolean getClientStatus(int pid, String processName)
ActivityManageractvityManager =
(ActivityManager)this.getSystemService(
this.ACTIVITY_SERVICE);
List
actvityManager.getRunningAppProcesses();
for(RunningAppProcessInfo procInfo :procInfos) {
if ((pid == procInfo.pid)
&&
(procInfo.processName.equals(processName))) {
status = true;
break;
}
}
procInfos.clear();
privateRunnable clientStatusCheckThread = new Runnable()
停止錄音後睡眠500毫秒
privatevoid stopClientStatusCheck()中斷線程mStatusCheckThread
private void stopClientStatusCheck() { if(mStatusCheckThread != null) { mStatusCheckThread.interrupt(); } }
首先明確一下概念,WebSocket協議是一種建立在TCP連接基礎上的全雙工通信的協議。概念強調了兩點內容:TCP基礎上 全雙工通信那麼什麼是全雙工通信呢? 全雙工就是指
開發項目過程中基本都會用到listView的下拉刷新和上滑加載更多,之前大家最常用的應該是pull to refresh或它的變種版吧,google官方在最新的andro
簡介現在在Android上加載圖片的框架都已經爛大街了,所以我們這裡也不說誰好誰壞,當然也不做比較了,因為得出的結果都是片面的,沒有誰好誰壞只有適不適合需求罷了起因是在泰
隨著app的迭代,嵌入的html5界面越來越多了,Webview這個強大組件引起的問題越發的多起來,例如: 1、WebView導致的oom問題 2、Android版本