Android开发实践:自定义ViewGroup的onLayout()分析

Android开发中,对于自定义View,分为两种,一种 是自定义控件(继承View类),另一种是自定义布局容器(继承ViewGroup)。如果是自定义控件,则一般需要重载两个方法,一个是 onMeasure(),用来测量控件尺寸,另一个是onDraw(),用来绘制控件的UI。而自定义布局容器,则一般需要实现/重载三个方法,一个是 onMeasure(),也是用来测量尺寸;一个是onLayout(),用来布局子控件;还有一个是dispatchDraw(),用来绘制UI。 本文主要分析自定义ViewGroup的onLayout()方法的实现。 ViewGroup 类的onLayout()函数是abstract型,继承者必须实现,由于ViewGroup的定位就是一个容器,用来盛放子控件的,所以就必须定义要以 什么的方式来盛放,比如LinearLayout就是以横向或者纵向顺序存放,而RelativeLayout则以相对位置来摆放子控件,同样,我们的自 定义ViewGroup也必须给出我们期望的布局方式,而这个定义就通过onLayout()函数来实现。 我们通过实现一个水平优先布局的视图容器来更加深入地了解onLayout()的实现吧,效果如图所示(黑色方块为子控件,白色部分为自定义布局容器)。该容器的布局方式是,首先水平方向上摆放子控件,水平方向放不下了,则另起一行继续水平摆放。 ** 1. 自定义ViewGroup的派生类** 第一步,则是自定ViewGroup的派生类,继承默认的构造函数。 1 <div class="line number2 index1 alt1"> 2 </div> <div class="line number3 index2 alt2"> 3 </div> <div class="line number4 index3 alt1"> 4 </div> <div class="line number5 index4 alt2"> 5 </div> <div class="line number6 index5 alt1"> 6 </div> <div class="line number7 index6 alt2"> 7 </div> <div class="line number8 index7 alt1"> 8 </div> <div class="line number9 index8 alt2"> 9 </div> <div class="line number10 index9 alt1"> 10 </div> <div class="line number11 index10 alt2"> 11 </div> <div class="line number12 index11 alt1"> 12 </div> <div class="line number13 index12 alt2"> 13 </div> <div class="line number14 index13 alt1"> 14 </div> </td> <td class="code"> <div class="container"> <div class="line number1 index0 alt2"> `public` `class` `CustomViewGroup ``extends` `ViewGroup {` </div> <div class="line number2 index1 alt1"> ` ` </div> <div class="line number3 index2 alt2"> ` ``public` `CustomViewGroup(Context context) {` </div> <div class="line number4 index3 alt1"> ` ``super``(context); ` </div> <div class="line number5 index4 alt2"> ` ``}` </div> <div class="line number6 index5 alt1"> ` ` </div> <div class="line number7 index6 alt2"> ` ``public` `CustomViewGroup(Context context, AttributeSet attrs) {` </div> <div class="line number8 index7 alt1"> ` ``super``(context, attrs); ` </div> <div class="line number9 index8 alt2"> ` ``}` </div> <div class="line number10 index9 alt1"> ` ` </div> <div class="line number11 index10 alt2"> ` ``public` `CustomViewGroup(Context context, AttributeSet attrs, intdefStyle) {` </div> <div class="line number12 index11 alt1"> ` ``super``(context, attrs, defStyle);` </div> <div class="line number13 index12 alt2"> ` ``}` </div> <div class="line number14 index13 alt1"> `}` </div> </div> </td> </tr> </table> ...

2014年11月17日 · 13 分钟 · 天边的星星

android ListView 设置分割线 Divider

这是一个极其隐蔽的 BUG 首先需求是:用 ColorDrawable 设置 ListView 分割线 ``` listView.setDivider(new ColorDrawable(0xffd4d5d6)); ``` 这样原理上绝对说得过去,但是你怎么都看不到效果,为什么呢,看源码吧! ![复制代码](http://common.cnblogs.com/images/copycode.gif) public void setDivider(Drawable divider) { if (divider != null) { mDividerHeight = divider.getIntrinsicHeight(); } else { mDividerHeight = 0; } //.... ![复制代码](http://common.cnblogs.com/images/copycode.gif) mDividerHeight 是分割线的高度,我们再看 [Drawable](eclipse-javadoc:%E2%98%82=MopoGameLauncher/E:%5C/adt-bundle-windows-x86_64-20130219%5C/sdk%5C/platforms%5C/android-17%5C/android.jar%3Candroid.graphics.drawable(Drawable.class%E2%98%83Drawable).getIntrinsicHeight() ``` public int getIntrinsicHeight() { return -1; } ``` 返回 -1,而 ColorDrawable 并没有重载此方法! 有些人运气好,在后面加了一句 listView.setDividerHeight(2); 这样问题就解决了,但是当你把这句放前面,就不知道为什么了! 所以正确方法是: ``` listView.setDivider(new ColorDrawable(0xffd4d5d6)); listView.setDividerHeight(2); ```

2014年11月13日 · 1 分钟 · 天边的星星

Android Volley完全解析(一),初识Volley的基本用法

1. Volley简介 我们平时在开发Android应用的时候不可避免地都需要用到网络技术,而多数情况下应用程序都会使用HTTP协议来发送和接收网络数据。Android系统中主要提供了两种方式来进行HTTP通信,HttpURLConnection和HttpClient,几乎在任何项目的代码中我们都能看到这两个类的身影,使用率非常高。 不过HttpURLConnection和HttpClient的用法还是稍微有些复杂的,如果不进行适当封装的话,很容易就会写出不少重复代码。于是乎,一些Android网络通信框架也就应运而生,比如说AsyncHttpClient,它把HTTP所有的通信细节全部封装在了内部,我们只需要简单调用几行代码就可以完成通信操作了。再比如Universal-Image-Loader,它使得在界面上显示网络图片的操作变得极度简单,开发者不用关心如何从网络上获取图片,也不用关心开启线程、回收图片资源等细节,Universal-Image-Loader已经把一切都做好了。 Android开发团队也是意识到了有必要将HTTP的通信操作再进行简单化,于是在2013年Google I/O大会上推出了一个新的网络通信框架——Volley。Volley可是说是把AsyncHttpClient和Universal-Image-Loader的优点集于了一身,既可以像AsyncHttpClient一样非常简单地进行HTTP通信,也可以像Universal-Image-Loader一样轻松加载网络上的图片。除了简单易用之外,Volley在性能方面也进行了大幅度的调整,它的设计目标就是非常适合去进行数据量不大,但通信频繁的网络操作,而对于大数据量的网络操作,比如说下载文件等,Volley的表现就会非常糟糕。 下图所示的这些应用都是属于数据量不大,但网络通信频繁的,因此非常适合使用Volley。 2. 下载Volley 介绍了这么多理论的东西,下面我们就准备开始进行实战了,首先需要将Volley的jar包准备好,如果你的电脑上装有Git,可以使用如下命令下载Volley的源码: **[plain]** [view plain](http://blog.csdn.net/guolin_blog/article/details/17482095#)[copy](http://blog.csdn.net/guolin_blog/article/details/17482095#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/284210)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/284210/fork) <div style="color: #000000;"> </div> </div> - <span style="color: black;">git clone https://android.googlesource.com/platform/frameworks/volley </span> 下载完成后将它导入到你的Eclipse工程里,然后再导出一个jar包就可以了。如果你的电脑上没有Git,那么也可以直接使用我导出好的jar包,下载地址是:http://www.kwstu.com/ResourcesView/kwstu_201441183330928 。 新建一个Android项目,将volley.jar文件复制到libs目录下,这样准备工作就算是做好了。 3. StringRequest的用法 前面已经说过,Volley的用法非常简单,那么我们就从最基本的HTTP通信开始学习吧,即发起一条HTTP请求,然后接收HTTP响应。首先需要获取到一个RequestQueue对象,可以调用如下方法获取到: **[java]** [view plain](http://blog.csdn.net/guolin_blog/article/details/17482095#)[copy](http://blog.csdn.net/guolin_blog/article/details/17482095#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/284210)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/284210/fork) <div style="color: #000000;"> </div> </div> - <span style="color: black;">RequestQueue mQueue = Volley.newRequestQueue(context); </span> 注意这里拿到的RequestQueue是一个请求队列对象,它可以缓存所有的HTTP请求,然后按照一定的算法并发地发出这些请求。RequestQueue内部的设计就是非常合适高并发的,因此我们不必为每一次HTTP请求都创建一个RequestQueue对象,这是非常浪费资源的,基本上在每一个需要和网络交互的Activity中创建一个RequestQueue对象就足够了。 接下来为了要发出一条HTTP请求,我们还需要创建一个StringRequest对象,如下所示: **[java]** [view plain](http://blog.csdn.net/guolin_blog/article/details/17482095#)[copy](http://blog.csdn.net/guolin_blog/article/details/17482095#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/284210)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/284210/fork) <div style="color: #000000;"> </div> </div> - <span style="color: black;">StringRequest stringRequest = <span class="keyword" style="font-weight: bold; color: #006699;">new</span> StringRequest(<span class="string" style="color: blue;">&#8220;http://www.baidu.com&#8221;</span>, </span> - <span class="keyword" style="font-weight: bold; color: #006699;">new</span> Response.Listener<String>() { - <span style="color: black;"> <span class="annotation" style="color: #646464;">@Override</span> </span> - <span class="keyword" style="font-weight: bold; color: #006699;">public</span> <span class="keyword" style="font-weight: bold; color: #006699;">void</span> onResponse(String response) { - <span style="color: black;"> Log.d(<span class="string" style="color: blue;">&#8220;TAG&#8221;</span>, response); </span> - } - <span style="color: black;"> }, <span class="keyword" style="font-weight: bold; color: #006699;">new</span> Response.ErrorListener() { </span> - <span class="annotation" style="color: #646464;">@Override</span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: #006699;">public</span> <span class="keyword" style="font-weight: bold; color: #006699;">void</span> onErrorResponse(VolleyError error) { </span> - Log.e(<span class="string" style="color: blue;">&#8220;TAG&#8221;</span>, error.getMessage(), error); - <span style="color: black;"> } </span> - }); 可以看到,这里new出了一个StringRequest对象,StringRequest的构造函数需要传入三个参数,第一个参数就是目标服务器的URL地址,第二个参数是服务器响应成功的回调,第三个参数是服务器响应失败的回调。其中,目标服务器地址我们填写的是百度的首页,然后在响应成功的回调里打印出服务器返回的内容,在响应失败的回调里打印出失败的详细信息。 ...

2014年10月30日 · 3 分钟 · 天边的星星

算法整理(二)—快速排序的两种实现方式:双边扫描和单边扫描

首先简单谈下快速排序的特点,时间复杂度O(nLog n),最差时间复杂度O(n^2),平均时间O(nLog n).因为用到了函数栈,空间复杂度为O(lg n),最差为O(n).是一种不稳定的排序方法。基本思想是分治法,这位大大的http://blog.csdn.net/morewindows/article/details/6684558 讲的非常清楚了,分治法+挖坑法,我就不多说了。就是以某个数为参照,使得左边的都小于他,右边的数都大于他。然后对他的左右两个区间采取同样的方法进行递归。 就其整体实现而言,有两大种思路,一是双边扫描,二是单边扫描。下面分别来上程序: 一、双边扫描 双边扫描是谭浩强书中的方法,个人觉得比下面的单边扫描更好理解,也是博文里采用的方法。下面看程序: **[cpp]** [view plain](http://blog.csdn.net/yanzi1225627/article/details/36045001#)[copy](http://blog.csdn.net/yanzi1225627/article/details/36045001#)[print](http://blog.csdn.net/yanzi1225627/article/details/36045001#)[?](http://blog.csdn.net/yanzi1225627/article/details/36045001#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/412329)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/412329/fork) <div> </div> </div> - <span style="color: black;"><span style=<span class="string" style="color: red;">&#8220;font-family:Comic Sans MS;font-size:18px;&#8221;</span>><span class="keyword" style="font-weight: bold; color: blue;">void</span> quickSort1(<span class="datatypes" style="font-weight: bold; color: #2e8b57;">int</span>* x, <span class="datatypes" style="font-weight: bold; color: #2e8b57;">int</span> l, <span class="datatypes" style="font-weight: bold; color: #2e8b57;">int</span> r){ </span> - <span style="color: black;"> </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">if</span>(l < r){ </span> - <span style="color: black;"> <span class="datatypes" style="font-weight: bold; color: #2e8b57;">int</span> i = l, j = r, key = x[l]; </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">while</span>(i < j){ </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">while</span>( i < j && x[j] >= key){ </span> - <span style="color: black;"> j&#8211;; </span> - <span style="color: black;"> } </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">if</span>(i < j){ </span> - <span style="color: black;"> x[i++] = x[j]; </span> - <span style="color: black;"> } </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">while</span>(i < j && x[i] <= key){ </span> - <span style="color: black;"> i++; </span> - <span style="color: black;"> } </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">if</span>(i < j){ </span> - <span style="color: black;"> x[j&#8211;] = x[i]; </span> - <span style="color: black;"> } </span> - <span style="color: black;"> } </span> - <span style="color: black;"> cout<<<span class="string" style="color: red;">&#8220;i = &#8220;</span> <<i<<<span class="string" style="color: red;">&#8221; j = &#8220;</span><<j<<endl; </span> - <span style="color: black;"> x[i] = key; </span> - <span style="color: black;"> quickSort1(x, l, i-1); </span> - <span style="color: black;"> quickSort1(x, i+1, r); </span> - <span style="color: black;"> } </span> - <span style="color: black;"> </span> - <span style="color: black;">}</span> </span> 双边扫描非常直观,首先进到程序里判断是否l<r,当满足条件才进去。这也是用递归的一个必要条件,一定要让函数有尽头,有边界。然后进入大while循环,接着进入小while循环,先从右边找,只要满足数字大于key就一直让j往左移。直到第一个不满足条件的,就是第一个小于key的数跳出while循环,将它放在左边挖的“坑”上。同时让坑的索引+1,接着从左边开始扫描,找到第一个大于key的数,再将它填到右边的坑上。右边的坑索引-1,接着再从右边扫描。直到最后跳出大while循环,此时i = j。也就是完成了一次快速排序的扫描。之后将最初的key放到x[i],其实放到x[j]也是一样的。因为i等于j么,此时!然后进行递归,对区间[l, i – 1], [i+1, r]进行同样的操作。 ...

2014年10月27日 · 9 分钟 · 天边的星星

玩转Android Camera开发(三):国内首发—使用GLSurfaceView预览Camera 基础拍照demo

GLSurfaceView是OpenGL中的一个类,也是可以预览Camera的,而且在预览Camera上有其独到之处。独到之处在哪?当使用Surfaceview无能为力、痛不欲生时就只有使用GLSurfaceView了,它能够真正做到让Camera的数据和显示分离,所以搞明白了这个,像Camera只开预览不显示这都是小菜,妥妥的。Android4.0的自带Camera源码是用SurfaceView预览的,但到了4.2就换成了GLSurfaceView来预览。如今到了4.4又用了自家的TextureView,所以从中可以窥探出新增TextureView的用意。 虽说Android4.2的Camera源码是用GLSurfaceView预览的,但是进行了大量的封装又封装的,由于是OpenGL小白,真是看的不知所云。俺滴要求不高,只想弄个可拍照的摸清GLSurfaceView在预览Camera上的使用流程。经过一番百度一无所获,后来翻出去Google一大圈也没发现可用的。倒是很多人都在用GLSurfaceView和Surfaceview同时预览Camera,Surfaceview用来预览数据,在上面又铺了一层GLSurfaceView绘制一些信息。无奈自己摸索,整出来的是能拍照也能得到数据,但是界面上不是一块白板就是一块黑板啥都不显示。后来在stackoverflow终于找到了一个可用的链接,哈哈,苍天啊,终于柳暗花明了!参考此链接,自己又改改摸索了一天才彻底搞定。之所以费这么多时间是不明白OpenGL ES2.0的绘制基本流程,跟简单的OpenGL的绘制还是稍有区别。下面上源码: 一、CameraGLSurfaceView.java 此类继承GLSurfaceView,并实现了两个接口 **[java]** [view plain](http://blog.csdn.net/yanzi1225627/article/details/33339965#)[copy](http://blog.csdn.net/yanzi1225627/article/details/33339965#)[print](http://blog.csdn.net/yanzi1225627/article/details/33339965#)[?](http://blog.csdn.net/yanzi1225627/article/details/33339965#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/402612)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/402612/fork) <div> </div> </div> - <span style="color: black;"><span style=<span class="string" style="color: red;">&#8220;font-family:Comic Sans MS;font-size:18px;&#8221;</span>><span class="keyword" style="font-weight: bold; color: blue;">package</span> org.yanzi.camera.preview; </span> - <span style="color: black;"> </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> javax.microedition.khronos.egl.EGLConfig; </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> javax.microedition.khronos.opengles.GL10; </span> - <span style="color: black;"> </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> org.yanzi.camera.CameraInterface; </span> - <span style="color: black;"> </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> android.content.Context; </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> android.graphics.SurfaceTexture; </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> android.opengl.GLES11Ext; </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> android.opengl.GLES20; </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> android.opengl.GLSurfaceView; </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> android.opengl.GLSurfaceView.Renderer; </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> android.util.AttributeSet; </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> android.util.Log; </span> - <span style="color: black;"> </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">public</span> <span class="keyword" style="font-weight: bold; color: blue;">class</span> CameraGLSurfaceView <span class="keyword" style="font-weight: bold; color: blue;">extends</span> GLSurfaceView <span class="keyword" style="font-weight: bold; color: blue;">implements</span> Renderer, SurfaceTexture.OnFrameAvailableListener { </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">private</span> <span class="keyword" style="font-weight: bold; color: blue;">static</span> <span class="keyword" style="font-weight: bold; color: blue;">final</span> String TAG = <span class="string" style="color: red;">&#8220;yanzi&#8221;</span>; </span> - <span style="color: black;"> Context mContext; </span> - <span style="color: black;"> SurfaceTexture mSurface; </span> - <span style="color: black;"> DirectDrawer mDirectDrawer; </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">public</span> CameraGLSurfaceView(Context context, AttributeSet attrs) { </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">super</span>(context, attrs); </span> - <span style="color: black;"> <span class="comment" style="color: #008200;">// TODO Auto-generated constructor stub</span> </span> - <span style="color: black;"> mContext = context; </span> - <span style="color: black;"> setEGLContextClientVersion(<span class="number" style="color: #c00000;">2</span>); </span> - <span style="color: black;"> setRenderer(<span class="keyword" style="font-weight: bold; color: blue;">this</span>); </span> - <span style="color: black;"> setRenderMode(RENDERMODE_WHEN_DIRTY); </span> - <span style="color: black;"> } </span> - <span style="color: black;"> <span class="annotation" style="color: #646464;">@Override</span> </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">public</span> <span class="keyword" style="font-weight: bold; color: blue;">void</span> onSurfaceCreated(GL10 gl, EGLConfig config) { </span> - <span style="color: black;"> <span class="comment" style="color: #008200;">// TODO Auto-generated method stub</span> </span> - <span style="color: black;"> Log.i(TAG, <span class="string" style="color: red;">&#8220;onSurfaceCreated&#8230;&#8221;</span>); </span> - <span style="color: black;"> mTextureID = createTextureID(); </span> - <span style="color: black;"> mSurface = <span class="keyword" style="font-weight: bold; color: blue;">new</span> SurfaceTexture(mTextureID); </span> - <span style="color: black;"> mSurface.setOnFrameAvailableListener(<span class="keyword" style="font-weight: bold; color: blue;">this</span>); </span> - <span style="color: black;"> mDirectDrawer = <span class="keyword" style="font-weight: bold; color: blue;">new</span> DirectDrawer(mTextureID); </span> - <span style="color: black;"> CameraInterface.getInstance().doOpenCamera(<span class="keyword" style="font-weight: bold; color: blue;">null</span>); </span> - <span style="color: black;"> </span> - <span style="color: black;"> } </span> - <span style="color: black;"> <span class="annotation" style="color: #646464;">@Override</span> </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">public</span> <span class="keyword" style="font-weight: bold; color: blue;">void</span> onSurfaceChanged(GL10 gl, <span class="keyword" style="font-weight: bold; color: blue;">int</span> width, <span class="keyword" style="font-weight: bold; color: blue;">int</span> height) { </span> - <span style="color: black;"> <span class="comment" style="color: #008200;">// TODO Auto-generated method stub</span> </span> - <span style="color: black;"> Log.i(TAG, <span class="string" style="color: red;">&#8220;onSurfaceChanged&#8230;&#8221;</span>); </span> - <span style="color: black;"> GLES20.glViewport(<span class="number" style="color: #c00000;"></span>, <span class="number" style="color: #c00000;"></span>, width, height); </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">if</span>(!CameraInterface.getInstance().isPreviewing()){ </span> - <span style="color: black;"> CameraInterface.getInstance().doStartPreview(mSurface, <span class="number" style="color: #c00000;">1</span>.33f); </span> - <span style="color: black;"> } </span> - <span style="color: black;"> </span> - <span style="color: black;"> </span> - <span style="color: black;"> } </span> - <span style="color: black;"> <span class="annotation" style="color: #646464;">@Override</span> </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">public</span> <span class="keyword" style="font-weight: bold; color: blue;">void</span> onDrawFrame(GL10 gl) { </span> - <span style="color: black;"> <span class="comment" style="color: #008200;">// TODO Auto-generated method stub</span> </span> - <span style="color: black;"> Log.i(TAG, <span class="string" style="color: red;">&#8220;onDrawFrame&#8230;&#8221;</span>); </span> - <span style="color: black;"> GLES20.glClearColor(<span class="number" style="color: #c00000;">1</span>.0f, <span class="number" style="color: #c00000;">1</span>.0f, <span class="number" style="color: #c00000;">1</span>.0f, <span class="number" style="color: #c00000;">1</span>.0f); </span> - <span style="color: black;"> GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); </span> - <span style="color: black;"> mSurface.updateTexImage(); </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">float</span>[] mtx = <span class="keyword" style="font-weight: bold; color: blue;">new</span> <span class="keyword" style="font-weight: bold; color: blue;">float</span>[<span class="number" style="color: #c00000;">16</span>]; </span> - <span style="color: black;"> mSurface.getTransformMatrix(mtx); </span> - <span style="color: black;"> mDirectDrawer.draw(mtx); </span> - <span style="color: black;"> } </span> - <span style="color: black;"> </span> - <span style="color: black;"> <span class="annotation" style="color: #646464;">@Override</span> </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">public</span> <span class="keyword" style="font-weight: bold; color: blue;">void</span> onPause() { </span> - <span style="color: black;"> <span class="comment" style="color: #008200;">// TODO Auto-generated method stub</span> </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">super</span>.onPause(); </span> - <span style="color: black;"> CameraInterface.getInstance().doStopCamera(); </span> - <span style="color: black;"> } </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">private</span> <span class="keyword" style="font-weight: bold; color: blue;">int</span> createTextureID() </span> - <span style="color: black;"> { </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">int</span>[] texture = <span class="keyword" style="font-weight: bold; color: blue;">new</span> <span class="keyword" style="font-weight: bold; color: blue;">int</span>[<span class="number" style="color: #c00000;">1</span>]; </span> - <span style="color: black;"> </span> - <span style="color: black;"> GLES20.glGenTextures(<span class="number" style="color: #c00000;">1</span>, texture, <span class="number" style="color: #c00000;"></span>); </span> - <span style="color: black;"> GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[<span class="number" style="color: #c00000;"></span>]); </span> - <span style="color: black;"> GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, </span> - <span style="color: black;"> GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_LINEAR); </span> - <span style="color: black;"> GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, </span> - <span style="color: black;"> GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); </span> - <span style="color: black;"> GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, </span> - <span style="color: black;"> GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE); </span> - <span style="color: black;"> GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, </span> - <span style="color: black;"> GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE); </span> - <span style="color: black;"> </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">return</span> texture[<span class="number" style="color: #c00000;"></span>]; </span> - <span style="color: black;"> } </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">public</span> SurfaceTexture _getSurfaceTexture(){ </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">return</span> mSurface; </span> - <span style="color: black;"> } </span> - <span style="color: black;"> <span class="annotation" style="color: #646464;">@Override</span> </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">public</span> <span class="keyword" style="font-weight: bold; color: blue;">void</span> onFrameAvailable(SurfaceTexture surfaceTexture) { </span> - <span style="color: black;"> <span class="comment" style="color: #008200;">// TODO Auto-generated method stub</span> </span> - <span style="color: black;"> Log.i(TAG, <span class="string" style="color: red;">&#8220;onFrameAvailable&#8230;&#8221;</span>); </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">this</span>.requestRender(); </span> - <span style="color: black;"> } </span> - <span style="color: black;"> </span> - <span style="color: black;">} </span> - <span style="color: black;"></span> </span> 关于这个类进行简单说明: ...

2014年10月27日 · 16 分钟 · 天边的星星

玩转Android Camera开发(四):预览界面四周暗中间亮,只拍摄矩形区域图片(附完整源码)

杂家前文曾写过一篇关于只拍摄特定区域图片的demo,只是比较简陋,在坐标的换算上不是很严谨,而且没有完成预览界面四周暗中间亮的效果,深以为憾,今天把这个补齐了。 在上代码之前首先交代下,这里面存在着换算的两种模式。第一种,是以屏幕上的矩形区域为基准进行换算。举个例子,屏幕中间一个 矩形框为100dip*100dip.这里一定要使用dip为单位,否则在不同的手机上屏幕呈现的矩形框大小不一样。先将这个dip换算成px,然后根据屏幕的宽和高的像素计算出矩形区域,传给Surfaceview上铺的一层View,这里叫MaskView(蒙板),让MaskView进行绘制。然后拍照时,通过屏幕矩形框的大小和屏幕的大小与最终拍摄图片的PictureSize进行换算,得到图片里的矩形区域图片,然后截取保存。第二种模式是,预先知道想要的图片的长宽,如我就是想截400*400(单位为px)大小的图片。那就以此为基准,换算出屏幕上呈现的Rect的长宽,然后让MaskView绘制。究竟用哪一种模式,按需选择。本文以第一种模式示例。下面上代码: 在杂家的前文基础上进行封装,首先封装一个MaskView,用来绘制四周暗中间亮的效果,或者你可以加一个滚动条,这都不是事。 一、MaskView.java **[java]** [view plain](http://blog.csdn.net/yanzi1225627/article/details/34931759#)[copy](http://blog.csdn.net/yanzi1225627/article/details/34931759#)[print](http://blog.csdn.net/yanzi1225627/article/details/34931759#)[?](http://blog.csdn.net/yanzi1225627/article/details/34931759#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/407657)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/407657/fork) <div> </div> </div> - <span style="color: black;"><span style=<span class="string" style="color: red;">&#8220;font-family:Comic Sans MS;font-size:18px;&#8221;</span>><span class="keyword" style="font-weight: bold; color: blue;">package</span> org.yanzi.ui; </span> - <span style="color: black;"> </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> org.yanzi.util.DisplayUtil; </span> - <span style="color: black;"> </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> android.content.Context; </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> android.graphics.Canvas; </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> android.graphics.Color; </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> android.graphics.Paint; </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> android.graphics.Paint.Style; </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> android.graphics.Point; </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> android.graphics.Rect; </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> android.util.AttributeSet; </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> android.util.Log; </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">import</span> android.widget.ImageView; </span> - <span style="color: black;"> </span> - <span style="color: black;"><span class="keyword" style="font-weight: bold; color: blue;">public</span> <span class="keyword" style="font-weight: bold; color: blue;">class</span> MaskView <span class="keyword" style="font-weight: bold; color: blue;">extends</span> ImageView { </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">private</span> <span class="keyword" style="font-weight: bold; color: blue;">static</span> <span class="keyword" style="font-weight: bold; color: blue;">final</span> String TAG = <span class="string" style="color: red;">&#8220;YanZi&#8221;</span>; </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">private</span> Paint mLinePaint; </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">private</span> Paint mAreaPaint; </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">private</span> Rect mCenterRect = <span class="keyword" style="font-weight: bold; color: blue;">null</span>; </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">private</span> Context mContext; </span> - <span style="color: black;"> </span> - <span style="color: black;"> </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">public</span> MaskView(Context context, AttributeSet attrs) { </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">super</span>(context, attrs); </span> - <span style="color: black;"> <span class="comment" style="color: #008200;">// TODO Auto-generated constructor stub</span> </span> - <span style="color: black;"> initPaint(); </span> - <span style="color: black;"> mContext = context; </span> - <span style="color: black;"> Point p = DisplayUtil.getScreenMetrics(mContext); </span> - <span style="color: black;"> widthScreen = p.x; </span> - <span style="color: black;"> heightScreen = p.y; </span> - <span style="color: black;"> } </span> - <span style="color: black;"> </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">private</span> <span class="keyword" style="font-weight: bold; color: blue;">void</span> initPaint(){ </span> - <span style="color: black;"> <span class="comment" style="color: #008200;">//绘制中间透明区域矩形边界的Paint</span> </span> - <span style="color: black;"> mLinePaint = <span class="keyword" style="font-weight: bold; color: blue;">new</span> Paint(Paint.ANTI_ALIAS_FLAG); </span> - <span style="color: black;"> mLinePaint.setColor(Color.BLUE); </span> - <span style="color: black;"> mLinePaint.setStyle(Style.STROKE); </span> - <span style="color: black;"> mLinePaint.setStrokeWidth(5f); </span> - <span style="color: black;"> mLinePaint.setAlpha(<span class="number" style="color: #c00000;">30</span>); </span> - <span style="color: black;"> </span> - <span style="color: black;"> <span class="comment" style="color: #008200;">//绘制四周阴影区域</span> </span> - <span style="color: black;"> mAreaPaint = <span class="keyword" style="font-weight: bold; color: blue;">new</span> Paint(Paint.ANTI_ALIAS_FLAG); </span> - <span style="color: black;"> mAreaPaint.setColor(Color.GRAY); </span> - <span style="color: black;"> mAreaPaint.setStyle(Style.FILL); </span> - <span style="color: black;"> mAreaPaint.setAlpha(<span class="number" style="color: #c00000;">180</span>); </span> - <span style="color: black;"> </span> - <span style="color: black;"> </span> - <span style="color: black;"> </span> - <span style="color: black;"> } </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">public</span> <span class="keyword" style="font-weight: bold; color: blue;">void</span> setCenterRect(Rect r){ </span> - <span style="color: black;"> Log.i(TAG, <span class="string" style="color: red;">&#8220;setCenterRect&#8230;&#8221;</span>); </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">this</span>.mCenterRect = r; </span> - <span style="color: black;"> postInvalidate(); </span> - <span style="color: black;"> } </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">public</span> <span class="keyword" style="font-weight: bold; color: blue;">void</span> clearCenterRect(Rect r){ </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">this</span>.mCenterRect = <span class="keyword" style="font-weight: bold; color: blue;">null</span>; </span> - <span style="color: black;"> } </span> - <span style="color: black;"> </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">int</span> widthScreen, heightScreen; </span> - <span style="color: black;"> <span class="annotation" style="color: #646464;">@Override</span> </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">protected</span> <span class="keyword" style="font-weight: bold; color: blue;">void</span> onDraw(Canvas canvas) { </span> - <span style="color: black;"> <span class="comment" style="color: #008200;">// TODO Auto-generated method stub</span> </span> - <span style="color: black;"> Log.i(TAG, <span class="string" style="color: red;">&#8220;onDraw&#8230;&#8221;</span>); </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">if</span>(mCenterRect == <span class="keyword" style="font-weight: bold; color: blue;">null</span>) </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">return</span>; </span> - <span style="color: black;"> <span class="comment" style="color: #008200;">//绘制四周阴影区域</span> </span> - <span style="color: black;"> canvas.drawRect(<span class="number" style="color: #c00000;"></span>, <span class="number" style="color: #c00000;"></span>, widthScreen, mCenterRect.top, mAreaPaint); </span> - <span style="color: black;"> canvas.drawRect(<span class="number" style="color: #c00000;"></span>, mCenterRect.bottom + <span class="number" style="color: #c00000;">1</span>, widthScreen, heightScreen, mAreaPaint); </span> - <span style="color: black;"> canvas.drawRect(<span class="number" style="color: #c00000;"></span>, mCenterRect.top, mCenterRect.left &#8211; <span class="number" style="color: #c00000;">1</span>, mCenterRect.bottom + <span class="number" style="color: #c00000;">1</span>, mAreaPaint); </span> - <span style="color: black;"> canvas.drawRect(mCenterRect.right + <span class="number" style="color: #c00000;">1</span>, mCenterRect.top, widthScreen, mCenterRect.bottom + <span class="number" style="color: #c00000;">1</span>, mAreaPaint); </span> - <span style="color: black;"> </span> - <span style="color: black;"> <span class="comment" style="color: #008200;">//绘制目标透明区域</span> </span> - <span style="color: black;"> canvas.drawRect(mCenterRect, mLinePaint); </span> - <span style="color: black;"> <span class="keyword" style="font-weight: bold; color: blue;">super</span>.onDraw(canvas); </span> - <span style="color: black;"> } </span> - <span style="color: black;"> </span> - <span style="color: black;"> </span> - <span style="color: black;"> </span> - <span style="color: black;">} </span> - <span style="color: black;"></span> </span> 说明如下: ...

2014年10月27日 · 16 分钟 · 天边的星星

Android 工具包 xUtils

xUtils简介 xUtils 包含了很多实用的android工具。 xUtils 最初源于Afinal框架,进行了大量重构,使得xUtils支持大文件上传,更全面的http请求协议支持(10种谓词),拥有更加灵活的ORM,更多的事件注解支持且不受混淆影响… xUitls最低兼容android 2.2 (api level 8) 目前xUtils主要有四大模块: DbUtils模块: - android中的orm框架,一行代码就可以进行增删改查; - 支持事务,默认关闭; - 可通过注解自定义表名,列名,外键,唯一性约束,NOT NULL约束,CHECK约束等(需要混淆的时候请注解表名和列名); - 支持绑定外键,保存实体时外键关联实体自动保存或更新; - 自动加载外键关联实体,支持延时加载; - 支持链式表达查询,更直观的查询语义,参考下面的介绍或sample中的例子。 ViewUtils模块: - android中的ioc框架,完全注解方式就可以进行UI,资源和事件绑定; - 新的事件绑定方式,使用混淆工具混淆后仍可正常工作; - 目前支持常用的20种事件绑定,参见ViewCommonEventListener类和包com.lidroid.xutils.view.annotation.event。 HttpUtils模块: - 支持同步,异步方式的请求; - 支持大文件上传,上传大文件不会oom; - 支持GET,POST,PUT,MOVE,COPY,DELETE,HEAD,OPTIONS,TRACE,CONNECT请求; - 下载支持301/302重定向,支持设置是否根据Content-Disposition重命名下载的文件; - 返回文本内容的请求(默认只启用了GET请求)支持缓存,可设置默认过期时间和针对当前请求的过期时间。 BitmapUtils模块: - 加载bitmap的时候无需考虑bitmap加载过程中出现的oom和android容器快速滑动时候出现的图片错位等现象; - 支持加载网络图片和本地图片; - 内存管理使用lru算法,更好的管理bitmap内存; - 可配置线程加载线程数量,缓存大小,缓存路径,加载显示动画等&#8230; 使用xUtils快速开发框架需要有以下权限: ``` <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ``` 混淆时注意事项: 添加Android默认混淆配置${sdk.dir}/tools/proguard/proguard-android.txt 不要混淆xUtils中的注解类型,添加混淆配置:-keep class * extends java.lang.annotation.Annotation { *; } 对使用DbUtils模块持久化的实体类不要混淆,或者注解所有表和列名称@Table(name=”xxx”),@Id(column=”xxx”),@Column(column=”xxx”),@Foreign(column=”xxx”,foreign=”xxx”);

2014年10月24日 · 1 分钟 · 天边的星星

Android 快速开发框架:ThinkAndroid

ThinkAndroid是包含Android mvc和简易sqlite orm以及ioc模块,它封装了Android httpclitent中的http模块, 具有快速构建文件缓存功能,无需考虑什么格式的文件,都可以非常轻松的实现缓存,它实现了图片缓存,在android中 加载的图片的时候oom的问题和快速滑动的时候图片加载位置错位等问题都可以轻易的解决掉。他还包括了一个手机开发中 经常应用的实用工具类,如日志管理,配置文件管理,android下载器模块,网络切换检测等等工具。 ThinkAndroid的开发宗旨是简洁,快速的进行Android应用程序的开发 目前ThinkAndroid主要有以下模块: MVC模块:实现视图与模型的分离。 ioc模块:android中的ioc模块,完全注解方式就可以进行UI绑定、res中的资源的读取、以及对象的初始化。 数据库模块:android中的orm框架,使用了线程池对sqlite进行操作。 http模块:通过httpclient进行封装http数据请求,支持异步及同步方式加载。 缓存模块:通过简单的配置及设计可以很好的实现缓存,对缓存可以随意的配置 图片缓存模块:imageview加载图片的时候无需考虑图片加载过程中出现的oom和android容器快速滑动时候出现的图片错位等现象。 配置器模块:可以对简易的实现配对配置的操作,目前配置文件可以支持Preference、Properties对配置进行存取。 日志打印模块:可以较快的轻易的是实现日志打印,支持日志打印的扩展,目前支持对sdcard写入本地打印、以及控制台打印 下载器模块:可以简单的实现多线程下载、后台下载、断点续传、对下载进行控制、如开始、暂停、删除等等。 网络状态检测模块:当网络状态改变时,对其进行检测。 github项目地址:https://github.com/white-cat/ThinkAndroid 其他框架 一.框架如下几种: 1.Roboguice 2.Spring for Android 3.afinal 4.xUtils 二.Roboguice说明 项目地址:https://github.com/roboguice/roboguice 要依赖三个包,加起来接近800K比较大; 控件和service都可以用IOC注入; 事件不能绑定 activity要继承RoboActivity 三.Spring for Android说明 四.afinal说明 - 项目地址:[https://github.com/yangfuhai/afinal](https://github.com/yangfuhai/afinal) - 依赖包只有152k - 页面控件可以注入,service不可注入 - 事件能绑定 - 提供sqlite,http,图片工具类 - activity要继承FinalActivity 五.xUtils说明 - 项目地址:[https://github.com/wyouflf/xUtils](https://github.com/wyouflf/xUtils) - 依赖包有274k,项目比较活跃 - 页面控件可以注入,service不可注入 - 事件能绑定 - 提供sqlite,http,图片工具类 - activity不要继承,但要侵入代码 - 是afinal项目改进而来,支持大数据上传 &nbsp; ion 项目地址:https://github.com/koush/ion

2014年10月24日 · 1 分钟 · 天边的星星

Android 网络开发框架的选择

在看android基础的时候,关于网络操作一般都会介绍HttpClient以及HttpConnection这两个包。前者是apache的开源库,后者是android自带的api。既然提到了他们,都二者进行一个比较,谷歌在官方文档已经说明了,建议在2.3以及以上版本使用HttpConnection。具体原因呢,是因为对2.1和2.2版本,HttpURLConnection有那么几个Bug,所以建议用Apache的HTTP Client;之后的版本,建议用HttpURLConnection。Apache的HTTP Client比较强大,拥有庞大而灵活的API,这个实现很稳定,并且Bug很少。然而,也就是因为太庞大了,以至于很难在保证兼容性的情况下改进它,故android 开发团队不应该维护该库而是转投更为轻量级的httpurlconnection。 当我们开发企业级应用的时候,一般都会选择使用已经封装好的http框架。开源的比较流行的有: 1、volley 2、android-async-http 3、retrofit 4、okhttp 5、androidquery 6、AndroidAsync 等。他们各有优劣,不同的框架有不同的效率,在使用的时候可以因地制宜地测试,根据效果来选择使用哪个,之前个人则比较喜欢用android-async-http,。如今Google推出了官方的针对Android平台上的网络通信库volley,能使网络通信更快,更简单,更健壮,Volley在提供了高性能网络通讯功能的同时,对网络图片加载也提供了良好的支持,完全可以满足简单REST客户端的需求, 我们没有理由不跟上时代的潮流。另外,但volley的扩展性很强,可以根据需要定制你自己的网络请求。所以,最后推荐还是使用volley进行开发,当然其他几个库也是非常具有学习以及参考意义的,可以将他们的精髓之处汲取到volley框架的拓展开发之中,做出自己理想的http通讯框架。 推荐博客: http://instructure.github.io/blog/2013/12/09/volley-vs-retrofit/ http://blog.csdn.net/t12x3456/article/details/9221611 http://blog.csdn.net/guolin_blog/article/details/12452307

2014年10月24日 · 1 分钟 · 天边的星星

Android敏捷开发指南(下)

本文延续上期话题,深入到测试、持续集成和部署等环节,紧密结合移动开发方法和技术,围绕Android平台的开发讨论提供更高质量移动产品的解决方案。 **通过清晰的架构实现测试驱动** 通过[《程序员》杂志9月刊文章](http://www.programmer.com.cn/13757/)的分析,我们可以看到,每一种工具都很难从完全意义上解决工程当中追求快速和高质量的要求。那么就需要通过整体架构实践,更好地解决这方面的问题。以下两种结构方法可供参考。 **平台和领域的分层结构** 如图1所示,这是一种最基础的分层结构。它将和Android平台相关的内容都包裹在了平台层,而将领域、状态、逻辑等和平台无关的内容完全隔离开来。创建项目时,将平台层和测试层分别对领域层产生依赖,以这样的方式达成对项目和对测试的独立构建。 这样做的好处是,领域层可以通过Java的JUnit进行完全测试,而项目各个类之间的因为有明显的项目级别分层,使得代码的考量更加清晰,方便于检验最核心关键的业务逻辑。且测试速度非常快,便于快速迭代。 但其带来的不便之处就是,随着项目代码的日益复杂,其与Android内部结合就越紧密,这时往往越难将业务逻辑很清晰的剥离。部分的业务逻辑将被分片散落在一些平台相关的调用当中。因此我们认为这样的一种层次逻辑更多地适用于简单的项目之中。 ![](http://ipad-cms.csdn.net/cms/attachment/201210/5061286bc38e9.jpg) 图1 平台和领域的分层结构 **MVP(Passive View)架构** 许多文章对Passive View是属于MVP还是MVC有各种不同角度的争论。这里我并不想过多地在词语上做过多纠缠,主要是希望通过描述这样的一种结构来树立比较好的对于Android开发的架构模型。 具体到Passive View的实现,如图2所示,描述了一个基本的原理。 随着项目的不断扩大复杂化,我们需要更加清晰化的代码架构。所以,Passive View对平台和领域的分层结构的层次结构有了更深层次的改进。引入Passive View模式的意义实际上还是从测试出发。出于对JUnit快速性能的青睐,所以依然尽量把层次划分为依赖Android内核的部分和非依赖部分。对于前 半部分,可以用基于Roboletric的上层测试完成,相对应的后半部分则可以使用标准Java进行单元测试。这样做既加快了测试速度,又得以保障测试的覆盖率。 ![](http://ipad-cms.csdn.net/cms/attachment/201210/5061290a0cd3f.jpg) 图2 Passive View架构实现原理图 更多详细的架构资料,请大家参考《GUI Architecture》一文,相信会得到许多很好的想法。 **测试驱动开发的实现** 之所以要强调代码框架结构,最重要的原因就是寻找快捷方便的途径进行TDD开发。在上一篇文章中已提到了Android常见的集中单元测试工具。之所以将框 架设计方式引入测试驱动开发过程,目的是将应用代码分开处理、分而治之。最大限度让各工具间扬长避短。最终实现高效率测试驱动的目的。 事实上,在目前的开发领域,我们也看到了许多正在应运而生的开发框架,例如Android Spring框架。这些都是下一步需要努力和完善的目标。 **业务行为驱动的功能测试方案** 如果说单元测试驱动开发帮助我们从开发角度完成了对代码质量的控制,那么基于业务行为的功能测试则为从业务到实现的统一、软件质量的保障提供了重要的解决方法。 功能(验收)测试:就功能测试而言,所指基本上就是测试科目中常提到的黑盒测试。只不过这里加上了更多具有上下文的信息,使得一次完整的黑盒测试更具有实质上的功能意义。 在 Android的开发中,通常是模拟真机或模拟器的用户操作(例如点击或滑动),来检测操作的结果是否符合预期。这个过程可以很好地代替人工的操作,更快 速和大量完成重复性的测试工作,特别是在发展较快、平台和系统版本覆盖较广的项目中,可以起到节约人力成本、提高测试覆盖与准确性的作用。 在常用的工具中,可以选取上一篇文章中介绍过的Robotium或Native Driver来实现。 前者社区发展速度快,实现功能相对更加完善,例如实现了一些手势操作效果等。不过问题是Robotium的组织目前正在倾向闭源化,有些最好的新功能已无法在开源版本中寻找到。 后者的原理是通过植入应用后门的方式,实现对应用的发出指令和进行资源获取。其物理模型比较适合不能再目标设备安装测试应用,或者希望远程控制的情况。开源状况良好,只是近一年,Native Driver的团队把重点发在其他平台上,对Android的更新比较慢。 基于场景的的跨平台验收测试:基于场景的验收测试,好处是可以在业务人员和开发人员之间建立验收机制。通过一系列关于场景的自然语言描述,完成测试的原始编 码。Cucumber是一个在行为驱动开发(BDD)领域比较常见的工具。其基本方法是通过执行文字形功能描述语言来实现对软件的自动化测试。图3展示了 一个常见的Cucumber测试场景。在Android开发中,通常是解析Cucumber,根据内容分情况拆分正具体的操作步骤。将这些步骤用基于 NativeDriver的Java代码进行实现,通过NativeDriver从指令端到设备端的Android自动测试框架完成控制与校验的自动化测 试。 ![](http://ipad-cms.csdn.net/cms/attachment/201210/50612a3536eaf.jpg) 图3 常见的Cucumber测试场景 当然,这些步骤也可以通过Robotium的远程控制(RC)调用来实现。其原理类似,如图4所示。 该方案还有额外的好处—用同样的方法,通过Cucumber可以实现对UIAutomation等其他平台自动化测试工具的控制。实现一份用例,多个平台的测试解决方案。图5给出了一个iOS平台上的基于Cucumber的跨平台测试实例。 ![](http://ipad-cms.csdn.net/cms/attachment/201210/50612a618a4e3.jpg) 图4 Cucumber测试实例 ![](http://ipad-cms.csdn.net/cms/attachment/201210/50612aa72955d.jpg) 图5 iOS平台上基于Cucumber的跨平台测试实例 **从持续集成到部署** **Jenkins最基本的持续集成** 在做好了各种测试实践后,需要考虑如何保证这种实践在每一次提交都产生效果,并且提供对代码库的不断保护。这里引入常用敏捷实践中持续集成的概念。 持 续集成主要是为了能够更好为产品在整个开发过程中提供交付保障。相对比传统服务器领域,移动开发中,碰到最多的问题是环境的不同与测试包的相对封闭。以前 通过改一个配置实现服务器实时测试修改的方法,很难直接应用于移动开发。此外,作为整个服务边缘的移动应用,往往会遇到大量不同的测试环境。 所以如图6所描述的,需要在持续集成服务器上部署相关的应用包,完成对不同环境的开发与测试。这样的测试通常是由持续集成服务器引导的不同物理移动设备来完成,以保证最终测试包的可交付性。尤其适用于对于网络和性能等相关的测试。 ![](http://ipad-cms.csdn.net/cms/attachment/201210/50612ae03a67e.jpg) 图6 利用相关应用包完成对不同环境的开发与测试 当然,如果细心的话,也可以看出图6中对于产品版本与配置管理的一些端倪。 **OTA自动化部署** 持续集成帮助我们有了稳定的应用包。有了这些包,我们现在就差最后的一步—自动化部署。在移动的真实交付中,由于安装包需要下载到移动终端完成。所以自动化部署也必须要模拟这一过程完成交付。 这里我们推荐使用持续集成服务器关于邮件服务推送的应用插件来完成。具体的过程通常为,在完成编译并顺利通过测试后,持续集成服务器会根据制定终端列表,推送测试内容以供安装。推送者收到请求后,可以根据需要自行安装相应apk文件,达到部署效果。 这样做打通了移动开发最后一步的障碍。并有效沟通了管理、设计、业务与测试的各个部门人员。持续集成管理员还可以通过管理安装邮件列表的形式,自行选择不同版本,环境安装包对不同相关人员的推送。 **让调试可持续化** 有 了开发,有了一系列持续化集成作业,有了作业完成的自动化打包部署,貌似可以休息了。但正如DevOps概念所说,虽然做了许多努力,但我们依然承认会有 错误的出现,那么如何尽早发现错误并准确定位,最终完成就显得尤为重要。在不同阶段,我们尝试使用不同的方法来进行调试。下面介绍我们的两个实践方法。 开发后调试。旧有的logcat是一个很好的调试工具,它在开发中起到了重要的作用,但对于做探索测试的QA们而言,大量繁琐的Debug信息往往令人看得眼花缭乱。 这 时我们就需要一个更好的工具进行错误栈的准确定位,因此我们引入了Acra。这是一个开源工具包,其功能是自动化地将Android应用的错误报告发送到 GoogleDoc或着Email等的可记录文本中。它的目的是为了帮助Android应用开发者在错误发生时收集行为和技术数据。 上线后的持续关注当然在上线后,Google Play也为我们提供了一个非常有好的平台用以收集各种来自用户的反馈信息。其中包括下载量、评价、应用异常报告等。 &nbsp; ![](http://ipad-cms.csdn.net/cms/attachment/201210/50612b2c496ca.jpg) &nbsp; &nbsp; ![](http://ipad-cms.csdn.net/cms/attachment/201210/50612b2d5d71a.jpg) &nbsp; ![](http://ipad-cms.csdn.net/cms/attachment/201210/50612b35d1eac.jpg) 图7 移动循环开发流程 在 应用异常报告中,详细记录了整个错误堆栈的信息,更包括错误信息的总数量、周发生次数,以及版本时间等信息。因为Android平台设备性能、具体型号等 多方面内容,并不一定所有的错误信息都一定去进行处理,但这个平台提供了一个持续关注上线情况的基础,为产品的紧急处理、故障修复和在开发提供了重要的信 息来源。 这里还要强调,测试优先的TDD实践,在每一个错误修复前,都应该把测试补好,这是同样错误不会再次出现的最重要保障。 **总结** 本文提出了移动开发过程中常见的问题,并分析了基于开发与维护过程中的一些具体实践方法,尝试从移动应用的开发技术角度提供解决方案。 除此之外,事实上和常见的软件开发一样,还有诸如配置管理、发布管理等也是必不可少的。我们总结了如图7所示的一个移动开发循环。通过对不同方面的配置技术实现,完成移动的全流程开发,供读者做更深的思考和方法演进。 转自:http://www.yidin.net/?p=6821

2014年10月24日 · 1 分钟 · 天边的星星