打好Android基础,实战中运用基础
实战中了解四大组件(至善影音一)
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); }
第一次写博客,感觉有点乱.下次会努力修正的.
更多相关文章
- 一句话锁定MySQL数据占用元凶
- 线程对象Android(安卓)开发之多线程处理、Handler 详解
- Android(安卓)设备唯一标识生成方式
- android远程控制(一)---从PC端写数据到android系统驱动让android系
- Tomcat Servlet 往外传输数据(对象 or list) 给 Android
- Android(安卓)适配器模式应用及设计原理
- Android初级教程:Android中解析方式之pull解析
- android 如何保留数据两位小数
- Android的存储系统—Vold与MountService分析(一)