第2节 获取视频信息

要知道设备上有哪些可以被播放的视频文件,一般来讲有两个方法,

  1. 遍历设备磁盘上所有的目录,根据文件的后缀名,把这些目录中所有的视频文件都找出来;
  2. 向安卓系统提供的Media Provider提出查询请求,从而获取我们希望的视频文件信息;

从“减小开发难度,利用安卓系统自身功能,选择最简单的方案”的角度出发,我们采用Media Provider

2.1 ContentProvider

ContentProvider是安卓系统的四大组件之一,为别的组件(Activity、Service)“提供内容”。它就像是一个拥有某种数据的网站,安卓系统运行的其它组件可以通过“网址”访问这个网站,获取需要查询的数据。

  1. ContentProvider可以是私有的,只能为它所在的应用提供数据访问请求;
  2. ContentProvider也可以是公开的,为别的应用程序提供数据访问请求。
    当我们自己开发一个ContentProvider的时候,就要在应用的AndroidManifest.xml文件中声明它的存在,
<application
        android:allowBackup="true"....../><activity android:name=".MyActivity" /><!--声明MyContentProvider的存在--><provider
            android:name=".MyContentProvider"android:authorities="com.anddle.videoplayer"android:exported="false"/>  -->false为私有的,true为公开的</application>

从这里也可以看出,它和Activity的地位是一样的,所以与Activity一样并称为安卓系统的四大组件之一。

2.2 系统级的ContentProvider

安卓系统上,有一个叫做Media ProviderContentProvider。它作为系统级别的应用程序在系统上运行,专门负责收集多媒体文件(音频、视频、文件)相关的信息。

Media Provider在开机启动后,会在后台“监听”磁盘上文件的变化,特定情况下,会自动更新多媒体文件的信息,例如磁盘上是否增加了媒体文件,是否被删除了媒体文件,有的媒体文件名称是否发生了修改等等。

所以当任何应用想获取这类文件相关的信息时,就可以向Media Provider发起查询的请求。Media Provider帮我们完成了视频文件信息的收集,因此,我们就不用自己去遍历磁盘上的文件进行视频文件的收集和整理了。

除了Media Provider,系统还提供了

  1. Contacts Provider:用来查询联系人信息;
  2. Calendar Provider:用来提供日历相关信息的查询;
  3. Bookmark Provider:用来提供书签信息的查询;


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

2.3 使用Media Provider的缺点

使用Media Provider有一点需要注意,Media Provider对磁盘多媒体文件的“监控”,并不是实时的。当删除磁盘上一个已有的视频文件时,Media Provider并不会马上知道,而是要等到下一次的扫描之后,才会更新这个信息。因此,如果使用Media Provider做为我们提供视频信息的来源,就要考虑到“一个视频刚好被修改了,但是还没有来得及在Media Provider中更新信息”的情况。

