
1)按计划发送消息或执行某个Runnanble
2)从其他线程中发送来的消息放入消息队列中,避免线程冲突(常见于更新UI线程)
学写一下,在UI线程中,系统已经有一个Activity来处理了,你可以再起若干个Handler来处理。在实例化Handler的时候,只要有Handler的指针,任何线程也都可以sendMessage。
Handler对于Message的处理是异步处理的。一个Looper 只有处理完一条Message才会读取下一条,所以消息的处理是阻塞形式的(handleMessage()方法里不应该有耗时 *** 作,可以将耗时 *** 作放在其他线程执行, *** 作完后发送Message(通过sendMessges方法),然后由handleMessage()更新UI)。
根据对视频的学习写了一个通过Button控件启动进度条(类似于下载等 *** 作)的程序,简单介绍一下,有两个Button控件,一个是开始,点击之后会显示一个进度条以每次十分之一的进度进行(一开始是隐藏的),另一个是停止,可以中断进度。
java代码:
1 package zzl.handleactivity
2
3 import android.app.Activity
4 import android.os.Bundle
5 import android.os.Handler
6 import android.os.Message
7 import android.view.Gravity
8 import android.view.View
9 import android.view.View.OnClickListener
10 import android.widget.Button
11 import android.widget.ProgressBar
12 import android.widget.Toast
13
14 public class Handler_01 extends Activity {
15
16 //声明变量
17 private Button startButton=null
18 private Button endButton=null
19 private ProgressBar firstBar=null
20 private Toast toast=null
21 @Override
22 protected void onCreate(Bundle savedInstanceState) {
23 super.onCreate(savedInstanceState)
24 setContentView(R.layout.main)
25
26 //根据ID获取对象
27 startButton =(Button)findViewById(R.id.startButton)
28 endButton=(Button)findViewById(R.id.endButton)
29 firstBar=(ProgressBar)findViewById(R.id.firstBar)
30 //给对象设置动作监听器
31 startButton.setOnClickListener(new StartButtonListener())
32 endButton.setOnClickListener(new EndButtonListener())
33 }
34
35 class StartButtonListener implements OnClickListener{
36
37 @Override
38 public void onClick(View v) {
39 // TODO Auto-generated method stub
40 //一开始执行,加入到消息队列,不延迟,
41 //然后马上执行run方法
42 firstBar.setVisibility(View.VISIBLE)
43 firstBar.setMax(100)
44 handler.post(upRunnable)
45 toast=Toast.makeText(Handler_01.this, "运行开始", Toast.LENGTH_SHORT)
46 toast.setGravity(Gravity.CENTER, 0, 0)
47 toast.show()
48 }
49 }
50 class EndButtonListener implements OnClickListener{
51
52 @Override
53 public void onClick(View v) {
54 // TODO Auto-generated method stub
55 //停止
56 handler.removeCallbacks(upRunnable)
57 System.out.println("It's time to stop...")
58 }
59 }
60
61 //创建handler对象,在调用post方法
62 //异步消息处理:将下载或者处理大数据等等单独放到另一个线程
63 //更好的用户体验
64 Handler handler=new Handler(){
65
66 @Override
67 public void handleMessage(Message msg){
68 firstBar.setProgress(msg.arg1)
69 firstBar.setSecondaryProgress(msg.arg1+10)
70 //handler.post(upRunnable)
71 if(msg.arg1<=100) {
72 handler.post(upRunnable) //将要执行的线程放入到队列当中
73 }else {
74 handler.removeCallbacks(upRunnable)
75 }
76 }
77 }
78
79 //声明线程类:实现Runnable的接口
80 Runnable upRunnable=new Runnable() {
81
82 int i=0
83 @Override
84 public void run() {//程序的运行状态
85 // TODO Auto-generated method stub
86 //postDelayed方法:把线程对象加入到消息队列中
87 // 隔2000ms(延迟)
88 System.out.println("It's time to start...")
89 i=i+10
90 //获取Message消息对象
91 Message msg=handler.obtainMessage()
92 //将msg对象的arg1(还有arg2)对象的值设置
93 //使用这两个变量传递消息优点:系统消耗性能较少
94 msg.arg1=i
95 try{
96 //设置当前显示睡眠1秒
97 Thread.sleep(1000)
98 }catch(InterruptedException e){
99 e.printStackTrace()
100 }
101 //将msg对象加入到消息队列当中
102 handler.sendMessage(msg)
103 if(i==100){//当值满足时,将线程对象从handle中剔除
104 handler.removeCallbacks(upRunnable)
105 firstBar.setVisibility(View.GONE)
106 //临时d出
107
108 toast=Toast.makeText(Handler_01.this, "运行结束", Toast.LENGTH_SHORT)
109 toast.setGravity(Gravity.CENTER, 0, 0)
110 toast.show()
111 }
112 }
113 }
114 }
main.xml
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:tools="http://schemas.android.com/tools"
3 android:orientation="vertical"
4 android:layout_width="match_parent"
5 android:layout_height="match_parent"
6 tools:context=".Handler_01" >
7
8 <ProgressBar
9 android:id="@+id/firstBar"
10 style="?android:attr/progressBarStyleHorizontal"
11 android:layout_width="200dp"
12 android:layout_height="wrap_content"
13 android:visibility="gone"/>
14
15 <Button
16 android:id="@+id/startButton"
17 android:layout_width="wrap_content"
18 android:layout_height="wrap_content"
19 android:text="@string/start" />
20
21 <Button
22 android:id="@+id/endButton"
23 android:layout_width="wrap_content"
24 android:layout_height="wrap_content"
25 android:text="@string/end" />
26
27 </LinearLayout>
总结:
1)当点击开始或者运行结束的时候,都会通过调用Toasd出临时窗口,Toast.makeText(Handler_01.this, "运行结束", Toast.LENGTH_SHORT),这一句一开始总是执行出错,原因在于必须调用它的show方法才可以显示出来,还可以通过设置它的位置来显示
2)在xml中 android:text="@string/end",则需要在layout下的string文件中敲写相应的代码
3)原本代码有一些瑕疵,就是没有下面这一段代码:
1 if(msg.arg1<=100) {
2 handler.post(upRunnable) //将要执行的线程放入到队列当中
3 }else {
4 handler.removeCallbacks(upRunnable)
5 }
这样就导致了upRunnable的run方法出现了死循环,这样,虽然程序UI本身没有问题,但是内部却又很大的缺陷
这是因为
1 if(i==100){//当值满足时,将线程对象从handle中剔除
2 handler.removeCallbacks(upRunnable)
3 firstBar.setVisibility(View.GONE)
4 toast=Toast.makeText(Handler_01.this, "运行结束", Toast.LENGTH_SHORT)
5 toast.setGravity(Gravity.CENTER, 0, 0)
6 toast.show()
7 }
这一段代码看似是把upRunnable线程从线程对象队列中移除,但是再次之前又前执行了handler.sendMessage(msg)这句代码
从而导致下面的代码又被执行到
1 public void handleMessage(Message msg){
2 firstBar.setProgress(msg.arg1)
3 firstBar.setSecondaryProgress(msg.arg1+10)
4
5 }
这样肯定会使upRunnable线程重新加入到线程对象队列中,updateThread的run方法重复执行,这就导致了死循环。所以必须加上之前的那段代码,通过判断来控制循环终止。并且run方法中的if(i==100)的那段代码也是可以不用的,不过我是写了一些其他代码就懒得优化了,这是后话了。
4) 刚刚大家看到我们可以通过敲写System.out.println在logcat中显示,一开始eclipse编译器中是没有,这是如何显示出来的?
大家可以再window的show view中找到logCat(deprecated)通过下图中绿色的“+”添加出来
然后显示内容的时候,选择右上角“V D I W E ”的I就可以比较清晰的显示出来了,当然你也可以选择另外一个logCat来显示,方法类似。
5)实际上,Handler在默认情况下,和调用它的Activity是处于同一个线程的。 上述Handler的使用示例中,虽然声明了线程对象,但是在实际调用当中它并没有调用线程的start()方法,而是直接调用当前线程的run()方法。
如果要实现调用另一个新的线程,只要注释post方法,然后加上这样两段代码即可: Thread t = new Thread(r) t.start()
在一个Android 程序开始运行的时候,会单独启动一个Process。默认的情况下,所有这个程序中的Activity,Service,Content Provider,Broadcast Receiver(Android 4大组件)都会跑在这个Process。一个Android 程序默认情况下也只有一个Process,但一个Process下却可以有许多个Thread。在这么多Thread当中,有一个Thread,称之为UI Thread。UI Thread在Android程序运行的时候就被创建,是一个Process当中的主线程Main Thread,主要是负责控制UI界面的显示、更新和控件交互。在Android程序创建之初,一个Process呈现的是单线程模型,所有的任务都在一个线程中运行。因此,UI Thread所执行的每一个函数,所花费的时间都应该是越短越好。而其他比较费时的工作(访问网络,下载数据,查询数据库等),都应该交由子线程去执行,以免阻塞主线程,导致ANR。那么问题来了,UI 主线程和子线程是怎么通信的呢。这就要提到这里要讲的Handler机制。简单来说,handler机制被引入的目的就是为了实现线程间通信的。handler一共干了两件事:在子线程中发出message,在主线程中获取、处理message。听起来好像so easy,如果面试中让阐述下Handler机制,这么回答显然不是面试官想要的答案。忽略了一个最重要的问题:子线程何时发送message,主线程何时获取处理message。
为了能让主线程“适时”得处理子线程所发送的message,显然只能通过回调的方式来实现——开发者只要重写Handler类中处理消息的方法,当子线程发送消时,Handler类中处理消息的方法就会被自动回调。
Handler类包含如下方法用于发送处理消息
void handleMessage(Message msg):处理消息的方法,该方法通常用于被重写。
final boolean hasMessage(int what):检查消息队列中是否包含what属性为指定值的消息。
final boolean hasMessage(int what,Object object):检查消息队列中是否包含what属性为指定值且object属性为指定对象的消息。
sendEmptyMessage(int what)发送空消息。
sendEmptyMessageDelayed(int what,longdelayMillis);指定多少毫秒之后发送空消息。
sendMessage(Message msg)立即发送消息。
sendMessageDelayed(int what,longdelayMillis);指定多少毫秒之后发送消息。
借助以上方法,就可以自由穿梭于主线程和子线程之中了。
到这里就结束了么?当然没有。要讲的东西才刚刚开始,要知道消息处理这件事,不是handler一个人在战斗,android的消息处理有三个核心类:Handler,Looper,和Message。其实还有一个MessageQueue(消息队列),但是Message Queue被封装到Looper里面了,不会直接与Message Queue打交道。
Looper的字面意思是“循环装置”,它被设计用来使一个普通线程变成Looper线程。所谓Looper线程就是循环工作的线程。在程序开发中,经常会需要一个线程不断循环,一旦有新任务则执行,执行完继续等待下一个任务,这就是Looper线程。Looper是用于给一个线程添加一个消息队列(MessageQueue),并且循环等待,当有消息时会唤起线程来处理消息的一个工具,直到线程结束为止。通常情况下不会用到Looper,因为对于Activity,Service等系统组件,Frameworks已经为初始化好了线程(俗称的UI线程或主线程),在其内含有一个Looper,和由Looper创建的消息队列,所以主线程会一直运行,处理用户事件,直到某些事件(BACK)退出。
如果,需要新建一个线程,并且这个线程要能够循环处理其他线程发来的消息事件,或者需要长期与其他线程进行复杂的交互,这时就需要用到Looper来给线程建立消息队列。
使用Looper也非常的简单,它的方法比较少,最主要的有四个:
public static prepare()为线程初始化消息队列。
public static myLooper()获取此Looper对象的引用
public static loop()让线程的消息队列开始运行,可以接收消息了。
public void quit()退出具体哪个Looper
在整个消息处理机制中,message又叫task,封装了任务携带的信息和处理该任务的handler,这个很好理解,就不做介绍了。
说了这么多,一定没听太明白。没关系,再对Handler运行机制做个总结:
子线程(无looper)借用主线程(有looper)里的Hander发送一条message到主线程,这个message会被主线程放入message queue,主线程里面有一个looper,在轮询message queue的时候发现有一条message。调用handler消息处理者,执行handlemessage方法,去处理这个message,就可以在handlemessage的方法里面更新ui。好像画面感不是太强,举个例子吧。试想有一个生产方便面的车间,这个车间有两条生产线,一条是生产面饼的,一条是生产调料包的,面饼的生产线比较先进一点,配备了一个工人叫handler,还配备了一个调料包分类循环器。这个车间能生产很多种方便面,有老坛酸菜,有泡椒凤爪,有香菇炖鸡,有红烧牛肉等等。其中方便面的面饼都是一样的,只有调料包和包装袋不一样,包装袋是根据调料包来定的。那么生产线运作起来了:工人Handler把子生产线(子线程)上的调料包(message)放到了主生产线(主线程)上的调料包分类循环器(Looper)上,通过轮询分类筛选后工人Handler确定这是一包合格的老坛酸菜味调料包,于是工人把老坛酸菜调料包和面饼放在一块(sendmessage),告诉主生产线,这是一包老坛酸菜方便面,请给这包方便面包一个老坛酸菜的包装袋(更新UI).
在android中经常会用到改变数据库内容后再去使用数据库更新的内容,很多人会重新去query一遍,但是这样的问题就是程序会特别占内存,而且有可能会搂关cursor而导致程序内存未释放等等。其实android内部提供了一种ContentObserver的东西来监听数据库内容的变化。
ContentObserver的构造函数需要一个参数Hanlder,因为ContentObserver内部使用了一个实现Runnable接口的内部类NotificationRunnable,来实现数据库内容的变化。需要使用hanlder去post消息。注册ContentObserver的方法是:getContentResolver().registerContentObserver(uri, notifyForDescendents, observer).
上面3个参数为:uri----Uri类型,是需要监听的数据库的uri.
notifyForDescendents---boolean true的话就会监听所有与此uri相关的uri。false的话则是直接特殊的uri才会监听。一般都设置为true.
observer-----ContentObserver 就是需要的contentobserver.
初始化一个ContentObserver对象,重载onChange(boolean ),在这个方法里去 *** 作数据库的使用,针对变化后的使用。
写了一个小demo,可以参考下。提示这种监听方式必须是contentprovider才能使用,因为contentprovider有uri.简单的那种sqlite数据库没有uri是使用不了的。
下面demo *** 作的是在一个activityA里点击button跳转至另外一个activityB,在B中点击button往数据库中加数据,加完后关闭B回到A。A的button的文字自动变化设置到数据库中的字符串。[code]
package ty.com.lto02
03 import android.app.Activity
04 import android.content.Intent
05 import android.database.ContentObserver
06 import android.os.Bundle
07 import android.os.Handler
08 import android.view.View
09 import android.widget.Button
10
11 public class ListenDataTest extends Activity{
12 private Button testBtn
13
14 @Override
15 protected void onCreate(Bundle savedInstanceState) {
16 super.onCreate(savedInstanceState)
17 setContentView(R.layout.listen_data_test)
18 getContentResolver().registerContentObserver(DataChangeProvider.CONTENT_URI,
19 true, cob)
20
21 testBtn = (Button)findViewById(R.id.test_btn)
22 testBtn.setOnClickListener(new View.OnClickListener() {
23
24 public void onClick(View v) {
25 Intent in = newIntent(ListenDataTest.this,DataChangeTest.class)
26 startActivity(in)
27
28 }
29 })
30
31 }
32
33 private ContentObserver cob = new ContentObserver(new Handler()) {
34
35 @Override
36 public boolean deliverSelfNotifications() {
37 return super.deliverSelfNotifications()
38 }
39
40 @Override
41 public void onChange(boolean selfChange) {
42 super.onChange(selfChange)
43 testBtn.setText(DataUtils.getChangeName(getApplicationContext()))
44 }
45
46 }
47
48 @Override
49 protected void onDestroy() {
50 super.onDestroy()
51 getContentResolver().unregisterContentObserver(cob)
52 }
53
54
55 }
[code]01 package ty.com.lto
02
03 import android.app.Activity
04 import android.content.ContentValues
05 import android.content.Intent
06 import android.database.ContentObservable
07 import android.database.ContentObserver
08 import android.os.Bundle
09 import android.os.Handler
10 import android.view.View
11 import android.widget.Button
12
13 public class DataChangeTest extends Activity{
14 private Button dataBtn
15 DataSqlite mDataSqlite
16 @Override
17 protected void onCreate(Bundle savedInstanceState) {
18 super.onCreate(savedInstanceState)
19 setContentView(R.layout.data_change_test)
20 dataBtn = (Button)findViewById(R.id.data_test_btn)
21 mDataSqlite = new DataSqlite(this)
22 dataBtn.setOnClickListener(new View.OnClickListener() {
23
24 public void onClick(View v) {
25 ContentValues con = new ContentValues()
26 con.put("name", "数据变化了")
27 getContentResolver().insert(DataChangeProvider.CONTENT_URI, con)
28 finish()
29 }
30 })
31 }
32 }
[code]view sourceprint?
001 package ty.com.lto
002
003
004 import android.content.ContentProvider
005 import android.content.ContentUris
006 import android.content.ContentValues
007 import android.content.Context
008 import android.content.UriMatcher
009 import android.database.Cursor
010 import android.database.SQLException
011 import android.database.sqlite.SQLiteDatabase
012 import android.database.sqlite.SQLiteOpenHelper
013 import android.database.sqlite.SQLiteQueryBuilder
014 import android.database.sqlite.SQLiteDatabase.CursorFactory
015 import android.net.Uri
016 import android.text.TextUtils
017
018 public class DataChangeProvider extends ContentProvider{
019 private SQLiteOpenHelper mOpenHelper
020 private static final int ALARMS = 1
021 private static final int ALARMS_ID = 2
022 private static final UriMatcher sURLMatcher = new UriMatcher(UriMatcher.NO_MATCH)
023 public static final Uri CONTENT_URI = Uri.parse("content://ty.com.lto/test")
024
025 static {
026 sURLMatcher.addURI("ty.com.lto", "test", ALARMS)
027 sURLMatcher.addURI("ty.com.lto", "test/#", ALARMS_ID)
028 }
029
030 private static class DatabaseHelper extends SQLiteOpenHelper{
031 private static final String TEST_DATABASE = "test.db"
032 private static final int VERSION = 1
033
034 public DatabaseHelper(Context context) {
035 super(context, TEST_DATABASE, null, VERSION)
036 // TODO Auto-generated constructor stub
037 }
038
039
040 @Override
041 public void onCreate(SQLiteDatabase db) {
042 String sql = "CREATE TABLE "+"test"+" (" +
043 "_id INTEGER PRIMARY KEY," +
044 "name TEXT "+
045 ")"
046 db.execSQL(sql)
047 }
048
049 @Override
050 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
051 String sql = "DROP TABLE IF EXIST "+TEST_DATABASE
052 db.execSQL(sql)
053 onCreate(db)
054 }
055
056 }
057
058 public DataChangeProvider() {
059 }
060
061 @Override
062 public int delete(Uri url, String where, String[] whereArgs) {
063 SQLiteDatabase db = mOpenHelper.getWritableDatabase()
064 int count
065 long rowId = 0
066 switch (sURLMatcher.match(url)) {
067 case ALARMS:
068 count = db.delete("test", where, whereArgs)
069 break
070 case ALARMS_ID:
071 String segment = url.getPathSegments().get(1)
072
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)