这章主要就是讲android的消息响应的,学Windows的时候接触了回调函数,学java的时候接触了监听器,现在Android就是把这2个集合起来罢了,没其他了。
先来看设置监听器的,无非就是组件对象setXxxx();设置监听器,在监听器类实现响应,就这样。
对于回调函数,每个组件里面都有负责处理不同事件的回调函数,我们要做的就是重写这些组件的回调函数,然后产生响应的时间,系统就会自动去调用这些回调函数了。
这样重写了回调函数之后,就不用再注册监听器就能得到响应。
还有一个问题就是顺序问题,一般都是监听器先处理---->回调函数处理----->Activity处理(这里涉及到那个处理函数最后返回的boolean值,如果是true表明时间已经完全处理好,不用再传了,如果是false就继续传下去处理)
系统响应事件:
Configuration cfg=getResources().getConfiguration();获得的配置对象包含了系统的配置信息。
cfg.orientation==ORIENTATION_LANDSCAPE?"横屏中":“竖屏中”; 像这样应用
如果系统的配置变化就会回调Activity的onConfigurationChanged()函数,所以可以在这里响应系统配置变化的操作。
如果想动态地改变横竖屏,可以调用Activity的setRequestedOrientation(int);
Handler类
最后来的是最难理解的Handler类了,首先android的UI组件都是非线程安全的,通常主线程--acticity上所有组件都在同一个线程上,我们叫这个线程做UI线程,但是当我们需要做一些耗时的操作的时候,如果也在UI线程上做,就会令UI线程不能及时响应UI组件的请求,出现ANR错误。因此一般不在UI线程上做耗时的工作,而是另起一个线程负责,但是UI组件非线程安全意味着另起的这个线程不能操纵UI组件,这个时候就需要用到Handler类了。
先来看一张图:
这图片里面涉及了几个东西:Thread , Handler , Looper , MessageQueue , Message
1.Looper: 一个线程可以产生一个Looper对象,由它来管理此线程里的Message Queue(消息队列)。对于UI线程,它是自动帮你弄好了Looper对象的,但是其他线程就需要你自己去弄,怎么弄?先Looper.prepare(); 这个函数就确保当前线程只有一个Looper,除此之外还需要Looper.loop(); 这个函数启动Looper,使其不断地从MessageQueue中去消息,交由handler处理。
2.Handler: 你可以构造Handler对象来与Looper沟通,以便push新消息到Message Queue里;或者接收Looper(从Message Queue取出)所送来的消息。这里要注意的就是handler是与线程配套的,从图可以看出线程包含着handler和Looper对象,这意味着UI线程有它自己的Looper和handler,新创的子线程也有它自己的Looper和handlerde 。
3. Message Queue(消息队列):用来存放线程放入的消息。
Message:message.arg1 = 1;
message.arg2 = 2;
message.obj = "Demo";
message.what = 3;
Bundle bundleData = new Bundle();
bundleData.putString("Name", "Lucy");
message.setData(bundleData);
arg1和arg2是int,obj是Object对象,这意味着这个项可以穿任意的数据了,最后的Bundle我暂时还不会用,感觉有obj这项还需要用到这个么?把所有数据包装成一个类传不就行了么?
下面是一个示例,这个示例是我自己写的,先讲讲大体上的:一个EditText叫你输入一个数字,下面是确定按钮(按下去就计算输入的数为上限的所有质数,结果显示在界面最下面的TextView中),再来一个next按钮(这个按钮控制一个ImageView,显示下一张图片),下面就是一个ImageView显示图片,在下面就是一个TextView来显示质数结果
先看看XML布局文件:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal">
<EditText
android:id="@+id/edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输出要计算的质数的上限"/>
<Button
android:id="@+id/ok_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="确定"/>
<Button
android:id="@+id/next_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下一张图片"/>
<ImageView
android:id="@+id/imageview"
android:layout_width="200px"
android:layout_height="200px"
android:src="@drawable/a"
android:scaleType="fitCenter"/>
<TextView
android:id="@+id/show"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="结果显示在这里"/>
</LinearLayout>
</ScrollView>
因为怕结果显示过长就加个ScrollView可以滚动看。
对应的java文件:
一部分一部分地来:
先处理线程无关的地方,next按钮的处理,把显示的图片全部放到数组中,按一个next就显示数组中的下一张图,循环显示:
next_btn.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View arg0)
{
// TODO Auto-generated method stub
imageview.setImageResource(images[index]);
index=(index+1)%images.length;
}
});
完成这个部分之后再来弄子线程部分:
子线程要弄的就是为子线程弄好那张图片中的东西,包括了子线程的Looper和Handler:
class CalThread extends Thread
{
private Handler cal_handler;//子线程的Handler的引用
@Override
public void run()
{
// TODO Auto-generated method stub
Looper.prepare();//先保证好子线程有1个Looper
cal_handler=new Handler()//先写好Handler再开始拿消息
{
@Override
public void handleMessage(Message msg)
{
// TODO Auto-generated method stub
if(msg.what==0x123)//通过这个来辨认消息的来源
{
Vector<Integer> vector=new Vector<>();//用于保存求出来的质数的vector
int limit=msg.arg1,i,j;//用msg.arg1来传递那个EditText的上限的值
//String string=new String();
for(i=2;i<limit;i++)
{
for(j=2;j<Math.sqrt(i);j++)
{
if(i%2==0)
break;
}
if(j>=Math.sqrt(i))
{
vector.addElement(i);
//string+=i+" ";
}
}//上面的循环来求2到上限的所有质数,结果都保存到vector中了
try
{
Thread.sleep(10000);//睡眠10秒钟,在这10秒内,UI线程的换图功能仍然能使用就证明没出现ANR错误,不会影响到UI线程响应组件了
} catch (InterruptedException e)
{
// TODO: handle exception
}
Message msg1=new Message();//封装一个0x233的消息,包含所有质数的vector的消息,发送到UI线程中
msg1.what=0x233;
msg1.obj=vector;
uiHandler.sendMessage(msg1);//发送到UI线程,利用的是UI线程的Handler来发送
Toast toast=Toast.makeText(MainActivity.this, "计算完毕", Toast.LENGTH_LONG);
toast.show();
}
}
};
Looper.loop();//开启Looper取消息
}
}
在UI线程里面创这个类对象,并且start(),它就会一直利用它的线程的Looper从Looper管理的MessageQueue中提取消息,遇到0x123消息就进入处理,并且把处理结果发送到UI线程的消息队列中,并且回调UI线程的Handler来处理,所以接下来我们做的就是写UI线程的Handler:
Handler uiHandler=new Handler()
{
@Override
public void handleMessage(Message msg)
{
// TODO Auto-generated method stub
if(msg.what==0x233)
{
Vector<Integer> vector=(Vector<Integer>)msg.obj;
String string=new String();
for(int i=0;i<vector.size();i++)
{
string+=vector.elementAt(i)+" ";
}
show.setText(string);
}
}
};
就这样,2个线程的双向发消息,并回调各自的Handler处理消息,子线程处理过程中还保证了UI线程能顺利响应到UI组件的要求。