2.4 Media Provider查询视频文件

  1. 确定向Media Provider发出查询请求的地址-uri,它就像访问网站时,要输入的网址一样。系统提供了两个位置的uri,一个是指向内部存储的uri,一个是指向外部存储的uri。我们要查询的视频文件都是存放在外部存储地址上的;

    Uri uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
  2. 确定要请求的视频文件信息。在视频列表中,我们需要展示视频的标题、创建时间,还需要播放它时使用的文件所在地址。这些信息在Media Provider中都对应着查询它们使用的字段名称;

    String[] searchKey = new String[] {
    MediaStore.Video.Media.TITLE, -->对应文件的标题
    MediaStore.Images.Media.DATA, -->对应文件的存放位置
    MediaStore.Images.Media.DATE_ADDED -->对应文件的创建时间
    };
  3. 确定查询的条件。我们之前假设过只关心那些叫做Video的目录。因此我们要确定的只是查询到的文件路径中,包含有/Video这个字段。

    String where = MediaStore.Video.Media.DATA + " like \"%"+"/Video"+"%\"";

    这个条件参数的写法就和SQL数据库语言的语法一样。这里我们不打算讲SQL语法,只要知道在我们这个例子中这样使用就好了;

  4. 设定查询结果的排序方式,使用默认的排序方式就可以了,

    String sortOrder = MediaStore.Video.Media.DEFAULT_SORT_ORDER;
  5. 获取ContentResolver对象,让它使用前面的参数向Media Provider发起查询请求;查询的结果存放在Cursor--指标当中;

    ContentResolver resolver = getContentResolver();
    Cursor cursor = resolver.query(uri,searchKey, where, null, sortOrder);
  6. 遍历Cursor,得到它指向的每一条查询到的信息;当Cursor指向某条数据的时候,我们就获取它携带的每个字段的值;

    while(cursor.moveToNext())
    {//获取视频的存放路径String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA));//获取视频的标题String name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.TITLE));//获取视频的创建时间String createdTime = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_ADDED));......
    }
  7. 存放获取的视频文件信息,创建一个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时间戳,就是一个类似于1464152901这样的数字。它是从1970年1月1日0时开始,所经过的秒数。
    所以要将视频文件创建的时间从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);
    }

    图片生成的尺寸可以通过第二个参数设置,

    1. MINI_KIND表示小的缩略图;
    2. FULL_SCREEN_KIND表示大尺寸的缩略图;
    3. MICRO_KIND表示超小图的缩略图;

    这里我们采用的是MINI_KIND

  8. Cursor使用完了之后要把它关闭掉,

    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.getColumnIndexOrThrow(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文件中,添加读取外部存储器的权限,

<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.anddle.anddleplayer"><!--添加读取存储器的权限--><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />......</manifest>

/*******************************************************************/
* 版权声明
* 本教程只在CSDN和安豆网发布,其他网站出现本教程均属侵权。
/*******************************************************************/

第3节 异步方式获取视频信息

获取视频信息所需要的时间是个不能确定的事情。如果视频很少,也许几十毫秒就能完成,如果视频很多(比如几十个),也许就要花二十多秒。

安卓应用只有一个主线程-各个组件都是在这个线程中运行。作为组件的之一的Activity就是在这个线程中更新应用界面的,例如,用户点击界面上的一个按钮,按钮得到响应,整个过程就是在这个主线程里。所以这个主线程绝对不可以做耗时的操作。假如在按钮中做了耗时的操作,那么当它进行耗时操作的时候,你去点击界面上的其它按钮是不会有反应的,就好像程序冻在了那里。

我们的代码一旦连续占用这个线程超过一定的时间,系统就会弹出“程序无响应的”提示,这个提示叫做ANR-Applicatin No Response。

因此,我们可以考虑把获取视频信息的操作放到一个单独的线程thread中进行。

这就好比你在正在做一件事情A,突然另一件事情B来打扰你,你不得不停下手头的工作来完成,做完了才能继续之前的工作;这时如果有另外一个人(另一个线程)来帮助你,把事情B全部包揽了,那你就不用分心了。当另一个人把事情B做完后,告诉你一声就可以了。

3.1 异步操作

启动一个新的线程,分担耗时工作的方法是一种异步操作:我让你帮我做一件事情,布置任务后,我就去做其他的事情了,等你做完了再告诉我结果;

与它对应的是同步操作:我让你帮我做一件事情,布置任务后,我啥也不做,就等着你做完了告诉我结果;

获取视频信息是个异步操作,启动一个新线程-工作线程thread-查询视频信息,查询完成后,工作线程再将结果通知到主线程,让主线程将查询到结果的结果显示到界面上。界面的更新一定要在主线程中进行,不能在别的线程修改,否则系统后提示运行错误,这一点相当重要。因此我们一定要将查询的结果发送给主线程,让主线程处理界面的更新。

3.2 异步操作的方案

安卓系统提供的异步操作方案有:

  1. 创建工作线程thread和Handler,利用Handler在工作线程和主线程之间传递数据;
  2. 使用AsyncTask帮助类,AsyncTask中封装了工作线程,通过AsyncTask完成工作线程和主线程之间的数据传递;

这里虽然将AsyncTask看成是一个单独的方案,但实际上它也是通过方案1实现的,只不过对于使用者来讲更加方便而已。

这里我们选择方案2。因为,

  1. 使用场景简单,只是单个任务的异步操作,没有多个线程之间的数据同步考虑;
  2. 使用方便,不用考虑太多的新线程创建的细节;

3.3 AsyncTask的使用

3.3.1 AsyncTask的用法

AsyncTask需要被继承成为一个新的子类来使用,在被继承时,要指定三种参数的类型-Param Progress Result,还需要实现doInBackground(Param...)函数,此外通常还要实现onProgressUpdate(Progress...) onPostExecute(Result) 两个回调函数。

class MyTask  extends AsyncTask<Param, Progress, Result> {@Overrideprotected Result doInBackground(Param... params) {return result;}@Overrideprotected void onProgressUpdate(Progress... progresses) {}@Overrideprotected void onPostExecute(Result result) {}@Overrideprotected void onCancelled() {}
}
  1. doInBackground(Param... params)函数:传入参数的Param类型就是AsyncTask<Param, Progress, Result>中指定的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;
    }
  2. onProgressUpdate(Progress... progresses)函数:传入参数的Progress类型就是AsyncTask<Param, Progress, Result>中指定的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];}
  3. onPostExecute(Result result)函数:传入参数的Result类型就是AsyncTask<Param, Progress, Result>中指定的Result类型。

    doInBackground()函数返回的类型也是Result

    @Override
    protected Result doInBackground(Param... params) {......return result;
    }

    返回的结果作为参数传递给onPostExecute()函数,

    @Override
    protected void onPostExecute(Result result) {}
  4. 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<Param, Progress, Result> {@Overrideprotected Result doInBackground(Param... params) {Param param0 = params[0];Param param1 = params[1];......Param paramN = params[N];while(!isCancelled()){......publishProgress(progress1, progress2, ..., progressN);}return result;}@Overrideprotected void onProgressUpdate(Progress... progresses) {Progress progress0 = progresses[0];Progress progress1 = progresses[1];......Progress progressN = progresses[N];......}@Overrideprotected void onPostExecute(Result result) {}@Overrideprotected void onCancelled() {}
}

