Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 手把手教你做視頻播放器(二)

手把手教你做視頻播放器(二)

編輯:關於Android編程

第2節 獲取視頻信息

要知道設備上有哪些可以被播放的視頻文件,一般來講有兩個方法,

遍歷設備磁盤上所有的目錄,根據文件的後綴名,把這些目錄中所有的視頻文件都找出來; 向安卓系統提供的Media Provider提出查詢請求,從而獲取我們希望的視頻文件信息;

從“減小開發難度,利用安卓系統自身功能,選擇最簡單的方案”的角度出發,我們采用Media Provider

2.1 ContentProvider

ContentProvider是安卓系統的四大組件之一,為別的組件(Activity、Service)“提供內容”。它就像是一個擁有某種數據的網站,安卓系統運行的其它組件可以通過“網址”訪問這個網站,獲取需要查詢的數據。

ContentProvider可以是私有的,只能為它所在的應用提供數據訪問請求; ContentProvider也可以是公開的,為別的應用程序提供數據訪問請求。
當我們自己開發一個ContentProvider的時候,就要在應用的AndroidManifest.xml文件中聲明它的存在,


        

      -->false為私有的,true為公開的

從這裡也可以看出,它和Activity的地位是一樣的,所以與Activity一樣並稱為安卓系統的四大組件之一。

2.2 系統級的ContentProvider

安卓系統上,有一個叫做Media ProviderContentProvider。它作為系統級別的應用程序在系統上運行,專門負責收集多媒體文件(音頻、視頻、文件)相關的信息。

Media Provider在開機啟動後,會在後台“監聽”磁盤上文件的變化,特定情況下,會自動更新多媒體文件的信息,例如磁盤上是否增加了媒體文件,是否被刪除了媒體文件,有的媒體文件名稱是否發生了修改等等。

所以當任何應用想獲取這類文件相關的信息時,就可以向Media Provider發起查詢的請求。Media Provider幫我們完成了視頻文件信息的收集,因此,我們就不用自己去遍歷磁盤上的文件進行視頻文件的收集和整理了。

除了Media Provider,系統還提供了

Contacts Provider:用來查詢聯系人信息; Calendar Provider:用來提供日歷相關信息的查詢; Bookmark Provider:用來提供書簽信息的查詢;


其它的就不再一一列出了。

2.3 使用Media Provider的缺點

使用Media Provider有一點需要注意,Media Provider對磁盤多媒體文件的“監控”,並不是實時的。當刪除磁盤上一個已有的視頻文件時,Media Provider並不會馬上知道,而是要等到下一次的掃描之後,才會更新這個信息。因此,如果使用Media Provider做為我們提供視頻信息的來源,就要考慮到“一個視頻剛好被修改了,但是還沒有來得及在Media Provider中更新信息”的情況。

2.4 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文件中,添加讀取外部存儲器的權限,



    
    ......

第3節 異步方式獲取視頻信息

獲取視頻信息所需要的時間是個不能確定的事情。如果視頻很少,也許幾十毫秒就能完成,如果視頻很多(比如幾十個),也許就要花二十多秒。

安卓應用只有一個主線程-各個組件都是在這個線程中運行。作為組件的之一的Activity就是在這個線程中更新應用界面的,例如,用戶點擊界面上的一個按鈕,按鈕得到響應,整個過程就是在這個主線程裡。所以這個主線程絕對不可以做耗時的操作。假如在按鈕中做了耗時的操作,那麼當它進行耗時操作的時候,你去點擊界面上的其它按鈕是不會有反應的,就好像程序凍在了那裡。

我們的代碼一旦連續占用這個線程超過一定的時間,系統就會彈出“程序無響應的”提示,這個提示叫做ANR-Applicatin No Response。

因此,我們可以考慮把獲取視頻信息的操作放到一個單獨的線程thread中進行。

這就好比你在正在做一件事情A,突然另一件事情B來打擾你,你不得不停下手頭的工作來完成,做完了才能繼續之前的工作;這時如果有另外一個人(另一個線程)來幫助你,把事情B全部包攬了,那你就不用分心了。當另一個人把事情B做完後,告訴你一聲就可以了。

3.1 異步操作

啟動一個新的線程,分擔耗時工作的方法是一種異步操作:我讓你幫我做一件事情,布置任務後,我就去做其他的事情了,等你做完了再告訴我結果;

與它對應的是同步操作:我讓你幫我做一件事情,布置任務後,我啥也不做,就等著你做完了告訴我結果;

獲取視頻信息是個異步操作,啟動一個新線程-工作線程thread-查詢視頻信息,查詢完成後,工作線程再將結果通知到主線程,讓主線程將查詢到結果的結果顯示到界面上。界面的更新一定要在主線程中進行,不能在別的線程修改,否則系統後提示運行錯誤,這一點相當重要。因此我們一定要將查詢的結果發送給主線程,讓主線程處理界面的更新。

3.2 異步操作的方案

安卓系統提供的異步操作方案有:

創建工作線程thread和Handler,利用Handler在工作線程和主線程之間傳遞數據; 使用AsyncTask幫助類,AsyncTask中封裝了工作線程,通過AsyncTask完成工作線程和主線程之間的數據傳遞;

這裡雖然將AsyncTask看成是一個單獨的方案,但實際上它也是通過方案1實現的,只不過對於使用者來講更加方便而已。

這裡我們選擇方案2。因為,

使用場景簡單,只是單個任務的異步操作,沒有多個線程之間的數據同步考慮; 使用方便,不用考慮太多的新線程創建的細節;

3.3 AsyncTask的使用

3.3.1 AsyncTask的用法

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);

3.3.2 獲取視頻信息的AsyncTask

根據我們的需要,自己定義個AsyncTaskVideoUpdateTask

不需要為新創建的線程傳入參數;所以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是一個合適的選擇。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved