Android AChartEngine

最近一段时间完成“体重记录”的功能,需要实现日历和曲线的效果。也花费不少精力吧,这里就先把曲线的实现分享出来,俗话说的好:“好记忆不如烂笔头”!

AChartEngine是什么?

AChartEngine是一个android应用的图表库,他支持一些常见的一些图表,如线状图,区域图,散点图,时间图,柱状图,饼状图,气泡图等。当然这次只用到了线状图。项目地址在http://code.google.com/p/achartengine/

下面先看下这次项目中实现的效果吧:

实现

总的来说,AChartEngine提供的api还是很全的,使用起来是很方便的,但是唯一的缺点就是api文档描述的不够详细,很多自己想要的效果都只能自己根据api的命名去推测,更有甚者得必须自己亲自一点点尝试才能实现出自己想要的效果,为了以后用到,这次也在代码中用到的接口表明了清晰的注释,废话不多说,直接上代码。

public class WeightCurveActivity extends ActivityBase {

	static final String TAG = WeightCurveActivity.class.getName();

	private LinearLayout rootLayout;
	private XYMultipleSeriesRenderer mRenderer;
	private XYMultipleSeriesDataset mDataset = new XYMultipleSeriesDataset();
	
	private int month;
	private Date date;
	private ArrayList<WeightRecord> records;
	private double[] xValues;
	private double[] yValues;

	public void onCreate(Bundle outState) {
		super.onCreate(outState);
		setContentView(R.layout.weight_curve);
		
		handleIntent();
		initRender();
		initData();
		initUI();
	}
	
	private void handleIntent() {
		String dateString = getIntent().getStringExtra(Const.DATE);
		date = DateHelper.parseString(dateString);
	}
	
	private void initRender() {
		mRenderer = buildRenderer();
		setChartSettings(0, 31, 30, 120);
	}
	
	private void initData() {
		month = DateHelper.getMonth(date);
		WeightRecordDao dao = new WeightRecordDao(this);
		records = dao.getMonthLists(date);
		Helper.showLog(TAG, records.size());
		dao.closeDB();
		initValues();
	}
	
	private void initValues() {
		int count = records.size();
		if (count > 0) {
			xValues = new double[count];
			yValues = new double[count];
			for (int i = 0; i < count; i ++) {
				WeightRecord record = records.get(i);
				xValues[i] = DateHelper.getDay(record.record_on);
				yValues[i] = (Math.round(record.weight * 10) / 10.0);
			}
			setXLabel();
		}
	}
	
	private void initUI() {
		addXYSeries(0);

		rootLayout = (LinearLayout) findViewById(R.id.root);
		View view = ChartFactory.getLineChartView(this, mDataset, mRenderer);
		rootLayout.addView(view);
	}
	
	private void setXLabel() {
		mRenderer.setXLabels(0); // 设置X轴标签不显示
		int length = xValues.length;
		for (int i = 0; i < length; i++) {
			mRenderer.addXTextLabel(i * 3 + 1, month + "/" + (int)xValues[i]);
		}
	}
	