使用一个AsyncTask的方法如下,

MyTask task = new MyTask();
task.excute(param0, param1, ..., paramN);
......
task.cancel(true);

3.3.2 获取视频信息的AsyncTask

根据我们的需要,自己定义个AsyncTaskVideoUpdateTask

  1. 不需要为新创建的线程传入参数;所以Param设置成Object
  2. 因为查询的过程很长,所以需要时不时通知主线程查询的状态,每查询到一条,就将视频数据传递给主线程;所以Progress设置成VideoItem
  3. 查询的结果已经在查询的过程中发送给了主线程,全部完成后,不需要再传递什么结果给主线程了,所以Result设置成Void
  4. 将查询视频信息的操作放到doInBackground()中进行,这是一个新创建的工作线程;
  5. 工作线程中,每发现一个视频,就通知给主线程;
class VideoUpdateTask extends AsyncTask<Object, VideoItem, Void> {@Overrideprotected Void doInBackground(Object... params) {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() && ! isCancelled()){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);publishProgress(item);  }cursor.close();}return null;}@Overrideprotected void onProgressUpdate(VideoItem... progresses) {VideoItem item = (VideoItem) progresses[0];//更新界面    ......}@Overrideprotected void onPostExecute(Result result) {//更新界面......}@Overrideprotected void onCancelled() {//更新界面......}
}

在视频列表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是一个合适的选择。


/*******************************************************************/
* 版权声明
* 本教程只在CSDN和安豆网发布,其他网站出现本教程均属侵权。

*另外,我们还推出了Arduino智能硬件相关的教程,您可以在我们的网店跟我学Arduino编程中购买相关硬件。同时也感谢大家对我们这些码农的支持。

*最后再次感谢各位读者对安豆的支持,谢谢:)
/*******************************************************************/

