当前位置: 首页 > 科技观察

Android开发的那些陷阱和技巧

时间:2023-03-15 14:43:51 科技观察

1.android:clipToPadding表示控件的绘图区域是否在padding中。默认为真。如果将这个属性的值设置为false,可以达到少做布局的效果。先来看一张效果图。上图中ListView的顶部默认有一个空隙。向上滑动后,缝隙消失,如下图。如果使用margin或者padding,是达不到这个效果的。添加headerView似乎有点矫枉过正而且太麻烦了。这里clipToPadding和paddingTop的效果刚刚好。同样,还有一个很神奇的属性:android:clipChildren,具体可以参考:[Android]神奇的android:clipChildren属性(http://www.cnblogs.com/over140/p/3508335.html)2.match_parent它按理说wrap_content这两个属性一目了然,一个是填充布局空间适应父控件,另一个是适应自身内容的大小。但是如果是在ListView这样的列表中,用错了就会出大问题。ListView中的getView方法需要计算列表项,所以需要确定ListView的高度,onMesure可以做测量。如果指定了wrap_content,就相当于告诉系统,如果我有10000个项目,你帮我计算显示,然后系统会根据你的要求新建10000个对象。那你是不是悲剧了?先看一张图。假设ListView现在有8条数据,match_parent需要创建7个新对象,而wrap_content需要8个,这里涉及到View的复用,就不多说了。所以这两个属性的设置将决定调用getView的次数。这就导致了另一个问题:getView被多次调用。什么叫多次?例如,position=0可能会被调用多次。看起来很奇怪。GridView和ListView都可能出现,也许罪魁祸首是wrap_content。归根结底还是View的布局有问题。如果嵌套View过于复杂,解决方法可以是通过代码测量list需要的高度,或者在getView中使用一个小技巧:parent.getChildCount==position@OverridepublicViewgetView(intposition,ViewconvertView,ViewGroupparent){if(parent.getChildCount()==position){//doesthingshere}returnconvertView;}3.IllegalArgumentException:pointerIndexoutofrange这个bug出现的场景还是很让人无语的。一开始我是用ViewPager+PhotoView(一个开源控件)来展示图片,在多点触控放大缩小的时候出现了这个问题。一开始怀疑是PhotoView的bug,找了半天无果。可怕的是不知道怎么试,老是死机。后来发现是android遗留的bug,源码中没有检查指针索引。不可能更改源代码并重新编译。明知道有异常,也不能从根本上解决。如果不崩溃,就只能try-catch了。解决方法是:自定义一个ViewPager,继承ViewPager。请看下面代码:/***自定义包android.support.v4.view.ViewPager,重写onInterceptTouchEvent事件,捕获系统级异常*/publicclassCustomViewPagerextendsViewPager{publicCustomViewPager(Contextcontext){this(context,null);}publicCustomViewPager(Contextcontext,AttributeSetattrs){super(context,attrs);}@OverridepublicbooleanonInterceptTouchEvent(MotionEventev){try{returnsuper.onInterceptTouchEvent(ev);}catch(IllegalArgumentExceptione){LogUtil.e(e);}catch(ArrayIndexOutOfBoundsExceptione){LogU(e);}returnfalse;}}将ViewPager中使用的布局文件换成CustomViewPager就OK了。4、ListView中的item点击事件没有响应。listView的Item点击事件突然没有响应。这个问题一般发生在给listView添加按钮、复选框等控件之后。此问题是由焦点冲突引起的。在android中,点击屏幕后,会根据你的layout分配click事件。当您在listView中添加一个按钮时,点击事件将首先分配给listView中的按钮。所以你的点击Item会失效。这时,您必须根据需要为项目的最外层布局设置点击事件,或者为您的某个布局元素添加点击事件。解决方法:在ListView的根控件中设置descendantFocusability属性(如果根控件是LinearLayout,则在LinearLayout中添加如下属性设置)。android:descendantFocusability="blocksDescendants"的官方文档也说明了这一点。5、getSupportFragmentManager()和getChildFragmentManager()有一个要求,一个Fragment需要嵌套3个Fragment。基本上可以想到用ViewPager来实现。开始代码是这样写的:mViewPager.setAdapter(newCustomizeFragmentPagerAdapter(getActivity().getSupportFragmentManager(),subFragmentList));造成的问题是嵌套的Fragment有时莫名其妙不显示。一开始,你不知道问题出在哪里。在不知道问题原因的情况下,解决问题显然是比较麻烦的。反复查找,终于在stackoverflow上看到了同样的问题。据说使用getChildFragmentManager()。太神奇了!mViewPager.setAdapter(newCustomizeFragmentPagerAdapter(getChildFragmentManager,subFragmentList));让我们看看这两者之间有什么区别。首先是getSupportFragmentManager(或getFragmentManager)的说明:返回FragmentManager,用于与与此片段的活动相关的片段进行交互。然后是getChildFragmentManager:返回一个privateFragmentManager,用于放置和管理FragmentsinsideofthisFragment。基本上,区别在于Fragment现在可以拥有自己的内部FragmentManager。子FragmentManager是处理仅包含在其添加到的Fragment中的Fragments的子FragmentManager。另一个FragmentManager包含在整个Activity中。已经说清楚了。6、ScrollView嵌套ListView的设计是不是很奇怪?两个同样可以滚动的View其实是放在一起的,还是嵌套的。曾经有这样一个需求:界面一共有4个区域,分别是公司基本信息(标志、名称、法人、地址)、公司简介、公司荣誉、公司口碑榜。每一部分内容都需要根据内容适配高度,不能写死。首先想到的是在外面围上一个ScrollView。然后用4个自定义控件封装这4个部分。基本信息和公司简介都比较简单。RecyclerView和TextView用于荣誉。RecyclerView(当然也可以用GridView,显示3列多行)存储荣誉图片,TextView显示荣誉名称。***口碑榜的一部分当然是ListView。这时候,问题就出来了。需要解决ListView放置在ScrollView中的滑动问题和RecyclerView的显示问题(如果RecyclerView无法计算高度,则看不到内容)。当然网上已经有类似的问题和解决方法了。给个网址:解决ScrollView嵌套ListView问题的四种方案(http://bbs.anzhuo.cn/thread-982250-1-1.html)ListView的情况还是比较好解决的,优雅的方式是无非就是写一个继承ListView的类,然后重写onMeasure方法。@OverrideprotectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){intexpandSpec=MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2,MeasureSpec.AT_MOST);super.onMeasure(widthMeasureSpec,expandSpec);}ListView可以重写onMeasure的解决方法,RecyclerView重写这个方法就OK没办法.毕竟,它实际上是计算高度的。有两种方式,一种是动态计算RecycleView,然后设置setLayoutParams;另一种类似于ListView的解决方案,定义一个类继承LinearLayoutManager或者GridLayoutManager(注意:不是继承RecyclerView),重写onMeasure方法(这个方法比较麻烦,这里就不展示了,下次再介绍我写一篇文章)。动态计算高度如下:intheightPx=DensityUtil.dip2px(getActivity(),(imageHeight+imageRowHeight)*lines);MarginLayoutParamsmParams=newMarginLayoutParams(LayoutParams.MATCH_PARENT,heightPx);mParams.setMargins(0,0,0,0);线性布局。LayoutParamslParams=newLinearLayout.LayoutParams(mParams);honorImageRecyclerView.setLayoutParams(lParams);思路是这样的:服务端返回荣誉图片后,由于是3列显示,所以只需要计算需要显示多少行即可,然后指定图片的行间距和高度,然后设置setLayoutParams就行了。intlines=(int)Math.ceil(totalImages/3d);至此,这个奇怪的需求就解决了。但是滑动的时候感觉有卡顿的现象。如果你聪明,你一定认为这是一个滑动冲突。应该是ScrollView的滚动干扰了ListView的滑动。我应该怎么办?你能禁用ScrollView的滚动吗?百度一下,一定能搜到答案。先上传代码:/***@authorLeo**Createdin2015-9-12*拦截ScrollView滑动事件*/publicclassCustomScrollViewextendsScrollView{privateintdownY;privateinttouchSlop;publicCustomScrollView(Contextcontext){this(context,null);}publicCustomScrollView(Contextcontext,AttributeSetattrs){this(context,attrs,0);}publicCustomScrollView(Contextcontext,AttributeSetattrs,intdefStyleAttr){super(context,attrs,defStyleAttr);touchSlop=ViewConfiguration.get(context).getScaledTouchSlop();}@OverridepublicbooleanonInterceptTouchEvent(MotionEvente){intaction=e.getAction();switch(action){caseMotionEvent.ACTION_DOWN:downY=(int)e.getRawY();break;caseMotionEvent.ACTION_MOVE:intmoveY=(int)e.getRawY();if(Math.abs(moveY-downY)>touchSlop){returntrue;}}returnsuper.onInterceptTouchEvent(e);}}只要理解getScaledTouchSlop()方法,就很好办了。此方法的注释是:在我们认为用户正在滚动之前触摸可以移动的距离(以像素为单位)。据说这是一个距离,就是说滑动的时候,手的移动必须大于这个距离才能开始移动控件,小于这个距离就不会触发移动。看起来很操蛋。但是还有一个问题:我每次加载这个界面的时间太长了。我每次从其他界面启动这个界面都会卡1~2秒,时间因手机性能不同而不同。不是网络请求,取数据是子线程完成的,与UI线程无关。这种经历让人很不舒服。几天过去了,还是一样。我很快就会给我的老板看。这种经历会被骂十遍。是不是和ScrollView的嵌套有关?好吧,那我就重构代码。不再有ScrollView。直接使用一个ListView,然后添加一个headerView来存放其他内容。因为控件封装的很好,不用多改布局就OK了。一跑起来,顺风顺水,一切都迎刃而解!这么简单的问题,为什么一定要用ScrollView来嵌套呢?stackoverflow早就告诉你了,不要做这种Nesting!不要这样窝!不要这样窝!重要的事情说三遍。ListViewinsideScrollViewisnotscrollingonAndroid(http://stackoverflow.com/questions/6210895/listview-inside-scrollview-is-not-scrolling-on-android)当然从android5.0开始Lollipop提供了新的API支持嵌入式滑动。这时候,这样的需求就可以很好的实现了。这里有个网站,大家有兴趣可以自己了解,这里就不展开讨论了。AndroidNestedScrolling实战(http://www.race604.com/android-nested-scrolling/)7.EmojiconTextView的setText(null)这是开源表情库com.rockerhieu.emojicon中TextView的增强版。这个开源工具包相信很多人都用过。TextView使用setText(null)完全没问题。但是EmojiconTextViewsetText(null)之后,就悲剧了,直接崩溃,显示空指针。起初我怀疑视图没有初始化,但事实并非如此。然后调试它。@OverridepublicvoidsetText(CharSequencetext,BufferTypetype){SpannableStringBuilderbuilder=newSpannableStringBuilder(text);EmojiconHandler.addEmojis(getContext(),builder,mEmojiconSize);super.setText(builder,type);}EmojiconTextView中的setText好像没问题。点击SpannableStringBuilder进去看看。源代码原来是这样的:/***CreateanewSpannableStringBuildercontainingacopyofthe*specifiedtext,includingitsspansifany.*/publicSpannableStringBuilder(CharSequencetext){this(text,0,text.length());}好的。问题已经找到了,text.length(),不是空指针惹的祸。text=text==null?"":text;SpannableStringBuilderbuilder=newSpannableStringBuilder(text);加一行判断就行了。8.cursor.close()一般来说,数据库的打开和关闭是不会忘记的,但是游标的使用可能不会引起太多的注意,尤其是游标的随意使用。例如使用ContentResolver结合Cursor查询SD卡中的图片,很容易写出如下代码:Cursorcursor=contentResolver.query(uri,null,MediaStore.Images.Media.MIME_TYPE+"=?or"+MediaStore.Images.Media.MIME_TYPE+"=?",newString[]{"image/jpeg","image/png"},MediaStore.Images.Media.DATE_MODIFIED);while(cursor.moveToNext()){//TODO}光标不做非空判断,关闭游标时不注意往往会抛出异常。在之前的项目中,经常会因为游标没有及时关闭或者异常处理不当而出现其他问题,问题看起来很奇怪,很难解决。后来我重构了整个项目中游标的使用,就再也没有出现过类似的问题。原理很简单。所有Cursor声明为:Cursorcursor=null;并放在try-catch之外;如果需要使用游标,先进行非空判断。然后在方法底部使用一个工具类来处理游标的关闭。/***@authorLeo**Createdin2015-9-15*/publicclassIOUtil{privateIOUtil(){}publicstaticvoidcloseQuietly(Closeablecloseable){if(closeable!=null){try{closeable.close();}catch(Throwablee){}}}publicstaticvoidcloseQuietly(Cursorcursor){if(cursor!=null){try{cursor.close();}catch(Throwablee){}}}}我认为,这样就没有必要在任何地方都进行try-catch-finally向上。9.java.lang.StringcannotbeconvertedtoJSONObject在解析服务器返回的JSON字符串时抛出该异常。调试没发现什么问题,好像是正常的JSON格式。后来发现JSON字符串多了BOM(ByteOrderMark)。服务器端的代码由PHP实现。有时为了修改方便,开发直接用Windows记事本打开保存,这样就引入了肉眼看不到的问题。其实“ufeff”的东西太多了,客户端代码可以过滤一下。//incase:Valueoftypejava.lang.StringcannotbeconvertedtoJSONObject//移除BOMheaderif(jsonStr!=null){jsonStr=jsonStr.trim();if(jsonStr.startsWith("ufeff")){jsonStr=jsonStr.substring(1);}}10.Shaperoundrecttoolargetoberenderedintoatexture圆矩形太大?一开始发现一个acitivity中的scrollView是滑动的,但实际上并没有嵌套ListView、GridView等任何列表控件。无外乎包含一些TextView、ImagView等。在Eclipse中查看日志输出,发现出现了这个warn级别的提示。难道我在外层嵌套了这个圆角矩形?我在很多地方都用过,为什么这个接口有问题?后来发现这个圆形长方形里面的内容太多了,已经超出了手机的容量。高度,可滑动数页。StackOverFlow上有人说:最简单的解决方案是去掉圆角。如果去除圆角并使用简单的矩形,硬件渲染器将不再为背景层创建单个大纹理,也不会再遇到纹理大小限制。还有建议:画到画布上。具体链接:HowDoSolveShaperoundrecttoolargetoberenderedintoatexture(http://stackoverflow.com/questions/14519025/how-do-solve-shape-round-rect-too-large-to-be-rendered-into-a-texture-in-android)我尝试了自定义控件LinearLayout,通过canvas绘制,还是无法解决。去掉radius属性确实可行,但是想保留怎么办?还有另一种解决方案,通过在androidManifest.xml中禁用硬件加速。为了控制粒度,我在这个活动中只禁用了这个功能。先想到这么多,以后再补充。