	private XYMultipleSeriesRenderer buildRenderer() {
		XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer();
		renderer.setAxisTitleTextSize(16); // 设置坐标轴字体大小
		renderer.setChartTitleTextSize(20); // 设置标题大小
		renderer.setLabelsTextSize(20); // 设置标签字体大小
		renderer.setLegendTextSize(15); // 设置底部曲线说明字体大小
		renderer.setShowGridX(true); // 设置X方向表格显示
		renderer.setShowLegend(false); // 设置底部曲线说明显示
		renderer.setGridColor(Color.LTGRAY); // 设置表格颜色
		renderer.setPointSize(5f);
		renderer.setMargins(new int[] { 30, 40, 10, 30 });
		renderer.setPanEnabled(true, false); // 设置曲线可滑动

		renderer.setApplyBackgroundColor(true); // 设置图表背景
		renderer.setBackgroundColor(Color.TRANSPARENT);

		renderer.setChartTitle("体重曲线");
		renderer.setXTitle("日期");
		renderer.setYTitle("体重");

		renderer.setXLabelsColor(getResources().getColor(
				R.color.main_font_color));
		renderer.setXLabelsAlign(Align.CENTER);
		renderer.setXLabelsPadding(5);
		renderer.setYLabelsColor(0,
				getResources().getColor(R.color.main_font_color));
		renderer.setYLabelsPadding(5);
		renderer.setYLabelsAlign(Align.RIGHT);

		renderer.setAxesColor(Color.GRAY); // 设置坐标轴颜色
		renderer.setMarginsColor(getResources().getColor(R.color.main_bg_color)); // 设置图表周围颜色
		
		renderer.setLabelsColor(Color.GRAY); // 设置标签颜色
		
		XYSeriesRenderer r = new XYSeriesRenderer();
		r.setColor(getResources().getColor(R.color.stress_font_color));
		r.setFillPoints(true);
		r.setPointStyle(PointStyle.CIRCLE);
		renderer.addSeriesRenderer(r);
		r.setDisplayChartValues(true); // 设置显示图表值
		r.setDisplayChartValuesDistance(1);
		r.setChartValuesTextSize(16);
		r.setChartValuesSpacing(10);
		r.setHighlighted(true);
		return renderer;
	}

	private void setChartSettings(double xMin, double xMax, double yMin,
			double yMax) {
		mRenderer.setXAxisMin(xMin); // 设置X轴最小值
		mRenderer.setXAxisMax(xMax); // 设置X轴最大值
		mRenderer.setYAxisMin(yMin); // 设置Y轴最小值
		mRenderer.setYAxisMax(yMax); // 设置Y轴最大值
	}

	private void addXYSeries(int scale) {
		XYSeries series = new XYSeries("", scale);
		if (records.size() > 0) {
			int seriesLength = xValues.length;
			for (int k = 0; k < seriesLength; k++) {
				series.add(k * 3 + 1, yValues[k]);
			}
		}
		mDataset.addSeries(series);
	}
}

现在看来代码倒是很简单,但是为了实现现在这个样子,当初费了不少精力来一步步尝试。当然AChartEngine能实现的不止这些,可以充分发挥自己的想象力与创造力,实现更加复杂的效果与功能。


设计模式之单例模式

单例模式解释

单例模式是一种对象创建性模式,使用单例模式,可以保证为一个类只生成唯一的实例对象。也就是说,在整个程序空间中,该类只存在一个实例对象。

单例模式的要点有三个:一是某个类只能有一个实例;而是必须自行创建整个实例;三是它必须自行向整个系统提供整个实例。

英文定义为:Ensure a class only has one instance, and provide a global point of access to it.

单例模式深入分析

单例模式适合一个类只有一个实例的情况, 比如窗口管理器,打印缓冲池和文件系统,它们都是原型的例子。典型的情况是,那些对象的类型被遍及一个软件系统的不同对象访问,因此需要一个全局的访问指针,这便是总所周知的单例模式的应用。当然这只有在你确信你不再需要任何多于一个的实例的情况下。

使用场景及代码实现

下面就举例来说明下:

单例模式的第一个版本,“饿汉式”,也就是当类加载进来的就立即实例化对象,但是这种方式比较的消耗计算机资源。如下:

public class Foo {
    // 在类被加载进入内存的时候就创建单一的Foo对象
    public static final Foo foo = new Foo();

    // 构造函数私有化
    private Foo() {

    }

    // 提供一个全局的静态方法
    public static Foo getFoo() {
        return foo;
    }
}

单例模式的第二个版本,“懒汉式”,在单线程下能够非常好的工作,但是在多线程下存在线程安全问题,如下:

// 这种方式在需要使用的时候才实例化
public class Foo {
    private static Foo foo;

    // 构造函数私有化
    private Foo() {

    }

    // 提供一个全局的静态方法
    public static Foo getFoo() {
        if (foo == null) {
            foo = new Foo();
        }
        return foo;
    }
}

单例模式的第三个版本,为解决多线程问题,采用了对函数进行同步的方式,但是比较浪费资源,因为每次都要进行同步检查,而实际中真正需要检查只是第一次实例化的时候,如下:

