Perface
前一阵子一直在讨论,Android的几大块中我认为比较重要的一些东西,比如:Activity的生命周期以及onSaveInstanceState()的使用要点。(突然发现,我好像对Android的个人总结文章好像不多,算了不管了)这里先稍稍给大家提一下Android的几大块:
- View:我们看到的UI组建,个人认为这个不是非常重要,在追求功能实现的过程中只要会用就OK
- Activity:用户界面(包含UI组建)简单来说可以理解为程序入口吧,作为UI操作的入口,通常体现为程序中的一个屏幕
- Intent:执行特定工作的意图,比如说:你要跳转你的Activity、拨电话、广播消息等
- ContentProvider:移动应用程序之间的共享数据,个人感觉就是个数据库,保存App的各种记录
- Services:平台服务,可长时间运行的后台进程
- Fragment:碎片,其实就是为平板等大屏设备所扩展出来的(常见的就是大屏幕设备左右分栏,左边功能区,右边是内容区),当然小屏幕设备通用可以使用。
About Handler
在Android里,所有的UI组件应该在UI线程中调用而不应该在子线程中调用(因为Android的UI组建是非线程安全的),因此需要一个跨线程通讯的一种机制。Handler就是这样一种机制,然而经过学习,我了解Handler实际上可以工作在两种工作模式:主线程上和跨线程上。首先先提一提Android组建的线程框架,这有助于理解Handler的工作机制。
Android的主线程通过一个消息队列执行所有工作----运行所有组件。主线程位于一个循环中并处理消息队列中的每条消息,如果任何消息用时超过5秒,Android就会抛出一条ANR消息。
这里可以发现,Android的线程通讯机制使用的是一种消息传递的方式。个人理解,Android会将产生的消息添加到消息列队里调度,当捕捉到一个消息之后就应该去处理这个消息,Handler的工作就是负责处理Message。这实际上是通过回调方法实现的,当一个Message被处理时,就会引发一个回调函数去处理这个消息,也就是之后会看到的 public void handleMessage(Message msg); 回调函数。
Handler on UI Thread
首先,来举个Handler在工作在主线程上的使用例子。这个例子是根据《Pro Android 4 》中演变来的。
问题描述:
创建一个Android应用程序,当按下一个按钮以后每隔一定时间显示一个Hello。
解决方案:
一个简单的解决方案就是,通过消息机制。按下按钮后发送一个延迟消息(这个在Android里面有个sendMessageDelay(Message m , long delay);的方法延长发送消息),当捕捉到消息以后简单的再发一个同样的延迟消息就OK了。
主要由以下几个步骤:
创建界面什么的在此就不说了
重写Handler的具体实现方法,也就是 public void handleMessage(Message msg);
在按钮按下的时候调用sendMessageDelay(Message m , long delay);方法
首先看一下我们的UI元素结构:
问题描述:
创建一个Android应用程序,当按下一个按钮以后每隔一定时间显示一个Hello。
解决方案:
一个简单的解决方案就是,通过消息机制。按下按钮后发送一个延迟消息(这个在Android里面有个sendMessageDelay(Message m , long delay);的方法延长发送消息),当捕捉到消息以后简单的再发一个同样的延迟消息就OK了。
主要由以下几个步骤:
创建界面什么的在此就不说了
重写Handler的具体实现方法,也就是 public void handleMessage(Message msg);
在按钮按下的时候调用sendMessageDelay(Message m , long delay);方法
首先看一下我们的UI元素结构:
我们来看一下Code,这里为了方便偷了个懒写成一个文件了
package com.javacs3.handlerlearning;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ScrollView;
import android.widget.TextView;
public class MainActivity extends Activity implements OnClickListener {
private TextView console_tv = null;
/* counter */
private int counter;
/* override handleMessage */
private Handler myHandler = new Handler() {
@Override
/* when the message is captured */
public void handleMessage(Message msg) {
/* print Hello World and 'What' */
printMessage("Hello World : (" + msg.what + ")");
/* send a delayMessage then */
MainActivity.this.sendMessage();
}
private void printMessage(String str) {
/* print a message on the activity */
console_tv.append(str + "\r\n");
((ScrollView) findViewById(R.id.scrollView1))
.fullScroll(ScrollView.FOCUS_DOWN);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
counter = 0;
console_tv = (TextView) findViewById(R.id.console_tv);
Button btn = (Button) findViewById(R.id.button);
btn.setOnClickListener(this);
sendMessage();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
private void sendMessage() {
/* get a new message from message queue */
Message m = Message.obtain();
/* describe what the message is, here we just equal to the counter */
m.what = counter++;
/* send a 100ms delay message */
myHandler.sendMessageDelayed(m, 100);
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
if (v.getId() == R.id.button) {
console_tv.append("Ha ha haha ....\r\n");
((ScrollView) findViewById(R.id.scrollView1))
.fullScroll(ScrollView.FOCUS_DOWN);
}
}
}
是不是很简单?大家可能会对Message这个对象不太了解,我在这里先补充说明一下:
Message这个类有几个比较重要的字段:
a.arg1和arg2:我们可以使用两个字段用来存放我们需要传递的整型值,在Service中,我们可以用来存放Service的ID。
b.obj:该字段是Object类型,我们可以让该字段传递某个多项到消息的接受者中。
c.what:这个字段可以说是消息的标志,在消息处理中,我们可以根据这个字段的不同的值进行不同的处理,类似于我们在处理Button事件时,通过switch(v.getId())判断是点击了哪个按钮。
在使用Message时,我们可以通过new Message()创建一个Message实例,但是Android更推荐我们通过Message.obtain()或者Handler.obtainMessage()获取Message对象。这并不一定是直接创建一个新的实例,而是先从消息池中看有没有可用的Message实例,存在则直接取出并返回这个实例。反之如果消息池中没有可用的Message实例,则根据给定的参数new一个新Message对象。通过分析源码可得知,Android系统默认情况下在消息池中实例化10个Message对象。
我原文这补充一下:如果你需要传递更多的信息于Message中可以使用setData(Bundle b);这个方法。
如果你足够细心的话你会发现这个例子实际上有一些问题,你会得到这样一个Warning:
package com.javacs3.handlerlearning;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ScrollView;
import android.widget.TextView;
public class MainActivity extends Activity implements OnClickListener {
private TextView console_tv = null;
/* counter */
private int counter;
/* override handleMessage */
private Handler myHandler = new Handler() {
@Override
/* when the message is captured */
public void handleMessage(Message msg) {
/* print Hello World and 'What' */
printMessage("Hello World : (" + msg.what + ")");
/* send a delayMessage then */
MainActivity.this.sendMessage();
}
private void printMessage(String str) {
/* print a message on the activity */
console_tv.append(str + "\r\n");
((ScrollView) findViewById(R.id.scrollView1))
.fullScroll(ScrollView.FOCUS_DOWN);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
counter = 0;
console_tv = (TextView) findViewById(R.id.console_tv);
Button btn = (Button) findViewById(R.id.button);
btn.setOnClickListener(this);
sendMessage();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
private void sendMessage() {
/* get a new message from message queue */
Message m = Message.obtain();
/* describe what the message is, here we just equal to the counter */
m.what = counter++;
/* send a 100ms delay message */
myHandler.sendMessageDelayed(m, 100);
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
if (v.getId() == R.id.button) {
console_tv.append("Ha ha haha ....\r\n");
((ScrollView) findViewById(R.id.scrollView1))
.fullScroll(ScrollView.FOCUS_DOWN);
}
}
}
是不是很简单?大家可能会对Message这个对象不太了解,我在这里先补充说明一下:
Message这个类有几个比较重要的字段:
a.arg1和arg2:我们可以使用两个字段用来存放我们需要传递的整型值,在Service中,我们可以用来存放Service的ID。
b.obj:该字段是Object类型,我们可以让该字段传递某个多项到消息的接受者中。
c.what:这个字段可以说是消息的标志,在消息处理中,我们可以根据这个字段的不同的值进行不同的处理,类似于我们在处理Button事件时,通过switch(v.getId())判断是点击了哪个按钮。
在使用Message时,我们可以通过new Message()创建一个Message实例,但是Android更推荐我们通过Message.obtain()或者Handler.obtainMessage()获取Message对象。这并不一定是直接创建一个新的实例,而是先从消息池中看有没有可用的Message实例,存在则直接取出并返回这个实例。反之如果消息池中没有可用的Message实例,则根据给定的参数new一个新Message对象。通过分析源码可得知,Android系统默认情况下在消息池中实例化10个Message对象。
我原文这补充一下:如果你需要传递更多的信息于Message中可以使用setData(Bundle b);这个方法。
如果你足够细心的话你会发现这个例子实际上有一些问题,你会得到这样一个Warning:
This Handler class should be static or leaks might occur (com.javacs3.handlerlearning.MainActivity.1) MainActivity.java
貌似这种偷懒的方法会导致泄露,所以解决方法也很简单,只要把匿名类改写成对应的独立类就OK了,记得传入Activity的引用到Handler的子类就好了。Android的机制处理的很好,消息回调的时候可以很好的处理你的工作。但是请记住,这种情况实际上还是让任务工作在主线程(UI线程上)但是只是产生了一个延长处理的效果。同样的,你不应该把费时的处理程序放在消息回调函数中做,而应该使用异步工作方式,由于篇幅限制我将Handler的异步工作模式放在下一篇讲。
为了方便大家学习我给大家几个自己觉得不错的链接以供参考:
最后是工程文件:
为了方便大家学习我给大家几个自己觉得不错的链接以供参考:
最后是工程文件:
handlerlearning.zip |