編輯:關於Android編程
要知道設備上有哪些可以被播放的視頻文件,一般來講有兩個方法,
遍歷設備磁盤上所有的目錄,根據文件的後綴名,把這些目錄中所有的視頻文件都找出來; 向安卓系統提供的Media Provider
提出查詢請求,從而獲取我們希望的視頻文件信息;
從“減小開發難度,利用安卓系統自身功能,選擇最簡單的方案”的角度出發,我們采用Media Provider
。
ContentProvider
是安卓系統的四大組件之一,為別的組件(Activity、Service)“提供內容”。它就像是一個擁有某種數據的網站,安卓系統運行的其它組件可以通過“網址”訪問這個網站,獲取需要查詢的數據。
ContentProvider
可以是私有的,只能為它所在的應用提供數據訪問請求; ContentProvider
也可以是公開的,為別的應用程序提供數據訪問請求。ContentProvider
的時候,就要在應用的AndroidManifest.xml
文件中聲明它的存在,
-->false為私有的,true為公開的
從這裡也可以看出,它和Activity的地位是一樣的,所以與Activity一樣並稱為安卓系統的四大組件之一。
安卓系統上,有一個叫做Media Provider
的ContentProvider
。它作為系統級別的應用程序在系統上運行,專門負責收集多媒體文件(音頻、視頻、文件)相關的信息。
Media Provider
在開機啟動後,會在後台“監聽”磁盤上文件的變化,特定情況下,會自動更新多媒體文件的信息,例如磁盤上是否增加了媒體文件,是否被刪除了媒體文件,有的媒體文件名稱是否發生了修改等等。
所以當任何應用想獲取這類文件相關的信息時,就可以向Media Provider
發起查詢的請求。Media Provider
幫我們完成了視頻文件信息的收集,因此,我們就不用自己去遍歷磁盤上的文件進行視頻文件的收集和整理了。
除了Media Provider
,系統還提供了
Contacts Provider
:用來查詢聯系人信息; Calendar Provider
:用來提供日歷相關信息的查詢; Bookmark Provider
:用來提供書簽信息的查詢;
…
其它的就不再一一列出了。
Media Provider
的缺點使用Media Provider
有一點需要注意,Media Provider
對磁盤多媒體文件的“監控”,並不是實時的。當刪除磁盤上一個已有的視頻文件時,Media Provider
並不會馬上知道,而是要等到下一次的掃描之後,才會更新這個信息。因此,如果使用Media Provider
做為我們提供視頻信息的來源,就要考慮到“一個視頻剛好被修改了,但是還沒有來得及在Media Provider
中更新信息”的情況。
Media Provider
查詢視頻文件確定向Media Provider
發出查詢請求的地址-uri,它就像訪問網站時,要輸入的網址一樣。系統提供了兩個位置的uri,一個是指向內部存儲的uri,一個是指向外部存儲的uri。我們要查詢的視頻文件都是存放在外部存儲地址上的;
Uri uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
確定要請求的視頻文件信息。在視頻列表中,我們需要展示視頻的標題、創建時間,還需要播放它時使用的文件所在地址。這些信息在Media Provider
中都對應著查詢它們使用的字段名稱;
String[] searchKey = new String[] {
MediaStore.Video.Media.TITLE, -->對應文件的標題
MediaStore.Images.Media.DATA, -->對應文件的存放位置
MediaStore.Images.Media.DATE_ADDED -->對應文件的創建時間
};
確定查詢的條件。我們之前假設過只關心那些叫做Video
的目錄。因此我們要確定的只是查詢到的文件路徑中,包含有/Video
這個字段。
String where = MediaStore.Video.Media.DATA + " like \"%"+"/Video"+"%\"";
這個條件參數的寫法就和SQL
數據庫語言的語法一樣。這裡我們不打算講SQL
語法,只要知道在我們這個例子中這樣使用就好了;
設定查詢結果的排序方式,使用默認的排序方式就可以了,
String sortOrder = MediaStore.Video.Media.DEFAULT_SORT_ORDER;
獲取ContentResolver對象,讓它使用前面的參數向Media Provider
發起查詢請求;查詢的結果存放在Cursor
--指標當中;
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(
uri,
searchKey,
where,
null,
sortOrder);
遍歷Cursor
,得到它指向的每一條查詢到的信息;當Cursor
指向某條數據的時候,我們就獲取它攜帶的每個字段的值;
while(cursor.moveToNext())
{
String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA));
String name = cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.TITLE));
String createdTime = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_ADDED));
......
}
存放獲取的視頻文件信息,創建一個VideoItem類
,
public class VideoItem {
String name;
String path;
Bitmap thumb;
String createdTime;
VideoItem(String strPath, String strName, String createdTime) {
this.path = strPath;
this.name = strName;
......
}
}
將視頻文件創建的時間從Unix格式的時間戳,轉換成“年月日分”這種可讀的形勢,
VideoItem(String strPath, String strName, String createdTime) {
......
SimpleDateFormat sf = new SimpleDateFormat("yy年MM月dd日HH時mm分");
Date d = new Date(Long.valueOf(createdTime)*1000);
this.createdTime = sf.format(d);
}
獲取視頻文件的縮略圖,Android SDK提供了一個利用視頻文件地址獲取視頻縮略圖的工具,用起來非常簡單的,通過它將得到縮略圖的Bitmap;
VideoItem(String strPath, String strName, String createdTime) {
......
this.thumb = ThumbnailUtils.createVideoThumbnail(path, MediaStore.Images.Thumbnails.MINI_KIND);
}
圖片生成的尺寸可以通過第二個參數設置,MINI_KIND
表示小的縮略圖;FULL_SCREEN_KIND
表示大尺寸的縮略圖;MICRO_KIND
表示超小圖的縮略圖。這裡我們采用的是MINI_KIND
;
Cursor
使用完了之後要把它關閉掉,
“`java
cursor.close();
整理一下前面的各個步驟,獲取外部存儲上Video目錄
中所有視頻文件的方式如下,
Uri uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
String[] searchKey = new String[] {
MediaStore.Video.Media.TITLE,
MediaStore.Images.Media.DATA,
MediaStore.Images.Media.DATE_ADDED
};
String [] keywords = null;
String where = MediaStore.Video.Media.DATA + " like \"%"+"/Video"+"%\"";
String sortOrder = MediaStore.Video.Media.DEFAULT_SORT_ORDER;
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(
uri,
searchKey,
where,
keywords,
sortOrder);
if(cursor != null)
{
while(cursor.moveToNext())
{
String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA));
String name = cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.TITLE));
String createdTime = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_ADDED));
VideoItem item = new VideoItem(path, name, createdTime);
......
}
cursor.close();
}
最後一點千萬不要忘記,要在應用的AndroidManifest.xml
文件中,添加讀取外部存儲器的權限,
......
獲取視頻信息所需要的時間是個不能確定的事情。如果視頻很少,也許幾十毫秒就能完成,如果視頻很多(比如幾十個),也許就要花二十多秒。
安卓應用只有一個主線程-各個組件都是在這個線程中運行。作為組件的之一的Activity就是在這個線程中更新應用界面的,例如,用戶點擊界面上的一個按鈕,按鈕得到響應,整個過程就是在這個主線程裡。所以這個主線程絕對不可以做耗時的操作。假如在按鈕中做了耗時的操作,那麼當它進行耗時操作的時候,你去點擊界面上的其它按鈕是不會有反應的,就好像程序凍在了那裡。
我們的代碼一旦連續占用這個線程超過一定的時間,系統就會彈出“程序無響應的”提示,這個提示叫做ANR
-Applicatin No Response。
因此,我們可以考慮把獲取視頻信息的操作放到一個單獨的線程thread中進行。
這就好比你在正在做一件事情A,突然另一件事情B來打擾你,你不得不停下手頭的工作來完成,做完了才能繼續之前的工作;這時如果有另外一個人(另一個線程)來幫助你,把事情B全部包攬了,那你就不用分心了。當另一個人把事情B做完後,告訴你一聲就可以了。
啟動一個新的線程,分擔耗時工作的方法是一種異步操作:我讓你幫我做一件事情,布置任務後,我就去做其他的事情了,等你做完了再告訴我結果;
與它對應的是同步操作:我讓你幫我做一件事情,布置任務後,我啥也不做,就等著你做完了告訴我結果;
獲取視頻信息是個異步操作,啟動一個新線程-工作線程thread-查詢視頻信息,查詢完成後,工作線程再將結果通知到主線程,讓主線程將查詢到結果的結果顯示到界面上。界面的更新一定要在主線程中進行,不能在別的線程修改,否則系統後提示運行錯誤,這一點相當重要。因此我們一定要將查詢的結果發送給主線程,讓主線程處理界面的更新。
安卓系統提供的異步操作方案有:
創建工作線程thread和Handler,利用Handler在工作線程和主線程之間傳遞數據; 使用AsyncTask幫助類,AsyncTask中封裝了工作線程,通過AsyncTask完成工作線程和主線程之間的數據傳遞;這裡雖然將AsyncTask看成是一個單獨的方案,但實際上它也是通過方案1
實現的,只不過對於使用者來講更加方便而已。
這裡我們選擇方案2
。因為,
AsyncTask需要被繼承成為一個新的子類來使用,在被繼承時,要指定三種參數的類型-Param
Progress
Result
,還需要實現doInBackground(Param...)
函數,此外通常還要實現onProgressUpdate(Progress...)
onPostExecute(Result)
兩個回調函數。
class MyTask extends AsyncTask {
@Override
protected Result doInBackground(Param... params) {
return result;
}
@Override
protected void onProgressUpdate(Progress... progresses) {
}
@Override
protected void onPostExecute(Result result) {
}
@Override
protected void onCancelled() {
}
}
doInBackground(Param... params)
函數:傳入參數的Param
類型就是AsyncTask
中指定的Param
類型。它運行在新創建的工作線程當中。
使用MyTask
時,要在主線程中使用excute()
方法傳入不定長參數,讓Task
運行起來,
MyTask task = new MyTask();
task.excute(param0, param1, ..., paramN);
不定長參數會以數組的形式傳遞到doInBackground()
函數當中,
@Override
protected Result doInBackground(Param... params) {
Param param0 = params[0];
Param param1 = params[1];
......
Param paramN = params[N];
return result;
}
onProgressUpdate(Progress... progresses)
函數:傳入參數的Progress
類型就是AsyncTask
中指定的Progress
類型。
在doInBackground()
中執行的是一個很耗時的工作,有時需要向主線程報告當前的運行狀況,這就要使用到publishProgress()
函數,publishProgress()
也是使用的不定長參數,
@Override
protected Result doInBackground(Param... params) {
......
publishProgress(progress1, progress2, ..., progressN)
return result;
}
不定長參數會以數組的形式傳遞到onProgressUpdate()
函數當中,
@Override
protected void onProgressUpdate(Progress... progresses) {
Progress progress0 = progresses[0];
Progress progress1 = progresses[1];
......
Progress progressN = progresses[N];
}
onPostExecute(Result result)
函數:傳入參數的Result
類型就是AsyncTask
中指定的Result
類型。
doInBackground()
函數返回的類型也是Result
@Override
protected Result doInBackground(Param... params) {
......
return result;
}
返回的結果作為參數傳遞給onPostExecute()
函數,
@Override
protected void onPostExecute(Result result) {
}
onCancel()
函數會在調用者取消AsyncTask
的工作的時候被觸發。
要取消AsyncTask
的工作,首先要在主線程中調用cancel()
方法,
task.cancel(true);
因為在doInBackground()
中執行的是一個很耗時的工作,需要時不時的檢查自己是否被取消執行了,
@Override
protected Result doInBackground(Param... params) {
......
if(isCancelled())
{
......
return result;
}
......
return result;
}
最後,onCancelled()
函數會被觸發,這個函數會在主線程中被執行,
@Override
protected void onCancelled() {
}
綜合上面的分析,自定義一個AsyncTask
的方法如下,
class MyTask extends AsyncTask {
@Override
protected Result doInBackground(Param... params) {
Param param0 = params[0];
Param param1 = params[1];
......
Param paramN = params[N];
while(!isCancelled())
{
......
publishProgress(progress1, progress2, ..., progressN);
}
return result;
}
@Override
protected void onProgressUpdate(Progress... progresses) {
Progress progress0 = progresses[0];
Progress progress1 = progresses[1];
......
Progress progressN = progresses[N];
......
}
@Override
protected void onPostExecute(Result result) {
}
@Override
protected void onCancelled() {
}
}
使用一個AsyncTask
的方法如下,
MyTask task = new MyTask();
task.excute(param0, param1, ..., paramN);
......
task.cancel(true);
根據我們的需要,自己定義個AsyncTask
-VideoUpdateTask
,
Param
設置成Object
; 因為查詢的過程很長,所以需要時不時通知主線程查詢的狀態,每查詢到一條,就將視頻數據傳遞給主線程;所以Progress
設置成VideoItem
; 查詢的結果已經在查詢的過程中發送給了主線程,全部完成後,不需要再傳遞什麼結果給主線程了,所以Result
設置成Void
; 將查詢視頻信息的操作放到doInBackground()
中進行,這是一個新創建的工作線程; 工作線程中,每發現一個視頻,就通知給主線程;
class VideoUpdateTask extends AsyncTask
在視頻列表Activity創建的時候,啟動VideoUpdateTask
,開始查詢符合我們要求的視頻信息。
private AsyncTask mVideoUpdateTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_list);
mVideoUpdateTask = new VideoUpdateTask();
mVideoUpdateTask.execute();
}
在視頻列表Activity退出的時候,判斷VideoUpdateTask
是否還在運行,如果還在運行,就讓它停止,
@Override
protected void onDestroy() {
super.onDestroy();
if((mVideoUpdateTask != null) &&
(mVideoUpdateTask.getStatus() == AsyncTask.Status.RUNNING))
{
mVideoUpdateTask.cancel(true);
}
mVideoUpdateTask = null;
}
onCreate()
與onDestroy()
是Activity生命周期的一部分,當一個Activity被創建的時候會調用到onCreate()
,當Activity被退出銷毀的時候會調用到onDestroy()
。所以在這兩個地方使用VideoUpdateTask
是一個合適的選擇。
在平時工作中,camera模塊是經常進行調試修改的模塊,所以熟悉camera的工作流程以及工作原理將會大大的提供工作效率,但對於整個android系統camera是個十分
1、程序運行效果圖 二、代碼實現 1、main.xml 2、tab1.xml、tab2.xm
實現功能:歌曲下載完成後通知主界面更新本地音樂除了下面說明的一個問題,還有一些BUG有待修復,後續博文將會繼續更新//DownloadDialogFragment回傳的是
前言:在上篇中,分析了MediaPlayer的從創建到setDataSource過程,盡管看了代碼,但是沒有從MediaPlayer生態上認識各類庫之間依賴調用關系,在本