public class Foo {
    private static Foo foo;

    // 构造函数私有化
    private Foo() {
    }

    // 提供一个全局的静态方法,使用同步方法
    public static synchronized Foo getFoo() {
        if (foo == null) {
            foo = new Foo();
        }
        return foo;
    }
}

单例模式的第四个版本,既解决了”懒汉式“的多线程问题,又解决了资源浪费的现象,看上去是一种不错的选择,如下:

public class Foo {
    private static Foo foo;

    // 构造函数私有化
    private Foo() {
    }

    // 提供一个全局的静态方法
    public static Foo getFoo() {
        if (foo == null) {
            synchronized (Foo.class) {
                if (foo == null) {
            	    foo = new Foo();
                }
            }
        }
        return foo;
    }
}

单例模式的优缺点分析

优点:客户端使用单例模式的实例的时候,只需要调用一个单一的方法即可生成一个唯一的实例,有利于节约资源。

缺点:首先单例模式很难实现序列化,这就导致采用单例模式的类很难被持久化,当然也很难通过网络传输;其次由于单例采用静态方法,无法在继承结构中使用。


Android手势

之前做的App是完全没有任何手势支持的,对于现在的程序来说,如果没有一些手势的支持,感觉实在是有点落后了,支持手势的App才叫cool。于是在这次重新搭建ifood for android框架的同时下决心让自己的App完全支持手势。下面就来看下自己实现的一个全局滑动切换窗口的例子。

在android系统中,手势的识别是通过 GestureDetector.OnGestureListener接口来实现的。如果要自定义手势需要重写这个接口里的一些方法,废话不多说,下面上代码:

自定义的一个GestureLisntener:

MyGestureListener.java
public class MyGestureListener implements OnGestureListener {

	static final String TAG = "MyGestureListener";

	private static final int SWIPE_MAX_OFF_PATH = 100;
	private static final int SWIPE_MIN_DISTANCE = 100;
	private static final int SWIPE_THRESHOLD_VELOCITY = 100;
	
	public Context context;
	
	public MyGestureListener(Context context) {
		this.context = context;
	}

	@Override
	public boolean onDown(MotionEvent e) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public void onShowPress(MotionEvent e) {
		// TODO Auto-generated method stub
		Log.e(TAG, "onShowPress");
	}

	@Override
	public boolean onSingleTapUp(MotionEvent e) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
			float distanceY) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public void onLongPress(MotionEvent e) {
		// TODO Auto-generated method stub
		Log.e(TAG, "onLongPress");
	}

	@Override
	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
			float velocityY) {
		if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
			return false;

		if ((e1.getX() - e2.getX()) > SWIPE_MIN_DISTANCE
				&& Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
			Log.e(TAG, "onFling left");

		} else if ((e2.getX() - e1.getX()) > SWIPE_MIN_DISTANCE
				&& Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
			Log.e(TAG, "onFling right");
			((Activity) context).finish();
			
		}
		return true;
	}

}

在这个类中的onFling()方法中从左向右滑动时实现了界面的切换,如果有更复杂的手势支持,同样可以在这个基类中进行添加。

接下来新建一个GestureActivity实现Gesture滑动切换界面,让支持手势的Activity继承它,这样就继承了它的手势支持功能,提高代码复用。

GestureActivity.java
public class GestureActivity extends ActivityBase {

	MyGestureListener listener = new MyGestureListener(this);
	protected GestureDetector gestureDetector = new GestureDetector(listener);
	
	public boolean onTouchEvent(MotionEvent event) {
        if (gestureDetector.onTouchEvent(event))
            return true;
        else  
            return false;
    }
	
}

这样就实现了一个简单的滑动切换页面的框架,如果想支持更多的手势,只需要重写MyGestureListener的方法就可以了。

不过不要高兴的太早,在一般的Activity手势支持是正常的,可是碰到一些包含ScrollView或者ListView的Activity时,手势就不相应了。原因是因为这些滑动的组件本身就已经具有了手势的支持,这样就会产生了冲突,导致自定义的手势没有被识别到。google了很久,似乎也没个具体的方法,后来看到说用dispatchTouchEvent(MotionEvent ev) 的方法,果然可以。于是在GestureActivity里就多了这样一个方法:

