实战中了解四大组件(至善影音一)

Android


介绍

最近写了一个影音播放器,既是为巩固自己的知识点,也是为了分享一下,如何将自己的基础体现在项目.进而提醒像我这样的初学者,不要太过执着于项目,打好基础才是根本,这个是关于影音播放器部分.
那么现在,就让我们来开始吧.
对于项目而言,我们在Java中学过,也经常听到过MVC三层架构,而我自己一般写的都是来自于MVC的三层架构的.

首先,我们作为开发人员,要很主观地学会跟着用户走,我们现在的APP打开的界面都是由Splash页面进入的,当然有些功能较多的APP第一次进入的时候是会走一个由几个页面组成的引导页.而对于今天这个项目,我则是让他由Splash进入.注意,我个人认为其实开发者也是客户,开发者应该自己要联系日常的APP,以及自己的习惯想法,按着每一个要求做,把开发分成不同模块,这样才不会思绪紊乱.
注:我不会在文中写入布局,因为布局除了自定义控件,其他的都是有自己的想法.

Splash页面:

注册清单文件

对于一个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添加到了主页面,完成主页面的布局.而对于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就可以了.

VideoPlay–>媒体影音播放器

对于这里,我们需要思考的问题就是,我们该如何进入这个页面,我们刚刚获取到的数据又怎么传递呢?
首先,我们要清楚对于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");            // 序列化对象写入文件-->序列化对象反序列化-->从文件中拿出来--->我们通过BundelvideoList = (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);            }

第一次写博客,感觉有点乱.下次会努力修正的.

更多相关文章

  1. 一句话锁定MySQL数据占用元凶
  2. 线程对象Android(安卓)开发之多线程处理、Handler 详解
  3. Android(安卓)设备唯一标识生成方式
  4. android远程控制(一)---从PC端写数据到android系统驱动让android系
  5. Tomcat Servlet 往外传输数据(对象 or list) 给 Android
  6. Android(安卓)适配器模式应用及设计原理
  7. Android初级教程:Android中解析方式之pull解析
  8. android 如何保留数据两位小数
  9. Android的存储系统—Vold与MountService分析(一)

随机推荐

  1. 如何在Android工程中导入其它工程作为引
  2. Android: Android学习的几点建议
  3. Android(安卓)初始化语言(Android(安卓)in
  4. Android(安卓)onTouchEvent, onClick及on
  5. Android配置蓝牙键值
  6. 在android4.0.4的SDK里裁剪APK,把不需要的
  7. Android(安卓)NDK编程入门笔记
  8. 谈Android手机客户端的适配测试
  9. [置顶] 2016这一年,回顾我们一起走过的"编
  10. Service 详解