一、android 精典博文内容推荐
app集成支付宝、app缓存管理、app列表圆角设计、App自动更新之通知栏下载(有续)、索引ListView、App数据格式之解析Json、拖拽ListView http://www.cnblogs.com/qianxudetianxia/category/293007.html
二、Android命名规范(自定义)
此规范参考自android源码,并加以改进,仅供个人使用,如果你觉得还有可取之处,可以参考下,以后有好的规则,再补充
总体规范:
类名要清晰,能反映出这个类的作用,最好能达到见名知义的效果
方法名要使用动宾短语 eg: public boolean moveTaskToBack(boolean nonRoot);
构造函数使用pascal命名规则,又叫大驼峰规则,首字母大写
普通方法和成员变量采用小驼峰规则(camel规则),首字母小写
普通方法的局部变量采用下划线规则,以_开头
1.类的成员变量
所有公开的类常量:定义为静态final类型,名称全部大写 eg: public static final String ACTION_MAIN = "android.intent.action.MAIN";
静态变量:名称以s开头 eg: private static long sInstanceCount = 0;
非静态的私有变量,protected的变量:以m开头 eg: private Intent mIntent;protected ViewParent mParent;
2.方法的命名
方法参数:名称以p开头,表示param的意思 eg: public int getCount(int pCount);
方法内的局部变量以_开头,
eg public int getCount (int pCount){
int _count;
}
三、android ListActivity自定义标题栏
android 自定义标题栏的步骤是
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
// 这里要主要requestWindowFeature和setContentView先后顺序哦
setContentView(R.layout.main);
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title_1);
}
但是这个在ListActivity中好像无效,因为ListActivity没有调用setContentView方法,网上的一些解决方法都是在onCreate中调用setContentView,也就是自己实现layout,
有没有更简单的方法呢,使用上面的那样,
其实是有的,在ListActivity中有一个方法很容易被忽略,ensureList(),这是唯一一个设置了layout的地方,但是不是在oncreate中设置的,listActivity没有实现onCreate方法,
继续找,发现在onRestoreInstanceState(), setListAdapter()和getListView()中调用了,也就是说在这3个方法中都有可能会执行setContentView方法,
知道这个就好办了,
解决方法一:在oncreate方法中调用getListView()代替掉上面的setContentView(R.layout.main)
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
//代替掉setContentView
getListView();
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title_1);
}
解决方法二:找到你的activity调用setListAdapter的地方,在后面加上面最后一句
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
/**
* 你自己的代码
*/
setListAdapter(adapter);
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title_1);
}
四、Android 设置dialog背景全透明无边框的最简单的方法
做dialog的全透明无边框背景,网上找了n久,都有问题,其实很简单,就两句搞定。
<style name="Translucent_NoTitle" parent="android:style/Theme.Dialog">
<item name="android:background">#00000000</item> <!-- 设置自定义布局的背景透明 -->
<item name="android:windowBackground">@android:color/transparent</item> <!-- 设置window背景透明,也就是去边框 -->
</style>
其他的都可以不用设置,有些属性会继承下来,唯一一个没被继承的是
<item name="
android:windowIsTranslucent
">true</item>,这个不设置也没影响
此方法同样可以用于activity,设置activity半透明
res/values/styles.xml
<resources>
<style name="Transparent ">
<item name="android:windowBackground">@color/transparent_background</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowAnimationStyle">@+android:style/Animation.Translucent</item>
</style>
</resources>
res/values/color.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="transparent_background">#50000000</color>
</resources>
注意:color.xml的#5000000前两位是透明的效果参数从00--99(透明--不怎么透明),后6位是颜色的设置
manifest.xml
<activity android:name=".TransparentActivity" android:theme="@style/Transparent">
</activity>
五、android layout_gravity失效的问题
相信对于Android的初学者来说,大家都曾经被layout里这两个极其相似的属性迷惑过。
简单使用一下搜索工具,我们就不难找到下面这样的答案:
layout_gravity 表示组件自身在父组件中的位置
gravity 表示组件的子组件在组件中的位置
看似很简单嘛
为什么这么简单的道理,总有同学会发现,在“某些时候”,layout_gravity这个属性不好使了,失去了它应有的作用
问题究竟出在哪里了呢?
当作为父layout的LinearLayout的属性为android:orientation="vertical" 的时候,android:layout_gravity="?"这里设为横向的时候才能生效。比如:left,right,center_horizontal等;
当作为父layout的LinearLayout的属性为android:orientation="horizental" 的时候,android:layout_gravity="?"这里设为纵向的时候才能生效。比如:top,bottom,center_vertical等;
有一个比较特殊的是center,不管是横向还是纵向的时候,它总有一个方向起作用, 因为LinearLayout他只可能有一个方向,
这nm的,确实让人蛋疼。其实也有点道理吧,就是LinearLayout横向的时候,如果有多个孩子,那就不知道把谁放最右了,
有两个解决方法吧,
(1)用RelativeLayout吧,这个算是费话吧 ,哈哈
(2)在LinearLayout中设置android:gravity这个从官方api的解释是怎么放置它的内容,LinearLayout的内容不就是他的孩子么,问题解决
六、一个神奇的网站http://linux.linuxidc.com/
本文转自 http://itindex.net/detail/15843-linux.linuxidc.com-%E8%B5%84%E6%96%99-android
Shared by Yuan
用户名与密码都是
www.linuxidc.com
linux.linuxidc.com - /2011年资料/Android入门教程/
[转到父目录]
2011年7月16日 21:33 <目录> Android 3D 游戏开发教程
2011年8月18日 13:26 <目录> Android WIFI开发介绍
2011年7月25日 16:53 <目录> Android 实战项目之五子棋附源码
2011年8月12日 8:53 <目录> Android 游戏开发之主角的移动与地图的平滑滚动
2011年7月19日 13:24 <目录> Android 的 Remote Service 开发实例
2011年6月18日 9:41 <目录> Android入门教程系列之1——贪吃蛇改进版
2011年8月13日 20:27 <目录> Android反编译工具
2011年7月18日 8:59 274623 Android图形系统的分析与移植.pdf
2011年8月13日 19:59 <目录> Android学习之三步搞定开机启动程序
2011年8月13日 20:13 <目录> Android学习之多点触摸并不神秘
2011年8月13日 19:54 <目录> Android学习之改变Activity切换方式
2011年8月13日 20:10 <目录> Android学习之解析JSON
2011年8月21日 17:09 <目录> Android小闹钟程序源码
2011年7月19日 21:29 <目录> Android应用开发揭秘
2011年8月20日 19:39 <目录> Android开发:巧妙运用ViewStub写出类似Tab选项卡
2011年8月23日 8:00 <目录> Android技术内幕 PDF
2011年7月26日 19:20 <目录> Android框架及应用开发介绍
2011年8月12日 9:10 <目录> Android游戏开发之切换游戏场景特效的实现
2011年8月18日 8:53 <目录> Android游戏开发之单点触摸与多点触摸的响应方式
2011年7月27日 7:59 <目录> Android游戏开发之地图编辑器的使用以及绘制地图
2011年8月14日 6:33 <目录> Android游戏开发之处理按键的响应方式
2011年8月12日 9:26 <目录> Android游戏开发之处理音乐与音效太鼓达人游戏原理
2011年8月22日 14:18 <目录> Android游戏开发之多线程的操作方式源码
2011年8月22日 14:07 <目录> Android游戏开发之小球重力感应实现源码
2011年8月12日 7:58 <目录> Android游戏开发之摄像头的原理以及更新
2011年8月12日 9:01 <目录> Android游戏开发之数据库SQLite 详细介绍
2011年8月12日 8:38 <目录> Android游戏开发之构建游戏框架View与SurFaceView的区别
2011年8月12日 8:33 <目录> Android游戏开发之检测游戏碰撞的原理实现
2011年8月12日 8:14 <目录> Android游戏开发之游戏帧动画的播放与处理
2011年8月12日 8:47 <目录> Android游戏开发之绘制游戏主菜单与进度条加载进度
2011年8月18日 8:58 <目录> Android游戏开发之触摸轨迹曲线的实现处理
2011年8月12日 9:14 <目录> Android游戏开发之飞行射击类游戏原理实现
2011年7月29日 19:40 <目录> Android游戏引擎ANGLE 实例和源码
2011年7月17日 11:24 <目录> Android的binder机制研究(C++部分)
2011年8月6日 8:29 <目录> Android笔记 Application对象的使用-数据传递以及内存泄漏问题
2011年8月6日 8:33 <目录> Android笔记 SQLite总结 ,字带数据库,附记事本,字典小程序
2011年8月18日 13:40 <目录> Android系统中WiFi网络的研究与实现
2011年8月12日 8:28 <目录> Android软件开发之EditText 详解
2011年8月12日 8:41 <目录> Android软件开发之ListView 详解
2011年8月12日 8:05 <目录> Android软件开发之TextView详解
2011年8月12日 9:04 <目录> Android软件开发之应用程序之间的通信介绍
2011年8月12日 8:51 <目录> Android软件开发之数据的新建 储存 读取 删除
2011年7月25日 17:04 <目录> Android软件开发之盘点
2011年8月12日 8:57 <目录> Android软件开发之盘点界面五大布局
2011年6月25日 18:57 <目录> Google Android SDK开发范例大全
2011年8月18日 14:02 <目录> Google Android学习一点通(高清中文版)
2011年6月25日 9:09 <目录> Professional Android Application Development [PDF]
2011年6月28日 15:43 <目录> Ubuntu 11.04 搭建Android开发环境
2011年7月21日 11:40 <目录> 一款Android上小游戏--交互式人机对战五子棋
2011年7月21日 11:54 <目录> 制作可独立分发的Android模拟器
2011年8月16日 8:56 <目录> 基于 Android NDK 的学习之旅-----Java 调用C
2011年8月16日 10:17 <目录> 基于 Android NDK 的学习之旅-----序言
2011年8月17日 15:46 <目录> 基于Android的高效短信查询软件的实现
2011年7月18日 9:00 307448 基于Android的高效短信查询软件的实现.pdf
七、android:layout_weight的巧妙应用(二)
之前我讲过layout_weight的巧妙应用一 http://www.cnblogs.com/xiaoQLu/archive/2011/08/08/2130328.html
现在更深入讲下weight的其他应用和原理
先看下图,我要用LinearLayout实现如下效果,2要自适应大小,也就是wrap_content,1要占满剩下的空间怎么办?(当然用相对布局很简单)
如果我要再实现一个更变态点的需求呢,就是2位置要动态变换呢(右图所示效果),根据不同的状态,设置2位置不同控件的隐藏和显示,这种情况下,用RelativeLayout实现就有点麻烦了,你要找到一个基准控件,然后根据他来布局其他控制,如果有一个设置不对,显示的布局就没有这个效果
先分析此问题的难点:
(1)控件2的宽度不确实的,导致1不知道自己的宽度要设为多少。
(2)控件2的位置有多个控件,需要动态控制不同控件来显示和隐藏
如果用LinearLayout就简单很多,linearlayout有一个属性,就是本篇的主角android:layout_weight属性,它是属于LinearLayout特有的。