public boolean dispatchTouchEvent(MotionEvent ev) {
    gestureDetector.onTouchEvent(ev);
    return super.dispatchTouchEvent(ev);
}

此时再试一下,果然所有Activity都实现了自定义的手势事件。但是为什么加上这个方法就可以了呢,必须要搞明白。

android中的事件类型分为按键事件和屏幕触摸事件,Touch事件是屏幕触摸事件的基础事件,有必要对它进行深入的了解。

一个最简单的屏幕触摸动作触发了一系列Touch事件:ACTION_DOWN->ACTION_MOVE->ACTION_MOVE->ACTION_MOVE…->ACTION_MOVE->ACTION_UP

当屏幕中包含一个ViewGroup,而这个ViewGroup又包含一个子view,这个时候android系统如何处理Touch事件呢?到底是ViewGroup来处理Touch事件,还是子view来处理Touch事件呢?答案是:不一定。

android系统中的每个View的子类都具有下面三个和TouchEvent处理密切相关的方法:

1.public boolean dispatchTouchEvent(MotionEvent ev) 这个方法用来分发TouchEvent

2.public boolean onInterceptTouchEvent(MotionEvent ev) 这个方法用来拦截TouchEvent

3.public boolean onTouchEvent(MotionEvent ev) 这个方法用来处理TouchEvent

当TouchEvent发生时,首先Activity将TouchEvent传递给最顶层的View, TouchEvent最先到达最顶层 view 的 dispatchTouchEvent ,然后由 dispatchTouchEvent 方法进行分发,如果dispatchTouchEvent返回true ,则交给这个view的onTouchEvent处理,如果dispatchTouchEvent返回 false ,则交给这个 view 的 interceptTouchEvent 方法来决定是否要拦截这个事件,如果 interceptTouchEvent 返回 true ,也就是拦截掉了,则交给它的 onTouchEvent 来处理,如果 interceptTouchEvent 返回 false ,那么就传递给子 view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。如果事件传递到某一层的子 view 的 onTouchEvent 上了,这个方法返回了 false ,那么这个事件会从这个 view 往上传递,都是 onTouchEvent 来接收。而如果传递到最上面的 onTouchEvent 也返回 false 的话,这个事件就会“消失”,而且接收不到下一次事件。

看到这终于清楚了上面的疑问,dispatchTouchEvent()方法直接将触摸事件交给了gestureDetector的触摸事件,这样就解决了冲突问题。


Android ActivityGroup

在android应用中底部导航栏可以说是十分常见的,如新浪微博,微信等都是这种设计,大家在做这种应用第一反应就是使用TabActivity,今天就来分享下如何用ActivityGroup来代替TabActivity,以及这样使用的优点。

ActivityGroup是Google提供的一个非常优秀的API,而TabActivity是ActivityGroup唯一的一个子类。

ActivityGroup的优点

首先来说ActivityGroup的优秀之处以及它的必要性,它为开发者提供了一种可能,这种可能不将Activity作为屏幕的顶级元素(Context)呈现,而是嵌入到ActivityGroup当中。这是一种极大的飞跃,它将场景(Context)细分化了,ActivityGroup是一个主场景,而用户可以通过导航按钮来切换想要的子场景。如使用微博功能,它是一个相当宏大的场景,具有看最新的广播信息、自己发微博、修改资料等子场景,用户可以通过按钮来切换到想要的子场景,而这个子场景仍活动于主场景之中。让一个主场景能拥有多个逻辑处理模块,主场景不再负责子场景逻辑,主场景只负责切换场景的逻辑,即每一个Activity(子场景)拥有一个逻辑处理模块,一个ActivityGroup有多个Activity,却不干预Activity的逻辑,这无疑细分化和模块化了逻辑代码。ActivityGroup和它将要内嵌的Activity所要实现的功能完全可以只用一个Activity来完成,你可以试想,当你把一个ActivityGroup和它所拥有的Activity的逻辑代码放在一个Activity中时,那这个Activity会拥有多少行代码,为维护带来非常的不便。

