1.前言
媒體播放是Android開發(fā)中常見的功能符喝,就拿視頻來說招狸,通常都是基于封裝好的框架做二次開發(fā)兄世,主要任務(wù)是界面的展示和用戶交互啼辣,無需考慮視頻是否支持,資源如何讀取御滩,保持屏幕常亮和防止設(shè)備休眠等鸥拧。這樣適合快速開發(fā),但那些不以視頻為主要業(yè)務(wù)的怎么辦艾恼?要知道成熟的視頻框架體積都不小住涉,殺雞焉用宰牛刀。
其實Android系統(tǒng)已經(jīng)支持大部分的媒體資源钠绍,對于播放要求不高的舆声,可以直接使用自帶的API開發(fā),還避免設(shè)備不兼容問題。
2.媒體概念
媒體是指傳播信息的媒介媳握,在Android設(shè)備上包括圖片碱屁、音頻和視頻。拆文解字一下蛾找,有三個關(guān)鍵點:信息娩脾、媒介和傳播,對設(shè)備而言就是內(nèi)容編解碼打毛、容器格式和網(wǎng)絡(luò)協(xié)議柿赊。為什么要說這些孩革,因為平時大家所接觸到的資源文件就是為了能廣泛地被使用而基于這三點設(shè)計出來的廉油,是能否在設(shè)備上播放和如何播放的決定因素,像PNG烫饼、MP3熬甫、MP4都是文件類型胰挑,可以認(rèn)為是方便大家去稱呼的。
為了容易理解椿肩,舉個例子吧瞻颂。南方潮濕、北方干燥郑象,想把空氣中的水汽由南運(yùn)到北贡这,并沿途分發(fā)水資源,怎么辦:
辦法 | 水汽 | 媒體 |
---|---|---|
減少體積厂榛,方便運(yùn)輸 | 對水汽壓縮形成水 | 使用算法將信息編碼成更少的字節(jié) |
使用容器裝載 | 不同材質(zhì)藕坯、不同形狀的杯子 | 不同文件類型所包含的不同后綴名的文件 |
動態(tài)調(diào)整使用的資源 | 根據(jù)當(dāng)?shù)厝彼闆r分發(fā) | 根據(jù)網(wǎng)絡(luò)情況切換碼率和緩沖大小 |
知道了媒體的組成,也就能理解官網(wǎng)指南中給出的支持媒體格式表中數(shù)據(jù)的含義了噪沙。大家可以看看,知道什么情況下不用引入框架吐根。
3.API及權(quán)限
在Android系統(tǒng)中播放音頻和視頻主要是以下兩個類:
- MediaPlayer正歼,播放音頻和視頻的邏輯由它實現(xiàn)。
- AudioManager拷橘,管理音頻資源和在設(shè)備上的輸出局义。
當(dāng)播放的資源來自網(wǎng)絡(luò)中的數(shù)據(jù)流時,添加網(wǎng)絡(luò)訪問權(quán)限:
<uses-permission android:name="android.permission.INTERNET" />
當(dāng)需要播放時屏幕常亮或阻止設(shè)備休眠時冗疮,添加喚醒鎖權(quán)限:
<uses-permission android:name="android.permission.WAKE_LOCK" />
MediaPlayer.setScreenOnWhilePlaying(); // 若是視頻萄唇,保持屏幕常量
4.播放流程
4.1.播放不同的來源
使用MediaPlayer可以簡單完成獲取、解碼和播放操作术幔。不過根據(jù)資源來源不同另萤,寫法略有不同。
- 本地資源(Raw資源)
MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you
- 內(nèi)部URIs(設(shè)備文件系統(tǒng),可通過Content Resolver獲得)
Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();
- 網(wǎng)絡(luò)URLs(數(shù)據(jù)流)
// 由于緩存需要四敞,數(shù)據(jù)源必須支持邊下邊播
String url = "http://........"; // your URL here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();
4.2.異步準(zhǔn)備資源
資源的解碼是比較耗時的泛源,尤其是來自網(wǎng)絡(luò)的視頻,緩沖不穩(wěn)定且解碼量大忿危。不建議將 prepare() 方法放在UI線程中處理达箍,因為阻塞超過十分之一秒就會引起用戶反感。系統(tǒng)提供另一個方法 prepareAsync() 來替代它铺厨, 先通過 setOnPreparedListener() 方法給MediaPlayer設(shè)置監(jiān)聽缎玫,
當(dāng)資源準(zhǔn)備完成后將回調(diào) MediaPlayer.OnPreparedListener 接口的 onPrepared() 方法,執(zhí)行你寫的播放邏輯解滓。
4.3.管理播放狀態(tài)
MediaPlayer的播放流程嚴(yán)格按照當(dāng)前的狀態(tài)一步一步執(zhí)行赃磨,只要不對就會報錯,請記住下面的圖伐蒂。
4.4.釋放資源
媒體播放是很耗資源的煞躬,當(dāng)不用時,記得通過以下代碼釋放它:
mediaPlayer.release();
mediaPlayer = null;
根據(jù)情況在 onStart()逸邦、onResume()恩沛、onRestart() 方法中創(chuàng)建,在 onStop()缕减、onDestory() 方法中釋放雷客。有種情況得注意,當(dāng)改變手機(jī)方向或者配置時會重啟界面桥狡,若不及時釋放搅裙,幾次就會占滿資源,詳見 Handling Runtime Changes裹芝。
5.后臺播放
關(guān)于這塊其實邏輯比較固定部逮,系統(tǒng)提供了MediaXXXCompat系列API幫助快速實現(xiàn),詳見huyongl1989的文章嫂易。那自己如何實現(xiàn)呢兄朋?
5.1.消息監(jiān)聽
直接將代碼移到Service中,不過需要注意的是Service默認(rèn)和Activity一樣運(yùn)行在主線程怜械,對于耗時操作也一樣得異步處理颅和。
public class MyService extends Service implements MediaPlayer.OnPreparedListener {
private static final String ACTION_PLAY = "com.example.action.PLAY";
MediaPlayer mMediaPlayer = null;
public int onStartCommand(Intent intent, int flags, int startId) {
...
if (intent.getAction().equals(ACTION_PLAY)) {
mMediaPlayer = ... // initialize it here
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.prepareAsync(); // prepare async to not block main thread
}
}
/** Called when MediaPlayer is ready */
public void onPrepared(MediaPlayer player) {
player.start();
}
}
和異步準(zhǔn)備資源一樣,對于錯誤也要異步通知缕允,不過監(jiān)聽器改為 MediaPlayer.OnErrorListener峡扩。
public class MyService extends Service implements MediaPlayer.OnErrorListener {
MediaPlayer mMediaPlayer;
public void initMediaPlayer() {
// ...initialize the MediaPlayer here...
mMediaPlayer.setOnErrorListener(this);
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// ... react appropriately ...
// The MediaPlayer has moved to the Error state, must be reset!
}
}
由于播放控制涉及Activity和Service的交互,不熟的看看圣騎士Wind的博客障本。
5.2.喚醒鎖
當(dāng)應(yīng)用程序在后臺播放時教届,設(shè)備仍可能進(jìn)入休眠,而系統(tǒng)此狀態(tài)下為了省電,嘗試關(guān)閉任何非必需功能巍佑,包括CPU和WiFi茴迁。所以得通過喚醒鎖告訴系統(tǒng)別休眠,但是會明顯減少電池使用時間萤衰,建議只在必需情況下使用堕义。相關(guān)代碼如下:
mMediaPlayer = new MediaPlayer();
// ... other initialization here ...
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK); // CPU不休眠
WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
wifiLock.acquire(); // WiFi不休眠
在MediaPlayer暫停或停止播放時脆栋,會自動釋放CPU鎖倦卖,不再需要WiFi功能時,得主動釋放鎖椿争,wifiLock.release()怕膛。
5.3.執(zhí)行清理
垃圾回收機(jī)制只對內(nèi)存敏感,可能會花不短時間才能清理MediaPlayer秦踪,所以 onDestory() 方法中記得釋放褐捻。
public class MyService extends Service {
MediaPlayer mMediaPlayer;
// ...
@Override
public void onDestroy() {
if (mMediaPlayer != null) mMediaPlayer.release();
}
}
對于短時間的停止播放就別清理了,重建也是挺耗資源的椅邓。
6.通過ContentResolver檢索資源
設(shè)備的媒體庫中有許多資源柠逞,可以通過以下代碼檢索:
ContentResolver contentResolver = getContentResolver();
Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = contentResolver.query(uri, null, null, null, null);
if (cursor == null) {
// query failed, handle error.
} else if (!cursor.moveToFirst()) {
// no media on the device
} else {
int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);
int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);
do {
long thisId = cursor.getLong(idColumn);
String thisTitle = cursor.getString(titleColumn);
// ...process entry...
} while (cursor.moveToNext());
}
檢索到后,拿ID拼接URI景馁,進(jìn)行播放:
long id = /* retrieve it from somewhere */;
Uri contentUri = ContentUris.withAppendedId(
android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setDataSource(getApplicationContext(), contentUri);
// ...prepare and start...
7.總結(jié)
這里僅僅講了媒體的播放板壮,那如何展示呢?系統(tǒng)提供了VideoView來播放視頻合住,它繼承SurfaceView來保證高效地渲染影片绰精,內(nèi)部封裝了MediaPlayer來獲取資源并監(jiān)聽控制。大家有興趣可以看看透葛,便于理解視頻播放笨使。