用LinearyLayout实现有两种写法,都差不多。写提供代码
实现一
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/hello" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name" />
</LinearLayout>
实现二
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<Button
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/hello" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name" />
</LinearLayout>
其实这两种实现只有一点区别,就是第一个button的layout_weidth,一个是fill_parent,另一个是0dip。其实两种都差不多,下边详细讲解:
首先大家要明白一个概念,就是一个控件(View)本身是可以无限大的,也说是说在它自己的onDraw()函数中,你想要它多大,它就可以有多大,但是为什么我们只能看到一部分呢?
这就要"归功"于android:layout_width和height属性,很多人认为这个是设置控制本身大小的,其实这个理解有偏差,layout_width这个属性不是控制一个子控件它本身的大小(自身大小由它
自己决定),而是父布局提供给这个控件的显示窗口大小,这里叫申请大小(下面同理)。
然后来讲解详细的计算过程,当LinearLayout包含的视图weight>0时,它会mesure两次(这里以上面第一个布局来讲解)
第一次,计算剩余空间,就是用屏幕的宽度减去子控件申请的宽度。设屏幕宽度为TW,第一个button的申请宽度设为x1,weight为w1,第二个button的申请宽度设为x2,weight为w2,剩余空间设为delta
表达示为 delta = TW - (x1+x2)
第二次,分配空间,即父视图最终提供给子控件的显示窗口的大小,
button1的最终显示大小为 x1+delta*w1/(w1+w2),
button2的最终显示大小为 x2+delta*w2/(w1+w2),
结合上面的实例来说明
上面第一个布局,假设手机分辨率为480x320,btn1申请宽度为fill_parent=320,btn2的申请宽度为wrap_content,这个是会调用控件自身的onMeasure计算出来的,假设计算结果为40,那么
第一步剩余空间就是320-(320+40) = -40,
第二步,分配空间,btn1的最终显示空间为320+(-40)*(1/(1+0)) = 240,btn2的最终显示空间为40+(-40*0/(1+0)) = 40
这样就满足了我们开始的那个需求,
再分析第一个布局,其实本质和第一个布局一样,只是把减去变为加上而已
第一步,剩余空间 = 320-(0+40) = 280;
第二步,分配 btn1最终空间 0+280*1/(1+0) = 280,btn2的最终空间为40+280*0/(0+1)
八、HashMap的遍历效率讨论(转载)
本文转自 http://hi.baidu.com/injava/item/aac168cd66af7a090bd93a3e
HashMap的遍历效率讨论
经常遇到对HashMap中的key和value值对的遍历操作,有如下两种方法:
Map<String, String[]> paraMap = new HashMap<String, String[]>();
................
//第一个循环
Set<String> appFieldDefIds = paraMap.keySet();
for (String appFieldDefId : appFieldDefIds) {
String[] values = paraMap.get(appFieldDefId);
......
}
//第二个循环
for(Entry<String, String[]> entry : paraMap.entrySet()){
String appFieldDefId = entry.getKey();
String[] values = entry.getValue();
.......
}
第一种实现明显的效率不如第二种实现。
分析如下 Set<String> appFieldDefIds = paraMap.keySet(); 是先从HashMap中取得keySet
代码如下:
public Set<K> keySet() {
Set<K> ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}
private class KeySet extends AbstractSet<K> {
public Iterator<K> iterator() {
return newKeyIterator();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return HashMap.this.removeEntryForKey(o) != null;
}
public void clear() {
HashMap.this.clear();
}
}
其实就是返回一个私有类KeySet, 它是从AbstractSet继承而来,实现了Set接口。
再来看看for/in循环的语法
for(declaration : expression)
statement
在执行阶段被翻译成如下各式
for(Iterator<E> #i = (expression).iterator(); #i.hashNext();){
declaration = #i.next();
statement
}
因此在第一个for语句for (String appFieldDefId : appFieldDefIds) 中调用了HashMap.keySet().iterator() 而这个方法调用了newKeyIterator()
Iterator<K> newKeyIterator() {
return new KeyIterator();
}
private class KeyIterator extends HashIterator<K> {
public K next() {
return nextEntry().getKey();
}
}
所以在for中还是调用了
在第二个循环for(Entry<String, String[]> entry : paraMap.entrySet())中使用的Iterator是如下的一个内部类
private class EntryIterator extends HashIterator<Map.Entry<K,V>> {
public Map.Entry<K,V> next() {
return nextEntry();
}
}
此时第一个循环得到key,第二个循环得到HashMap的Entry
效率就是从循环里面体现出来的第二个循环此致可以直接取key和value值
而第一个循环还是得再利用HashMap的get(Object key)来取value值
现在看看HashMap的get(Object key)方法
public V get(Object key) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length); //Entry[] table
Entry<K,V> e = table[i];
while (true) {
if (e == null)
return null;
if (e.hash == hash && eq(k, e.key))
return e.value;
e = e.next;
}
}
其实就是再次利用Hash值取出相应的Entry做比较得到结果,所以使用第一中循环相当于两次进入HashMap的Entry中
而第二个循环取得Entry的值之后直接取key和value,效率比第一个循环高。
其实按照Map的概念来看也应该是用第二个循环好一点,它本来就是key和value的值对,将key和value分开操作在这里不是个好选择。
九、listview反弹实现详解
本文转自 http://jianwang0412.iteye.com/blog/1267885
重写listview,通过监听滑动事件,根据滑动时所处的位置,以及滑动的方向,使用view的内置scrollTo或scrollBy函数来移动view到你手势互动的距离(此处为一半),然后当确定消费了给事件后,又回滚到(0,0)点。当然只有在超出了边界时才回滚。而且回滚的过程由TranslateAnimation来控制,这样的好处在代码的解释中。我是基于网络上的listviewpress改了一些(有几处好像是被篡改了,我又按我的理解将它改正过来,运行后没问题)。一下是关键的代码,整个代码见附件中。有不懂的可以问问,大家互相学习。
- package com.listview.test;
-
- import android.content.Context;
- import android.graphics.Rect;
- import android.util.AttributeSet;
- import android.util.Log;
- import android.view.GestureDetector;
- import android.view.MotionEvent;
- import android.view.View;
- import android.view.GestureDetector.OnGestureListener;
- import android.view.animation.TranslateAnimation;
- import android.widget.ListView;
-
- public class CustomerListView extends ListView {
-
- private Context mContext;
- private boolean outBound = false;
- private int distance;
- private int firstOut;
-
- public CustomerListView(Context c) {
- super(c);
- this.mContext = c;
- }
-
- public CustomerListView(Context c, AttributeSet attrs) {
- super(c, attrs);
- this.mContext = c;
- }
-
- public CustomerListView(Context c, AttributeSet attrs, int defStyle) {
- super(c, attrs, defStyle);
- this.mContext = c;
- }
-
- GestureDetector gestureDetector = new GestureDetector(
- new OnGestureListener() {
-
- public boolean onDown(MotionEvent e) {
-
- return false;
- }
-
- public boolean onFling(MotionEvent e1, MotionEvent e2,
- float velocityX, float velocityY) {
-
- return false;
- }
-
- public void onLongPress(MotionEvent e) {
-
-
- }
-
-
-
-
-
-
-
- public boolean onScroll(MotionEvent e1, MotionEvent e2,
- float distanceX, float distanceY) {
-
-
-
- int firstPos = getFirstVisiblePosition();
- int lastPos = getLastVisiblePosition();
- int itemCount = getCount();
-
-
-
-
- if (outBound && firstPos != 0 && lastPos != (itemCount - 1)) {
- scrollTo(0, 0);
- return false;
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- View firstView = getChildAt(firstPos);
- View lastView = getChildAt(lastPos-1);
-
-
-
-
- if (!outBound) {
- firstOut = (int) e2.getRawY();
- }
- if (firstView != null
- && (outBound || (firstPos == 0
- && firstView.getTop() == 0 && distanceY < 0))) {
- distance = (int) (firstOut - e2.getRawY());
- /**
- * scrollBy中的值带有方向,x若为正,则应该以view中该x点显示在新的原点上,即拿新的点去
- <span style="white-space: pre;"> </span>*重合y轴,就好像整个布局被往左拉动。
- * y为正,则向上滑动|y|距离。负则相反。
- */
- scrollBy(0, distance / 2);
- Log.v("onScroll", "e2.getRawY():"+e2.getRawY());
- Log.v("onScroll", "distance:"+distance);
- Log.v("onScroll", "distanceY:"+distanceY);
- return true;
- }
- if (lastView == null&&(outBound || (lastPos == itemCount - 1 && distanceY > 0))) {
- Log.d("bottom", "bottom");
- distance = (int) (firstOut - e2.getRawY());
- scrollBy(0, distance/2);
- return true;
- }
- return false;
- }
-
- public void onShowPress(MotionEvent e) {
-
-
- }
-
- public boolean onSingleTapUp(MotionEvent e) {
-
- return false;
- }
- });
-
-
-
-
- public boolean dispatchTouchEvent(MotionEvent ev) {
- if(getFirstVisiblePosition()==0){
- int act = ev.getAction();
- if ((act == MotionEvent.ACTION_UP || act == MotionEvent.ACTION_CANCEL)
- && outBound) {
- outBound = false;
- }
- if (!gestureDetector.onTouchEvent(ev)) {
- outBound = false;
- } else {
- outBound = true;
- }
- Rect rect = new Rect();
- getLocalVisibleRect(rect);
-
-
-
- TranslateAnimation am = new TranslateAnimation(0, 0, -rect.top, 0);
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- am.setDuration(300);
- startAnimation(am);
- Log.v("onScroll","after scrollBy top:"+rect.top);
- scrollTo(0, 0);
- getLocalVisibleRect(rect);
- Log.v("onScroll", "new top:"+rect.top);
- }
- Log.d("getLastVisiblePosition()", getLastVisiblePosition()+"");
- Log.d("getCount()", getCount()+"");
- if(getLastVisiblePosition()==getCount()-1){
- int act = ev.getAction();
- if ((act == MotionEvent.ACTION_DOWN || act == MotionEvent.ACTION_CANCEL)
- && outBound) {
- outBound = false;
- }
- if (!gestureDetector.onTouchEvent(ev)) {
- outBound = false;
- } else {
- outBound = true;
- }
- if(outBound){
- Rect rect1 = new Rect();
- getLocalVisibleRect(rect1);
- TranslateAnimation am1 = new TranslateAnimation(0, 0, rect1.top, 0);
- am1.setDuration(300);
- startAnimation(am1);
- scrollTo(0, 0);
- }
- }
- return super.dispatchTouchEvent(ev);
- };
- }
listviewPress.zip (82.6 KB)
十、Android SAX解析之错误纠正!!
本文转自 http://blog.youkuaiyun.com/feng88724/article/details/7013675
在讲这次错误之前,先看一下下面这段代码。 【◆以下解析方法是错误的×】
- import java.util.ArrayList;
- import java.util.List;
-
- import org.xml.sax.Attributes;
- import org.xml.sax.SAXException;
- import org.xml.sax.helpers.DefaultHandler;
-
- import android.util.Log;
-
- public class XmlHandler extends DefaultHandler{
-
- private final String TAG = this.getClass().getSimpleName();
-
-
- private final String TAG_Article = "Article";
- private final String TAG_ArticleID = "ArticleID";
- private final String TAG_Title = "Title";
- private final String TAG_Date = "Date";
- private final String TAG_SmallPictures = "SmallPictures";
- private final String TAG_LargePictures = "LargePictures";
- private final String TAG_Category = "Category";
- private static final String TAG_HeadNote = "HeadNote";
- private static final String TAG_SubTitle = "SubTitle";
- private static final String TAG_Source = "Source";
-
-
- private String currentName;
-
-
- private News news = null;
-
-
- private List<News> newsList = null;
-
-
- private long start_time;
-
- private boolean flag = false;
-
- @Override
- public void characters(char[] ch, int start, int length)
- throws SAXException {
- super.characters(ch, start, length);
-
- if(!flag) {
- return;
- }
-
- String value = new String(ch, start, length);
- Log.d(TAG, "Element: " + currentName + " Element Value: " + value);
- if(value != null) {
- if(TAG_ArticleID.equals(currentName)) {
- news.setArticleId(value);
- } else if(TAG_Title.equals(currentName)) {
- news.setTitle(value);
- } else if(TAG_Date.equals(currentName)) {
- news.setDate(value);
- } else if(TAG_Category.equals(currentName)) {
- news.setCategory(value);
- } else if(TAG_SmallPictures.equals(currentName)) {
- news.setSmallPicture(value);
- } else if(TAG_LargePictures.equals(currentName)) {
- news.setLargePicture(value);
- } else if(TAG_HeadNote.equals(currentName)) {
- news.setHeadNote(value);
- } else if(TAG_SubTitle.equals(currentName)) {
- news.setSubTitle(value);
- } else if(TAG_Source.equals(currentName)) {
- news.setSource(value);
- }
- }
- }
-
- @Override
- public void startDocument() throws SAXException {
- super.startDocument();
-
- start_time = System.currentTimeMillis();
- newsList = new ArrayList<News>();
- }
-
- @Override
- public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
- super.startElement(uri, localName, qName, attributes);
- this.currentName = localName;
- flag = true;
- if(TAG_Article.equals(localName)) {
- news = new News();
- }
- }
-
- @Override
- public void endElement(String uri, String localName, String qName)
- throws SAXException {
- super.endElement(uri, localName, qName);
- flag = false;
-
- if(TAG_Article.equals(localName)) {
- newsList.add(news);
- }
- }
-
- @Override
- public void endDocument() throws SAXException {
- super.endDocument();
-
- long end = System.currentTimeMillis();
- Log.d(TAG, "Parse List's Xml Cost: " + (end - start_time) + " !!");
- }
- }
Baidu 或者 Google 一下 “Android Sax 解析” , 给出的Sample无一例外都是如此。 坑爹啊... 甚至连有些书籍中都是这么写的, 比如《Android开发入门与实践》。(本书亲自确认过,其他书情况不详)
没错, 一般情况下,这么写是可以的, 而且在大多数情况下解析出来也是正确的。 但是就是偶尔会出错, 这个时候通常你都莫不着头脑, 怎么回事? 数据没错啊,解析部分代码貌似也没问题.. 真是奇了怪了。 其实问题都出在上面那段代码上!!
大家都认为 SAX 解析过程大致如下:
startDocument -> startElement -> characters -> endElement -> endDocument
没错,就是这样, startElement 读取起始标签, endElement 读取结束标签,characters 呢?当然是读取其值, 这没错,但是大家都天真的以为 characters 只执行一次,并且一次就读取了全部内容。错就错在这!
其实characters 是很有可能会执行多次的,当遇到内容中有回车,\t等等内容时,它很有可能就执行多次。 有的人可能会说,那我没有这些是不是就只执行一次了? 看下我实测结果:
测试用XML如下:
- <News>
- <Article>
- <ArticleID>1000555</ArticleID>
- <Title><![CDATA[ 郑州“亚洲第一桥”通车6年成危桥 ]]></Title>
- <Date>2011-11-25 14:23:52</Date>
- <SmallPictures>livenews/images/s20.png</SmallPictures>
- <LargePictures>livenews/images/l20.png</LargePictures>
- <Category>闻天下</Category>
- <HeadNote></HeadNote>
- <SubTitle></SubTitle>
- <Author></Author>
- <Source>人民日报</Source>
- <Abstract></Abstract>
- </Article>
- <Article>
- <ArticleID>1000554</ArticleID>
- <Title><![CDATA[ 内地事业单位拟设统一工资制度 ]]></Title>
- <Date>2011-11-25 14:22:33</Date>
- <Category><![CDATA[ 闻天下 ]]></Category>
- <HeadNote></HeadNote>
- <SubTitle></SubTitle>
- <Author></Author>
- <Source></Source>
- <Abstract></Abstract>
- </Article>
- <Article>
- <ArticleID>1000553</ArticleID>
- <Title></Title>
- <Date>2011-11-25 14:21:23</Date>
- <SmallPictures>livenews/images/s21.png</SmallPictures>
- <LargePictures>livenews/images/l21.png</LargePictures>
- <Category><![CDATA[ 星娱乐 ]]></Category>
- <HeadNote></HeadNote>
- <SubTitle></SubTitle>
- <Author></Author>
- <Source><![CDATA[ 凤凰网综合 ]]></Source>
- <Abstract></Abstract>
- </Article>
- <News>
可以很明显的看到,在解析 <ArticleID>1000553</ArticleID> 这一段时, characters执行了两次,将内容"1000553"分两次读取.. 用上面那种方式的最终结果就是 ArticleID = 00553 了。 那如果你的应用需要根据这个id 进一步获取内容岂不是死翘翘了?(比如这边根据id获取新闻详细内容)
好了,废话不多说了,看下正确的写法! 【★以下解析方法才是正确的 √ 】
- import java.util.ArrayList;
- import java.util.List;
-
- import org.xml.sax.Attributes;
- import org.xml.sax.SAXException;
- import org.xml.sax.helpers.DefaultHandler;
-
- import android.util.Log;
-
- public class XmlHandler extends DefaultHandler{
-
- private final String TAG = this.getClass().getSimpleName();
-
-
- private final String TAG_Article = "Article";
- private final String TAG_ArticleID = "ArticleID";
- private final String TAG_Title = "Title";
- private final String TAG_Date = "Date";
- private final String TAG_SmallPictures = "SmallPictures";
- private final String TAG_LargePictures = "LargePictures";
- private final String TAG_Category = "Category";
- private static final String TAG_HeadNote = "HeadNote";
- private static final String TAG_SubTitle = "SubTitle";
- private static final String TAG_Source = "Source";
-
-
- private News news = null;
-
-
- private List<News> newsList = null;
-
-
- private long start_time;
-
-
- private StringBuilder sb = new StringBuilder();
-
- @Override
- public void characters(char[] ch, int start, int length)
- throws SAXException {
- super.characters(ch, start, length);
-
-
- sb.append(ch, start, length);
- }
-
- @Override
- public void startDocument() throws SAXException {
- super.startDocument();
-
- start_time = System.currentTimeMillis();
- newsList = new ArrayList<News>();
- }
-
- @Override
- public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
- super.startElement(uri, localName, qName, attributes);
-
- sb.setLength(0);
- if(TAG_Article.equals(localName)) {
- news = new News();
- }
- }
-
- @Override
- public void endElement(String uri, String localName, String qName)
- throws SAXException {
- super.endElement(uri, localName, qName);
-
-
- String value = sb.toString();
-
- if(TAG_ArticleID.equals(localName)) {
- news.setArticleId(value);
- } else if(TAG_Title.equals(localName)) {
- news.setTitle(value);
- } else if(TAG_Date.equals(localName)) {
- news.setDate(value);
- } else if(TAG_Category.equals(localName)) {
- news.setCategory(value);
- } else if(TAG_SmallPictures.equals(localName)) {
- news.setSmallPicture(value);
- } else if(TAG_LargePictures.equals(localName)) {
- news.setLargePicture(value);
- } else if(TAG_HeadNote.equals(localName)) {
- news.setHeadNote(value);
- } else if(TAG_SubTitle.equals(localName)) {
- news.setSubTitle(value);
- } else if(TAG_Source.equals(localName)) {
- news.setSource(value);
- }
- if(TAG_Article.equals(localName)) {
- newsList.add(news);
- }
- }
-
- @Override
- public void endDocument() throws SAXException {
- super.endDocument();
-
- long end = System.currentTimeMillis();
- Log.d(TAG, "Parse List's Xml Cost: " + (end - start_time) + " !!");
- }
- }
归纳为三点:
1.startElement的时候, new StringBuilder(); 或者 sb.setLength(0); (我建议后者)
2.characters的时候,sb.append(ch, start, length);
3.endElement的时候,sb.toString(); 此时StringBuilder中的内容才是解析的结果
通过这种方法就不会再有数据离奇丢失的情况了(同时也不需要像错误方法那样再设个currentTag之类的了,逻辑繁杂了,还出错)!
希望大家可以尽早看到这篇文章,不要继续被吭了!!!