TabActivity的不足

再来说说TabActivity的不足之处,首先,TabActivity自己独有的视图几乎没人使用(也就是难看的标签页按钮形式),大多数开发者用到的特性几乎都是从ActivityGroup继承下来的。还有就是TabActivity的强制依赖关系,它的布局文件必须将TabHost作根标签,并且id必须为”@android:id/tabhost”,必须有TabWidget标签,且它的id必须是”@android:id/tabs”,还有加载Activity的View容器,id必须为@android:id/tabcontent。光是强制依赖关系,我就觉得不是很舒服。而且在android3.0以后已经建议用Fragment来代替TabActivity了。只是由于目前为了要兼容3.0之前的版本,Fragment还没有被开发者们所普及。

下面就来看下这次食物库重构后的用ActivityGroup来实现的主页面架构吧:

MainActivityGroup.java

public class MainActivityGroup extends ActivityGroup implements
		OnCheckedChangeListener {
	private static final String RECORD = "record";
	private static final String CATEGORY = "category";
	private static final String MORE = "more";

	private ActivityGroupManager manager = null;
	private FrameLayout container = null;
	private RadioGroup radioGroup;

	

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.test_main);

		initView();
	}

	private void initView() {
		radioGroup = (RadioGroup) findViewById(R.id.main_radio);
		radioGroup.setOnCheckedChangeListener(this);

		manager = new ActivityGroupManager();
		container = (FrameLayout) findViewById(R.id.container);
		manager.setContainer(container);
		switchActivity(ActivityGroupManager.RECORD_ACTIVITY_VIEW, RECORD, RecordActivity.class);
	}

	@Override
	public void onCheckedChanged(RadioGroup group, int checkedId) {
		switch (checkedId) {
		case R.id.record_button:
			switchActivity(ActivityGroupManager.RECORD_ACTIVITY_VIEW, RECORD, RecordActivity.class);
			break;
		case R.id.category_button:
			switchActivity(ActivityGroupManager.CATEGORY_ACTIVITY_VIEW, CATEGORY, CategoryActivity.class);
			break;
		case R.id.more_button:
			switchActivity(ActivityGroupManager.MORE_AVTIVITY_VIEW, MORE, MoreActivity.class);
			break;
		default:
			break;
		}
	}
	
	private View getActivityView(String activityName, Class<?> activityClass) {
		return getLocalActivityManager().startActivity(activityName,
				new Intent(MainActivityGroup.this, activityClass))
				.getDecorView();
	}

	private void switchActivity(int num, String activityName, Class<?> activityClass) {
		manager.showContainer(num, getActivityView(activityName, activityClass));
	}

}

可以看到在“主场景”MainActivityGroup中基本没有任何逻辑代码,只有各个“子场景”切换的逻辑,而子场景的切换用了一个ActivityGroupManager类来管理,这样又起到了代码分离的作用,

ActivityGroupManager.java
public class ActivityGroupManager {

	private static final String TAG = "frag_manager";

	public static final int RECORD_ACTIVITY_VIEW = 0;
	public static final int CATEGORY_ACTIVITY_VIEW = 1;
	public static final int MORE_AVTIVITY_VIEW = 2;

	private HashMap<Integer, View> hashMap;
	private ViewGroup container;

	public ActivityGroupManager() {
		hashMap = new HashMap<Integer, View>();
	}

	public void setContainer(ViewGroup container) {
		this.container = container;
	}
	
	public void showContainer(int num, View view) {
		if (!hashMap.containsKey(num)) {
			hashMap.put(num, view);
			container.addView(view);
		}

		for (Iterator<Integer> iter = hashMap.keySet().iterator(); iter.hasNext();) {
			Object key = iter.next();
			View v = hashMap.get(key);
			v.setVisibility(View.INVISIBLE);
		}
		
		view.setVisibility(View.VISIBLE);
	}
}