手把手教你做视频播放器(二)-获取视频信息相关推荐

  1. 手把手教你做视频播放器(六)-竖屏的播放界面

    第7节 竖屏的播放界面 播放视频的功能放在一个单独的Activity当中.我们将为它们设置横竖屏两种布局. 在竖屏的时候,上半部分播放视频,下半部分显示视频信息: 在设备旋转成横屏的时候,视频进行全屏 ...

  2. 手把手教你做视频播放器(四)-刷新与停止刷新列表

    第5节 刷新与停止刷新列表 虽然经过我们的假设,忽略了很多不需要关注的视频文件,但设备上依然有可能有很多的满足了我们假设条件的视频存在,这时就需要一个"取消刷新"的功能. 如果视频 ...

  3. 手把手教你使用Python网络爬虫获取音效信息

    点击上方"Python爬虫与数据挖掘",进行关注 回复"书籍"即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 一腔热血勤珍重,洒去犹能化碧涛. ...

  4. 手把手教你使用Python网络爬虫获取招聘信息

    1.前言 现在在疫情阶段,想找一份不错的工作变得更为困难,很多人会选择去网上看招聘信息.可是招聘信息有一些是错综复杂的.而且不能把全部的信息全部罗列出来,以外卖的58招聘网站来看,资料整理的不清晰. ...

  5. 手把手教你使用Python网络爬虫获取菜谱信息

    /1 前言/ 在放假时 ,经常想尝试一下自己做饭,下厨房这个网址是个不错的选择. 下厨房是必选的网址之一,主要提供各种美食做法以及烹饪技巧.包含种类很多. 今天教大家去爬取下厨房的菜谱 ,保存在wor ...

  6. 手把手教你做音乐播放器(三)获取音乐信息

    第3节 获取音乐信息 在"视频播放器"的开发过程当中,我们已经学会了如何获取视频文件的信息: 定义一个视频信息的数据结构VideoItem: 自定义一个AnsycTask,在它的工 ...

  7. 手把手教你做树莓派魔镜-MagicMirror(二)-烧写系统卡

    本系列文章: 手把手教你做树莓派魔镜-MagicMirror(一)-准备工作 手把手教你做树莓派魔镜-MagicMirror(二)-烧写系统卡 手把手教你做树莓派魔镜-MagicMirror(三)-系 ...

  8. Blender图解教程:手把手教你做马里奥问号箱 二 强迫症修复版(附模型下载)

    看完 <Blender图解教程:手把手教你做马里奥问号箱>有强迫症同学反应对最后的结果不能忍.那么老王教大家修复一下.初学的同学可以通过这个例子进一步理解一下什么是UV. 修复前 如果你没 ...

  9. 手把手教你给 SSH 启用二次身份验证

    目前来说,二次验证是比较常用的安全手段,通过设置二次验证,就可以有效的避免账户密码的泄露导致的安全问题.因为,每次登陆前都需要获取一次性验证码,如果没有验证码的话就无法成功登陆. 1.安装 PAM 模 ...

  10. 手把手教你做短视频去水印微信小程序(2-首页)

    手把手教你做短视频去水印微信小程序系列教程(2-首页) 文章目录 手把手教你做短视频去水印微信小程序系列教程(2-首页) 前言 一.顶部banner 二.地址解析 1.整体代码 2. input框输入 ...

最新文章

  1. android界面数据存储,Android应用开发基础之数据存储和界面展现(二)
  2. HTML-语义类标签
  3. iview select 怎么清空_在使用iview时发现要先重置一下表单然后填写完后再重置可以清空Select多选框,否则清不掉,什么原因?...
  4. hystrix-dashboard
  5. 电脑连接电视方法详解_电脑如何连网?——校园宽带的连接方法(详解版)
  6. java服务注册中心有哪些_Spring Cloud服务注册中心简述
  7. 2016年10个重要的可视化发展
  8. [日志]家居清洁十大秘笈
  9. Java中使用Jedis操作Redis,java初级面试笔试题
  10. x12arima季节调整方法_你所不知道的秋冬季节养生要点,都在这里!
  11. 移动端H5 页面 input 获取焦点不灵敏
  12. UDSonCAN资料收集
  13. tomcat编码配置gbk_修改Tomcat编码方式的两种方法
  14. Understanding Deep Image Representations by Inverting Them
  15. morning 是字符串的内容变成good_小洁详解《R数据科学》--第十章 使用stringr处理字符串(上)...
  16. java中闰月_java 实现万年历
  17. 内存淘汰策略 删除策略
  18. Web of science以及中国知网学术论文爬取教程(附代码)
  19. 一键修复wpcap.dll文件丢失或出错
  20. JAVA要不要看源码_为什么要看源码、如何看源码,高手进阶必看

热门文章

  1. 【数据仓库】 BI 项目管理之角色和职责
  2. Java开发手册、帮助文档
  3. 【预测模型】基于粒子群算法优化DBN深度置信网络实现数据预测matlab代码
  4. 谷歌浏览器有哪些好用的屏蔽广告插件?怎么屏蔽烦人的百度广告?
  5. php require找不到文件,第一次运行Fatal error: require_once找不到文件
  6. windows命令查找程序_如何在Windows 10上查找和设置屏幕保护程序
  7. 学报格式和论文格式一样吗_求《浙江大学学报》的论文格式要求 - 论文投稿 - 小木虫 - 学术 科研 互动社区...
  8. RS485_PTZ_云台控制
  9. 中国象棋游戏设计与实现
  10. 基于SSM实现学生竞赛管理系统