Android
最近写了一个影音播放器,既是为巩固自己的知识点,也是为了分享一下,如何将自己的基础体现在项目.进而提醒像我这样的初学者,不要太过执着于项目,打好基础才是根本,这个是关于影音播放器部分.
那么现在,就让我们来开始吧.
对于项目而言,我们在Java中学过,也经常听到过MVC三层架构,而我自己一般写的都是来自于MVC的三层架构的.
首先,我们作为开发人员,要很主观地学会跟着用户走,我们现在的APP打开的界面都是由Splash页面进入的,当然有些功能较多的APP第一次进入的时候是会走一个由几个页面组成的引导页.而对于今天这个项目,我则是让他由Splash进入.注意,我个人认为其实开发者也是客户,开发者应该自己要联系日常的APP,以及自己的习惯想法,按着每一个要求做,把开发分成不同模块,这样才不会思绪紊乱.
注:我不会在文中写入布局,因为布局除了自定义控件,其他的都是有自己的想法.
对于一个Activity,让他成为我们的用户打开的第一页面,我们应该要在AndroidManifest.xml,对我们的这个Activity进行声明.
<activity
android:name="ui.activity.SpalshActivity"
android:label="@string/title_activity_spalsh"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
intent-filter>
activity>
对于Splash页面,我这里要讲的知识点是联合Handler,Intent知识.
一般来说,Splash页面会跳转到下一个页面.这样就能使用我们Intent(意图).
Intent是各组件之间交互的一种重要方式,可以知名我们想要执行的动作.除了Activity之间的交互,服务,广播发送之间的问题也会涉及到Intent,当然Intent还能携带数据进行传递,当然还有隐式启动Intent.对于隐式启动,我认为是一种类似于过滤匹配的方法,有兴趣者可以自行查一下,这里没有用到就不过多讲解.
基本用法:
Intent intent=new Intent(A.this,B.class);
startActivity(intent);
但是大家不要以为这就完了,在很多情况下我们需要这个Splash进行待久一点,因为我们的数据读取或者是Activity初始化都需要时间.那么我们需要怎么做?
//把延时2秒利用Handler处理,其中为了避免过多点击导致多次进入开始Activity的逻辑,出现多次闪屏,所以做了一次判断.
private void delayEnterMainActivity(boolean isDelay){
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if(!hasEnterMain){
hasEnterMain = true;
startActivity(new Intent(SpalshActivity.this,MainActivity.class));
finish();
}
}
}, isDelay?2000:0);
}
private boolean hasEnterMain = false;
知识点:Handler(小秘书)
一般分为
handler.sendMessage();//消息发送,根据消息,执行相关代码
hanlder.post(r);//r是要执行的任务代码。意思就是说r的代码实际是在UI线程执行的。可以写更新UI的代码。(工作线程是不能更新UI的)
对于耗时操作大家都会放到子线程,而Android中为了保护主线程(UI线程),是不允许子线程进行UI操作,而我们有时候为了更新UI,就应该使用Handler(异步消息处理机制),后面还会涉及到具体用法,我这里就只是讲解我当前所用的
new Handler().postDelayed(Runnable r, time);
这是一个延时操作,记住我上面说的post是不会开启一个新的线程,UI更新还是在主线程中操作,所以尽量不要做耗时操作,这里我们是通过延时发送信息给主线程,让主线程去处理.
不知道大家对上面的讲解怎么样,其实Handler是一个面试常考的题目.我一般是这么认为的.
子线程Handler对象发送Message–>MessageQueue队列等待—>Looper在队列中找信息—>然后对应相对的消息分配给主线程—->主线程运行代码
而我对Splash之所以选出这个,是因为我看到了现在很多APP都是通过倒数5秒的做法来打广告,而我们就可以利用我们上面的做法,然后时间设定随意.
至于是否需要延迟,我们可以通过你在按钮中设定 这个是isDelay的值啊.
ViewPager+FragmentPagerAdapter+Fragment的组合
这是一个比较经典的组合,能够展示出我们日常看到的通过滑动获取到不同的Fragment,并且我们的Tab也会跟着走动.目前网上很多框架或者是写好的类都能直接引用做到这种效果,并且做得很好.
知识点:ViewPager
ViewPager:可以使视图滑动.
实现过程:
1.往布局文件添加
.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" >
.support.v4.view.ViewPager>
2.添加页卡
这里的页卡,我们使用的是Fragment(碎片),Fragment刚出来的时候是为了适应手机与平板,但是后来人们发现Fragment拥有自己的生命周期和接收、处理用户的事件,这样就不必在Activity写一堆控件的事件处理的代码了。更为重要的是,你可以动态的添加、替换和移除某个Fragment。这样Fragment在很多Activity实现比较麻烦的场合就利用了Fragment.
2.1 新建一个Fragment页卡继承Fragment–实现自己的布局
2.2 Activity继承FragmentActivity,相当于一个管理Fragment的容器,而我们在这里声明数组,用来添加页卡
//fragments是一个ArrayList数组.
fragments.add(new VideoListFragment());
fragments.add(new AudioListFragment());
adapter = new MyPagerAdapter(getSupportFragmentManager(), fragments);
并且将数据设定给Adapter,然后将适配器设置给ViewPager
2.3 其实Adapter并没有想象中麻烦,我们选择继承FragmentAdapter,并且实现其中的方法.
public class MyPagerAdapter extends FragmentPagerAdapter {
private ArrayList fragments;
public MyPagerAdapter(FragmentManager fm,ArrayList fragments) {
super(fm);
// TODO Auto-generated constructor stub
this.fragments=fragments;
}
@Override
public Fragment getItem(int position) {
// TODO Auto-generated method stub
return fragments.get(position);
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return fragments.size();
}
}
思路过程:
ViewPager–>Fragment作为对象,拥有自己布局,添加到数组–>数组传递到Adapter–>Adapter联合ViewPager
对于ViewPager:其实我们还能设定它的监听方法,通过这个方法就能获取到自己活动的位置,然后设定数据,进行操作了.
viewPager.addOnPageChangeListener();
上面说到的,是把两个Fragment添加到了主页面,完成主页面的布局.而对于Fragment的页面,我们还需要去写.
知识点:内容提供者的使用,异步数据查询(AsyncQueryHandler)
ListView:ListView+Adapter+Data
思路:
ListView:完成布局
Data:通过本机的内容提供者获取.
内容提供者:实现程序之间的数据共享功能,安卓自带的电话簿,短信,媒体库等程序都提供了类似的接口.
Fragment:getContext.getContentResolver()
Activity:getContentResolver();
这里,我还要介绍一个对应于内容提供者获得的数据的查询.
AsyncQueryHandler,这在数据量很小的时候是没有问题的,但是如果数据量大了,可能导致UI线程发生ANR异常(超过5秒)。
所以我们选择了
queryHandler =
new SimpleQueryHandler(getActivity().getContentResolver());
queryHandler.startQuery(0, adapter, Media.EXTERNAL_CONTENT_URI, projection, null, null, null);
//这里的SimpleQueryHandler是我自定义继承AsyncQueryHandler的一个类
而我们查询是使用startQuery的方法,而对应的会有onQueryComplete()的相应,通知查询完毕,一般利用onQueryComplete()中的方法进行对adapter的数据更新.
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
// TODO Auto-generated method stub
super.onQueryComplete(token, cookie, cursor);
//CursorUtil.printCursor(cursor);
if(cookie!=null && cookie instanceof CursorAdapter){
CursorAdapter adapter = (CursorAdapter) cookie;
adapter.changeCursor(cursor);// 相当于notifyDatesetChange
}
}
Adapter:VideoView继承CursorAdapter,实现方法
因为适配器需要我们使用数据对每一项进行填充,那么我们就需要数据.我们可以看到bindView中是存在cursor参数的,我们就可以利用这个才获取到我们的数据.
因为数据不是仅仅只有一个或者是一个数组,而是一个拥有属性的对象,所以我们需要使用bean包封装它的属性.而在bean包,我们创建一个静态方法,目的是为了从cursor中获取到数据填入我们的当前对象.
public static VideoItem fromCursor(Cursor cursor){
VideoItem videoItem = new VideoItem();
videoItem.setDuration(cursor.getLong(cursor.getColumnIndex(Media.DURATION)));
videoItem.setPath(cursor.getString(cursor.getColumnIndex(Media.DATA)));
videoItem.setSize(cursor.getLong(cursor.getColumnIndex(Media.SIZE)));
videoItem.setTitle(cursor.getString(cursor.getColumnIndex(Media.TITLE)));
return videoItem;
}
Adapter中获取VideoItem然后将数据传递给相应的控件对象,就能完成Adapter的每一个对象.
而后,我们只要将Fragment中的listView对象绑定Adapter就可以了.
对于这里,我们需要思考的问题就是,我们该如何进入这个页面,我们刚刚获取到的数据又怎么传递呢?
首先,我们要清楚对于ListView是存在一个setOnItemClickListener()方法的,里面包含了我们按得Item的位置,那么怎么利用这个posiiton来获取到我们的数据.
这里我们通过设定一个方法,将每一条游标记录逐渐加入到ArrayList中
/**
* 将cursor中的数据解析出并放入集合
* @param cursor
* @return
*/
private ArrayList cursorToList(Cursor cursor){
cursor.moveToPosition(-1);//将cursor移动到最初位置,否则获取到的数据很可能不全
ArrayList list = new ArrayList();
//遍历cursor的所有结果集,然后将每一条结果集转成VideoItem对象放入集合当中
while(cursor.moveToNext()){
list.add(VideoItem.fromCursor(cursor));
}
return list;
}
在ItemClick中运用Intent(bundle)传递数据:
//通过传递vedioList和position来完成 Item数据的传递
Cursor cursor = (Cursor) adapter.getItem(position);
ArrayList videoList = cursorToList(cursor);
Bundle bundle = new Bundle();
bundle.putInt("currentPosition", position);
bundle.putSerializable("videoList", videoList);
enterActivity(VideoPlayerActivity.class, bundle);
}//这里的enterActivity我是自己写的一个方法,里面包含有Intent.putExtras(bundle);
获取数据
currentPosition = getIntent().getExtras().getInt("currentPosition");
// 序列化对象写入文件-->序列化对象反序列化-->从文件中拿出来--->我们通过Bundel
videoList = (ArrayList) getIntent().getExtras().getSerializable("videoList");
上面出现过两个我们平时很少看到的
putSerializable和getSerializable,其中我们了解到一般的getExtra
而对于对象,我们一般有Serializable和Parcelable,这里我们就要使用到Serializable了.
在获取到数据之后,我们就可以利用获取到当前item.
VideoItem videoItem = videoList.get(position);
tv_name.setText(videoItem.getTitle());
videoView.setVideoURI(Uri.parse(videoItem.getPath()));
然后利用数据做一些设置,再利用VideoView的生命周期,去完成一些自定义的逻辑。
详情方法可以参考:
http://blog.csdn.net/xiaoqiang_0719/article/details/51361927
至于其中两个比较重要的逻辑,我还是需要讲一下。
1. 系统电量的更新 我们需要明白电量的实时变更是系统通过广播,我们需要注册广播接受者来接受.
// 注册广播接收者
private void registerBatteryBroadcastReceiver() {
filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
batteryBroadcastReceiver = new BatteryBroadcastReceiver();
registerReceiver(batteryBroadcastReceiver, filter);
}
其中我们这里的Filter(Action)–>类比于过滤器,就是为了系统能够将相应的广播匹配给我.一般我们还能够加入Filter,来添加过滤参数.注意这里的广播注册是动态的,有静态的注册是在xml声明.
上面注册广播的时候,我们就要写一个广播接收者继承BroadcastReceiver,实现方法:
onReceive()–>接受的是Intent–>Intent会携带你需要的数据的
方法内实现你需要的逻辑.
注销:我一般选择在onDestory()方法内.–>unregisterReceiver()
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
unregisterReceiver(batteryBroadcastReceiver);
}
系统时间的更新:
对于这种实时异步的操作,你是不是又想起了你的小助手Handler,好的.我们可以在这里使用到我们的Handler的常用方法.
1. 声明一个 Hanlder
2. 写一个方法,方法发送一段Message给Handler,然后让Handler去实现.
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
// 更新时间
case MESSAGE_UPDATE_TIME:
updateSystemTime();
break;
/**
* 更新系统时间
*/
private void updateSystemTime() {
tv_time.setText(StringUtil.formatSystemTime());
handler.sendEmptyMessageDelayed(MESSAGE_UPDATE_TIME, 1000);
}
以上就是最常用的Handler用法,他的原理还是我上面说的,我个人看来是不停发信息,不停地调用.
一般来说数据传到了位置,然后界面的逻辑是由你自己决定的,而其中的难点很多都在于你要异步处理更新,所以我上面也给出了Handler的解决方法.
其实,就算你完成了所有逻辑,我估计你播放视频还会出现Bug,因为你没有使用到解码器,谷歌的只是支持. 接下来:将会为大家介绍一款重头戏:Vitamio
Vitamio 是一款 Android 与 iOS平台上的全能多媒体开发框架,全面支持硬件解码与 GPU 渲染。
Vitamio 使用了 FFmpeg 做为媒体解析器和最主要的解码器
FFmpeg :c语音写的开源多媒体解码框架
使用方法:
1. GitHub上下载https://github.com/yixia
2. 导入项目
3. 作为依赖包
4. 然后你就可以建立一个VitamioVideoPlayer对其进行使用,将viewVideoPlayer放进去,然后我们就可以加入本来的Video出错就能跳转过来了.当然逻辑处理还是跟ViewVideoPlayer一样的.
跳转代码块:
videoView.setOnErrorListener(new OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
switch(what){
case MediaPlayer.MEDIA_ERROR_UNKNOWN:
Intent intent = new Intent(VideoPlayerActivity.this,VitamioPlayerActivity.class);
//LogUtil.e(this, "播放出错");
if(uri!=null){
intent.setData(uri);
}else{
Bundle bundle=new Bundle();
bundle.putInt("currentPosition", currentPosition);
bundle.putSerializable("videoList", videoList);
intent.putExtras(bundle);
}
第一次写博客,感觉有点乱.下次会努力修正的.