值得一说的是在ActivityGroupManager类中的showContainer ()方法并没有像网上的做法这样:

container.removeAllViews();
container.addView(view);

这种做法看似代码逻辑更简单,但是这样就会导致每次切换“子场景”的时候都会把已经加载过的View remove掉,一方面性能有所欠缺,另一个方面“子场景”的状态无法记住。而现在的做法就很好的解决了上面的问题。

好了,然后就是各个“子场景”(Activity)的代码了,每个“子场景”的逻辑各自独立,这里就不上代码了。

这里还有一个需要提到的是底部导航栏的实现,由于android没有像iOS那样预定好的组件,只有自己定义一个布局了,这里我用到的是用RadioGroup来实现类似微信的底部导航栏效果,下面是布局代码,在其他使用到的地方直接include进来就可以了。

<?xml version="1.0" encoding="utf-8"?>
<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_radio"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|center"
    android:layout_marginBottom="-20dp"
    android:gravity="center"
    android:orientation="horizontal"
    android:padding="0dp" >

    <RadioButton
        android:id="@+id/record_button"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_weight="1.0"
        android:background="@null"
        android:button="@null"
        android:checked="true"
        android:drawableTop="@drawable/main_tab_record_selector"
        android:gravity="center"
        android:tag="record" />

    <RadioButton
        android:id="@+id/category_button"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1.0"
        android:background="@null"
        android:button="@null"
        android:drawableTop="@drawable/main_tab_category_selector"
        android:gravity="center_horizontal"
        android:tag="category" />

    <RadioButton
        android:id="@+id/more_button"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1.0"
        android:background="@null"
        android:button="@null"
        android:drawableTop="@drawable/main_tab_more_selector"
        android:gravity="center_horizontal"
        android:tag="more" />

</RadioGroup>

Android Handler

Handler的定义

Handler主要接受子线程发送的数据, 并用此数据配合主线程更新UI。 当应用程序启动时,Android首先会开启一个主线程 (也就是UI线程) ,主线程为管理界面中的UI控件,进行事件分发, 比如说,你要是点击一个 Button ,Android会分发事件到Button上,来响应你的操作。如果此时需要一个耗时的操作,例如: 联网读取数据,或者读取本地较大的一个文件的时候,你不能把这些操作放在主线程中,如果你放在主线程中的话,界面会出现假死现象, 如果5秒钟还没有完成的话,会收到Android系统的一个错误提示 “强制关闭”。

这个时候我们需要把这些耗时的操作,放在一个子线程中,因为子线程涉及到UI更新,Android主线程不是线程安全的,也就是说,更新UI只能在主线程中更新,子线程中操作是危险的。这个时候,Handler就出现了,来解决这个复杂的问题。Handler运行在主线程中(UI线程中),它与子线程可以通过Message对象来传递数据,这个时候,Handler就承担着接受子线程传过来的(子线程用sedMessage()方法传递)Message对象(里面包含数据),把这些消息放入主线程队列中,配合主线程进行更新UI。

Handler的一些特点

Handler 为Android操作系统中的线程通信工具,包为android.os.Handler。

与Handler绑定的有两个队列,一个为消息队列,另一个为线程队列。Handler可以通过这两个队列来分别:

1.发送、接受、处理消息–消息队列;

2.启动、结束、休眠线程–线程队列;

Android OS中,一个进程被创建之后,主线程(可理解为当前Activity)创建一个消息队列,这个消息队列维护所有顶层应用对象(Activities, Broadcast receivers等)以及主线程创建的窗口。你可以在主线程中创建新的线程,这些新的线程都通过Handler与主线程进行通信。通信通过新线程调用 Handler的post()方法和sendMessage()方法实现,分别对应功能:

1.post() 将一个线程加入线程队列;

2.sendMessage() 发送一个消息对象到消息队列;

当然,post()方法还有一些变体,比如 post(Runnable) postAtTime(Runnable,long) postDelayed(Runnable long) sendEmptyMessage(int) sendMessage(Message) sendMessageAtTime(Message,long) sendMessageDelayed(Message,long)

Handler实例

