最近一段时间完成“体重记录”的功能,需要实现日历和曲线的效果。也花费不少精力吧,这里就先把曲线的实现分享出来,俗话说的好:“好记忆不如烂笔头”!
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;
}
}
优点:客户端使用单例模式的实例的时候,只需要调用一个单一的方法即可生成一个唯一的实例,有利于节约资源。
缺点:首先单例模式很难实现序列化,这就导致采用单例模式的类很难被持久化,当然也很难通过网络传输;其次由于单例采用静态方法,无法在继承结构中使用。
之前做的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应用中底部导航栏可以说是十分常见的,如新浪微博,微信等都是这种设计,大家在做这种应用第一反应就是使用TabActivity,今天就来分享下如何用ActivityGroup来代替TabActivity,以及这样使用的优点。
ActivityGroup是Google提供的一个非常优秀的API,而TabActivity是ActivityGroup唯一的一个子类。
首先来说ActivityGroup的优秀之处以及它的必要性,它为开发者提供了一种可能,这种可能不将Activity作为屏幕的顶级元素(Context)呈现,而是嵌入到ActivityGroup当中。这是一种极大的飞跃,它将场景(Context)细分化了,ActivityGroup是一个主场景,而用户可以通过导航按钮来切换想要的子场景。如使用微博功能,它是一个相当宏大的场景,具有看最新的广播信息、自己发微博、修改资料等子场景,用户可以通过按钮来切换到想要的子场景,而这个子场景仍活动于主场景之中。让一个主场景能拥有多个逻辑处理模块,主场景不再负责子场景逻辑,主场景只负责切换场景的逻辑,即每一个Activity(子场景)拥有一个逻辑处理模块,一个ActivityGroup有多个Activity,却不干预Activity的逻辑,这无疑细分化和模块化了逻辑代码。ActivityGroup和它将要内嵌的Activity所要实现的功能完全可以只用一个Activity来完成,你可以试想,当你把一个ActivityGroup和它所拥有的Activity的逻辑代码放在一个Activity中时,那这个Activity会拥有多少行代码,为维护带来非常的不便。
再来说说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>
Handler主要接受子线程发送的数据, 并用此数据配合主线程更新UI。 当应用程序启动时,Android首先会开启一个主线程 (也就是UI线程) ,主线程为管理界面中的UI控件,进行事件分发, 比如说,你要是点击一个 Button ,Android会分发事件到Button上,来响应你的操作。如果此时需要一个耗时的操作,例如: 联网读取数据,或者读取本地较大的一个文件的时候,你不能把这些操作放在主线程中,如果你放在主线程中的话,界面会出现假死现象, 如果5秒钟还没有完成的话,会收到Android系统的一个错误提示 “强制关闭”。
这个时候我们需要把这些耗时的操作,放在一个子线程中,因为子线程涉及到UI更新,Android主线程不是线程安全的,也就是说,更新UI只能在主线程中更新,子线程中操作是危险的。这个时候,Handler就出现了,来解决这个复杂的问题。Handler运行在主线程中(UI线程中),它与子线程可以通过Message对象来传递数据,这个时候,Handler就承担着接受子线程传过来的(子线程用sedMessage()方法传递)Message对象(里面包含数据),把这些消息放入主线程队列中,配合主线程进行更新UI。
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)
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) : 定时执行Message和Runnalbe 对象
// (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中的内容。 这样就达到了多线程的结果。