public class MyHandlerActivity extends Activity {
    Button button;
    MyHandler myHandler;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.handlertest);

        button = (Button) findViewById(R.id.button);
        myHandler = new MyHandler();
        // 当创建一个新的Handler实例时, 它会绑定到当前线程和消息的队列中,开始分发数据
        // Handler有两个作用, (1) : 定时执行MessageRunnalbe 对象
        // (2): 让一个动作,在不同的线程中执行.

        // 它安排消息,用以下方法
        // post(Runnable)
        // postAtTime(Runnable,long)
        // postDelayed(Runnable,long)
        // sendEmptyMessage(int)
        // sendMessage(Message);
        // sendMessageAtTime(Message,long)
        // sendMessageDelayed(Message,long)
      
        // 以上方法以 post开头的允许你处理Runnable对象
        //sendMessage()允许你处理Message对象(Message里可以包含数据,)

        MyThread m = new MyThread();
        new Thread(m).start();
    }

    /**
    * 接受消息,处理消息 ,此Handler会与当前主线程一块运行
    * */

    class MyHandler extends Handler {
        public MyHandler() {
        }

        public MyHandler(Looper L) {
            super(L);
        }

        // 子类必须重写此方法,接受数据
        @Override
        public void handleMessage(Message msg) {
            // TODO Auto-generated method stub
            Log.d("MyHandler", "handleMessage......");
            super.handleMessage(msg);
            // 此处可以更新UI
            Bundle b = msg.getData();
            String color = b.getString("color");
            MyHandlerActivity.this.button.append(color);

        }
    }

    class MyThread implements Runnable {
        public void run() {

            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            Log.d("thread.......", "mThread........");
            Message msg = new Message();
            Bundle b = new Bundle();// 存放数据
            b.putString("color", "我的");
            msg.setData(b);

            MyHandlerActivity.this.myHandler.sendMessage(msg); // Handler发送消息,更新UI

        }
    }

}

简单的说,Activity的onCreate方法里启动一个线程,在这个线程的run方法里使用一个Message对象并使用Handler的sendMessage方法发送到队列中,最后在Activity里new一个Handler的内部类实现handMessage方法,使用这个方法把队列中的Message对象取出来以实现异步操作。

然后是post的例子,这里稍微说一下,直接使用new Handler().post(Runnable)这样的写法并没有新开线程,也就是说依然是在主线程中执行,相当于简单调用了线程的run方法而不是start方法。这个有人说是android的bug,解决方案是这样使用:

HandlerThread handlerThread = new HandlerThread("myHandlerThread");  
handlerThread.start();  
handler = new Handler(handlerThread.getLooper());

来看一个完整的post例子:

public class MyThread extends Activity {    
    private Handler handler = null;    
    @Override    
    public void onCreate(Bundle savedInstanceState) {    
        super.onCreate(savedInstanceState);    
        HandlerThread handlerThread = new HandlerThread("myHandlerThread");    
        handlerThread.start();    
        handler = new Handler(handlerThread.getLooper());    
        handler.post(new MyRunnable());    
        System.out.println("Oncreate---The Thread id is :"    
                + Thread.currentThread().getId());    
        setContentView(R.layout.main);    
    }    
    private class MyRunnable implements Runnable {    
        public void run() {    
            System.out.println("Runnable---The Thread is running");    
            System.out.println("Runnable---The Thread id is :"    
                    + Thread.currentThread().getId());    
            try {    
                Thread.sleep(6000);    
            } catch (InterruptedException e) {    
                // TODO Auto-generated catch block    
                e.printStackTrace();    
            }    
        }    
    }    
}

在这个demo中,用到了HandlerThread,在HandlerThread对象中可以通过getLooper方法获取一个Looper对象控制句柄,我们可以将其这个Looper对象映射到一个Handler中去来实现一个线程同步机制。于是就有以下结果;

1:控制台的输出:

Oncreate—The Thread id is :1

Runnable—The Thread is running

Runnable—The Thread id is :10

2:程序启动后,我们立刻看到main.xml中的内容。 这样就达到了多线程的结果。