Android详细教程(基础篇):二十六、Android组件通信,Intent详解、消息机制(Message、Handler、Looper)详解、 PendingIntent详解
Android组件通信
9.1. Intent
Intent 是一个将要执行的动作的抽象的描述,一般来说是作为参数来使用,由Intent来协助完成android各个组件之间的通讯。比如说调用startActivity()来启动一个activity,或者由broadcaseIntent()来传递给所有感兴趣的BroadcaseReceiver, 再或者由startService()/bindservice()来启动一个后台的service.所以可以看出来,intent主要是用来启动其他的activity 或者service,所以可以将intent理解成activity之间的粘合剂。
9.1.1. Intent的构造函数
public class Intent extends Object implements Parcelable Cloneable
|
Constructors:
Intent() Create an empty intent. Intent(Intent o) Copy constructor. Intent(String action) Create an intent with a given action. Intent(String action, Uri uri) Create an intent with a given action and for a given data url. Intent(Context packageContext, Class<?> cls) Create an intent for a specific component. Intent(String action, Uri uri, Context packageContext, Class<?> cls) Create an intent for a specific component with a specified action and data. |
1、Intent() 空构造函数
2、Intent(Intent o) 拷贝构造函数
3、Intent(String action) 指定action类型的构造函数
4、Intent(String action, Uri uri) 指定Action类型和Uri的构造函数,URI主要是结合程序之间的数据共享ContentProvider
5、Intent(Context packageContext, Class<?> cls) 传入组件的构造函数,也就是上文提到的
6、Intent(String action, Uri uri, Context packageContext, Class<?> cls) 前两种结合体
Intent有六种构造函数,3、4、5是最常用的,并不是其他没用!
Intent(String action, Uri uri) 的action就是对应在AndroidMainfest.xml中的action节点的name属性值。在Intent类中定义了很多的Action和Category常量。
示例:
Intent intent = new Intent(Intent.ACTION_EDIT, null); startActivity(intent); |
代码是用了第四种构造函数,只是uri参数为null。执行此代码的时候,系统就会在程序主配置文件AndroidMainfest.xml中寻找
<action android:name="android.intent.action.EDIT" />对应的Activity,如果对应有多个activity具有<action android:name="android.intent.action.EDIT" />此时就会弹出一个dailog选择Activity。
9.1.2. Activity对Intent的支持
Activity程序支持的Intent操作方法。
9.1.3. Intent的构成
9.1.3.1. Action-动作
用来指明要实施的动作是什么,比如说ACTION_VIEW, ACTION_EDIT等。具体的可以查阅android SDK-> reference中的Android.content.intent类,里面的constants中定义了所有的action。
相当于通过Activity制定要操作的类型
9.1.3.2. Data-数据
Data 要传递的具体数据,一般由一个Uri变量来表示,下面是一些简单的例子:
ACTION_VIEW content://contacts/1 //显示identifier为1的联系人的信息。 ACTION_DIAL content://contacts/1 //给这个联系人打电话 |
9.1.3.3. Type-数据类型
显式指定Intent的数据类型(MIME)。一般Intent的数据类型能够根据数据本身进行判定,但是通过设置这个属性,可以强制采用显式指定的类型而不再进行推导。
9.1.3.4. Category-类别
这个选项指定了将要执行的这个action的其他一些额外的信息,例如 LAUNCHER_CATEGORY 表示Intent 的接受者应该在Launcher中作为顶级应用出现;而ALTERNATIVE_CATEGORY表示当前的Intent是一系列的可选动作中的一个,这些动作可以在同一块数据上执行。具体同样可以参考android SDK-> reference中的Android.content.intent类。以前我也写过一篇于category有关的文章,点击这里可以查看。
9.1.3.5. component-组件
指定Intent的的目标组件的 类名称。通常 Android会根据Intent 中包含的其它属性的信息,比如action、data/type、category进行查找,最终找到一个与之匹配的目标组件。但是,如果 component这个属性有指定的话,将直接使用它指定的组件,而不再执行上述查找过程。指定了这个属性以后,Intent的其它所有属性都是可选的。
9.1.3.6. extras-附加信息
是其它所有附加信息的集合。使用extras可以为组件提供扩展信息,比如,如果要执行“发送电子邮件”这个动作,可以将电子邮件的标题、正文等保存在extras里,传给电子邮件发送组件。
9.1.4. Android中页面跳转以及传值的几种方式!
1、页面跳转及传值:
Activity跳转与传值,主要是通过Intent类来连接多个Activity,通过Builder类来传递数据。
最常见最一般的页面跳转代码,很简单,如下:
Intent intent = new Intent(A.this, B.class); startActivity(intent); |
也可以这样写:
Intent intent = new Intent(); intent.setClass(A.this, B.class); startActivity(intent); |
只要这两句,就可以实现从A页面跳转到B页面了。 (A、B均继承自Activity)
有的时候,在跳转页面时还需要传递数据,这个时候如何做呢?
如果数据比较少,比如只要传一个名字,那么只要加一句"intent.putExtra("Name", "feng88724");"即可,代码如下:
Intent intent = new Intent(); intent.setClass(A.this, B.class); intent.putExtra("Name", "feng88724"); startActivity(intent); |
如果数据比较多,就需要使用 Bunder类了,代码如下: (说明直接看注释)
Intent intent = new Intent(A.this, B.class); /* 通过Builder对象存储需要传递的数据 */ Builder builder = new Builder (); /*字符、字符串、布尔、字节数组、浮点数等等,都可以传*/ builder.putString("Name", "feng88724"); builder.putBoolean("Ismale", true); /*把builder对象加给Intent*/ intent.putExtras(builder); startActivity(intent); |
以上我们讲的都是如何进行页面跳转及数据传递,那么在另一个页面B上,应该如何接收数据呢?
9.1.5. 接受Intent传来的值:
在A页面上是以Builder封装了对象,自然在B页面也是以Builder的方式来解开封装的数据。主要通过
"getIntent().getExtras()"方法来获取Builder对象,然后再从Builder中获取数据。 也可以通过
" this.getIntent().getStringExtra("Name");"方法直接从Intent中获取数据。
从Builder获取数据的代码
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /*加载页面*/ setContentView(R.layout.main); /*获取Intent中的Builder对象*/ Builder builder = this.getIntent().getExtras(); /*获取Builder中的数据,注意类型和key*/ String name = bunder.getString("Name"); boolean ismale = builder.getBoolean("Ismale"); } |
9.1.6. 通过Intent跳转到另一页面之后,再返回到之前的页面:
有时,在页面跳转之后,需要返回到之前的页面,同时要保留用户之前输入的信息,这个时候该怎么办呢?
在页面跳转后,前一个Activity已经被destroy了。如果要返回并显示数据,就必须将前一个Activity再次唤醒,
同时调用某个方法来获取并显示数据。
要实现这个效果,需要做以下几步:
1. 首先,从A页面跳转到B页面时,不可以使用"startActivity()"方法,而要使用"startActivityForResult"方法。
2. 在A页面的Activity中,需要重写"onActivityResult"方法
/*这里的requestCode用来标识某一个调用,一般由我们定义一个常量。 resultCode是返回代码,同样用来标识一个返回类型,而data则是它要返回的参数。*/ @Override protected void onActivityResult(int resultCode,Intent data){ switch(requestCode){ case RESULT_OK: /*取得来自B页面的数据,并显示到画面*/ Builder builder = data.getExtras();
/*获取Builder中的数据,注意类型和key*/ String name = bunder.getString("Name"); boolean ismale = bunder.getBoolean("Ismale"); } } |
- 在B页面上加一个返回按钮,并在事件写如下代码:
/*给上一个Activity返回结果*/ B.this.setResult(RESULT_OK,intent); /*结束本Activity*/ B.this.finish();
|
9.1.7. Intent的解析
应用程序的组件为了告诉Android自己能响应、处理哪些隐式Intent请求,可以声明一个甚至多个Intent Filter。每个Intent Filter描述该组件所能响应Intent请求的能力——组件希望接收什么类型的请求行为,什么类型的请求数据。比如之前请求网页浏览器这个例子中,网页浏览器程序的Intent Filter就应该声明它所希望接收的Intent Action是WEB_SEARCH_ACTION,以及与之相关的请求数据是网页地址URI格式。如何为组件声明自己的Intent Filter? 常见的方法是在AndroidManifest.xml文件中用属性< Intent-Filter>描述组件的Intent Filter。
前面我们提到,隐式Intent(Explicit Intents)和Intent Filter(Implicit Intents)进行比较时的三要素是Intent的动作、数据以及类别。实际上,一个隐式Intent请求要能够传递给目标组件,必要通过这三个方面的检查。如果任何一方面不匹配,Android都不会将该隐式Intent传递给目标组件。接下来我们讲解这三方面检查的具体规则。
9.1.7.1. 动作匹配-action
<intent-filter>元素中可以包括子元素< action>,比如:
<intent-filter> <action android:name=”com.example.project.SHOW_CURRENT” /> <action android:name=”com.example.project.SHOW_RECENT” /> <action android:name=”com.example.project.SHOW_PENDING” /> </intent-filter> |
一条< intent-filter>元素至少应该包含一个<action>,否则任何Intent请求都不能和该<intent-filter>匹配。如果Intent请求的Action和< intent-filter>中某一条<action>匹配,那么该Intent就通过了这条<intent-filter>的动作匹配。如果Intent请求或<intent-filter>中没有说明具体的Action类型,那么会出现下面两种情况。
(1) 如果<intent-filter>中没有包含任何Action类型,那么无论什么Intent请求都无法和这条<intent- filter>匹配;
(2) 反之,如果Intent请求中没有设定Action类型,那么只要< intent-filter>中包含有Action类型,这个 Intent请求就将顺利地通过< intent-filter>的行为测试。
9.1.7.2. 类别匹配
<intent-filter>元素可以包含< category>子元素,比如:
<intent-filter> .... <category android:name=”android.Intent.Category.DEFAULT” /> <category android:name=”android.Intent.Category.BROWSABLE” /> </intent-filter> |
只有当Intent请求中所有的Category与组件中某一个IntentFilter的<category>完全匹配时,才会让该 Intent请求通过匹配,IntentFilter中多余的<category>声明并不会导致匹配失败。一个没有指定任何类别的 IntentFilter仅仅只会匹配没有设置类别的Intent请求。
数据在< intent-filter>中的描述如下:
<intent-filter> ... <data android:type=”video/mpeg” android:scheme=”http” . . . /> <data android:type=”audio/mpeg” android:scheme=”http” . . . /> </intent-filter> |
元素指定了希望接受的Intent请求的数据URI和数据类型,URI被分成三部分来进行匹配:scheme、 authority和path。其中,用setData()设定的Inteat请求的URI数据类型和scheme必须与IntentFilter中所指定的一致。若IntentFilter中还指定了authority或path,它们也需要相匹配才会通过测试。
讲解完Intent基本概念之后,接下来我们就使用Intent**Android自带的电话拨号程序,通过这个实例你会发现,使用Intent并不像其概念描述得那样难。最终创建Intent的代码如下所示。
Intent i = new Intent(Intent.ACTION_DIAL,Uri.parse(”tel://13800138000″)); |
创建好Intent之后,你就可以通过它告诉Android希望启动新的Activity了。startActivity(i);
9.1.8. 实例:
1、调用web浏览器
Java代码
Uri myBlogUri = Uri.parse("http://kuikui.javaeye.com"); returnIt = new Intent(Intent.ACTION_VIEW, myBlogUri); |
2、地图
Java代码
Uri mapUri = Uri.parse("geo:38.899533,-77.036476"); returnIt = new Intent(Intent.ACTION_VIEW, mapUri); |
3、调拨打电话界面
Java代码
Uri telUri = Uri.parse("tel:100861"); returnIt = new Intent(Intent.ACTION_DIAL, telUri) |
4、直接拨打电话
Java代码
Uri callUri = Uri.parse("tel:100861"); returnIt = new Intent(Intent.ACTION_CALL, callUri); |
5、卸载
Java代码
Uri uninstallUri = Uri.fromParts("package", "xxx", null); returnIt = new Intent(Intent.ACTION_DELETE, uninstallUri); |
6、安装
Java代码
Uri installUri = Uri.fromParts("package", "xxx", null); returnIt = new Intent(Intent.ACTION_PACKAGE_ADDED, installUri); |
7、播放
Java代码
Uri playUri = Uri.parse("file:///sdcard/download/everything.mp3"); returnIt = new Intent(Intent.ACTION_VIEW, playUri); |
8、调用发邮件
Java代码
Uri emailUri = Uri.parse("mailto:[email protected]"); returnIt = new Intent(Intent.ACTION_SENDTO, emailUri);
|
9、发邮件
Java代码
returnIt = new Intent(Intent.ACTION_SEND); String[] tos = { "[email protected]" }; String[] ccs = { "[email protected]" }; returnIt.putExtra(Intent.EXTRA_EMAIL, tos); returnIt.putExtra(Intent.EXTRA_CC, ccs); returnIt.putExtra(Intent.EXTRA_TEXT, "body"); returnIt.putExtra(Intent.EXTRA_SUBJECT, "subject"); returnIt.setType("message/rfc882"); Intent.createChooser(returnIt, "Choose Email Client"); |
10、发短信
Java代码
Uri smsUri = Uri.parse("tel:100861"); returnIt = new Intent(Intent.ACTION_VIEW, smsUri); returnIt.putExtra("sms_body", "shenrenkui"); returnIt.setType("vnd.android-dir/mms-sms"); |
11、直接发邮件
Java代码
Uri smsToUri = Uri.parse("smsto://100861"); returnIt = new Intent(Intent.ACTION_SENDTO, smsToUri); returnIt.putExtra("sms_body", "shenrenkui"); |
12、发彩信
Java代码
Uri mmsUri = Uri.parse("content://media/external/images/media/23"); returnIt = new Intent(Intent.ACTION_SEND); returnIt.putExtra("sms_body", "shenrenkui"); returnIt.putExtra(Intent.EXTRA_STREAM, mmsUri); returnIt.setType("image/png"); |
用获取到的Intent直接调用startActivity(returnIt)就ok了。
范例一:打开网页
package com.makyan.demo; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class IntentActionActivity extends Activity { private Button button = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_intent_action); button = (Button) super.findViewById(R.id.button); button.setOnClickListener(new OnClickListener() {
@Override public void onClick(View v) { Uri uri = Uri.parse("www://baidu.com"); Intent it = new Intent(); it.setAction(Intent.ACTION_VIEW); it.setData(uri); startActivity(it); } }); } } |
范例二:发送彩信
范例四:发送邮件
9.2. ActionGroup
9.3. 消息机制(Message、Handler、Looper)
9.3.1. Message、Loope、Handler之间的关系
主线程一般在Android之中称为UI线程,一个界面显示,那么这个就属于主线程,而子线程指的是那些利用
Runnable实现的线程操作类。每个消息(Message)都可以通过Handler进行增加和取出,而操作Handler的对象
就是主线程。
9.3.2. Message –消息的封装
9.3.3. Handler –操作Message消息
范例一:
配置:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MessageActivity" > <TextView android:id="@+id/show" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/message" /> </LinearLayout> |
Activity:
package com.makyan.demo;
import java.util.Timer; import java.util.TimerTask; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.widget.TextView; public class MessageActivity extends Activity { private int count = 0; private final static int state = 1; private TextView txt; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_message); txt = (TextView) super.findViewById(R.id.show); Timer task = new Timer(); task.schedule(new MyTimerTask(), 0, 1000); }
Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { switch(msg.what){ case state: txt.setText("杨雄工作室" + count); count ++; } } };
private class MyTimerTask extends TimerTask{ @Override public void run() { Message msg = new Message(); msg.what = state; handler.sendMessage(msg); } } }
|
子线程产生消息,通过Handler发出去,在Activity层,Handler去处理接受到的消息
只要是子线程就无法更新组件,就只能采用在子线程之中返回要操作的消息,而后在主线程之中利用
Handler处理这些消息,从而实现线程的操作。
9.3.4. Looper –存放消息的队列,自动完成对Message对象的存取
范例二:主线程与子线程之间的消息交互
配置:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".LooperActivity" > <TextView android:id="@+id/info" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="等待子线程发送消息" /> <Button android:id="@+id/but" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="启动" /> </LinearLayout> |
Activity:
package com.makyan.demo; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; public class LooperActivity extends Activity { private final static int STATE = 2; private TextView txt; private Button but; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_looper); txt = (TextView) super.findViewById(R.id.info); but = (Button) super.findViewById(R.id.but); but.setOnClickListener(new OnClickListenerImpl());
} private class OnClickListenerImpl implements OnClickListener{ @Override public void onClick(View v) { Looper looper = Looper.myLooper(); MyHandler handler = new MyHandler(looper); String data = "杨雄工作室..."; Message msg = handler.obtainMessage(STATE, 1, 1, data); handler.sendMessage(msg); } }
private class MyHandler extends Handler{ public MyHandler(Looper looper){ super(looper); } public void handleMessage(Message msg) { switch(msg.what){ case STATE: txt.setText(msg.obj.toString()); }; } } }
|
以上程序中,我们完全可以不使用Looper,效果是一样的,所以在消息机制中最主要的是Handler和Message
示例:主线程与子线程之间的消息交互(即主线程发送消息给子线程,子线程发送消息给主线程)
配置:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".ThreadInteractionActivity" >
<TextView android:id="@+id/showInfo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="等待......" /> <Button android:id="@+id/but" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="交互" /> </LinearLayout> |
Activity:
package com.makyan.demo; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; public class ThreadInteractionActivity extends Activity { private final static int MAINWHAT = 1; //主线程WHAT标记 private final static int CHILDWHAT = 2; //子线程WHAT标记 private TextView txt ; private Button but; private Handler mainHandler; private Handler childHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread_interaction); txt = (TextView) super.findViewById(R.id.showInfo); but = (Button) super.findViewById(R.id.but); new Thread(new ChildThread(),"Child Thread").start(); but.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Message msg = childHandler.obtainMessage(); msg.obj = "主线程发送消息给子线程"; msg.what = CHILDWHAT; childHandler.sendMessage(msg); } });
mainHandler = new Handler(){ public void handleMessage(Message msg) { super.handleMessage(msg); System.out.println(msg.obj.toString()); switch(msg.what){ case MAINWHAT: txt.setText("子线程已经回应"); } } }; } class ChildThread implements Runnable{ @Override public void run() { Looper.prepare(); childHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch(msg.what){ case CHILDWHAT: Message msg2 = mainHandler.obtainMessage(); msg.obj = "我已经接受到您的消息"; msg2.what = MAINWHAT; mainHandler.sendMessage(msg2); break; } } }; } }
@Override protected void onDestroy() { childHandler.getLooper().quit(); } }
|
范例三:时钟显示
配置:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".AnalogClickActivity" > <AnalogClock android:id="@+id/analog" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/timeShow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="时钟显示.." /> </LinearLayout> |
Activity:
package com.makyan.demo; import java.text.SimpleDateFormat; import java.util.Date; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.widget.TextView; public class AnalogClickActivity extends Activity { private final static int state = 1; private TextView txt; Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch(msg.what){ case state: txt.setText(msg.obj.toString()); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_analog_click); txt = (TextView) super.findViewById(R.id.timeShow); new Thread(new Clock()).start(); } class Clock implements Runnable{ @Override public void run() { while(true){ Message msg = handler.obtainMessage(state, 1, 1,"当前时间:" + new SimpleDateFormat("yyyy-mm-dd HH:mm:ss").format(new Date())); handler.sendMessage(msg); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } } |
9.3.5. ProgressBar -使用消息机制对进度条的控制
9.3.6. 详解Android Handler的使用
我们进行Android开发时,Handler可以说是使用非常频繁的一个概念,它的用处不言而喻。本文就详细介绍Handler的基本概念和用法。
Handler的基本概念
Handler主要用于异步消息的处理:当发出一个消息之后,首先进入一个消息队列,发送消息的函数即刻返回,而另外一个部分逐个的在消息队列中将消息取出,然后对消息进行出来,就是发送消息和接收消息不是同步的处理。 这种机制通常用来处理相对耗时比较长的操作。
Handler工具类在多线程中有两方面的应用:
1、发送消息,在不同的线程间发送消息,使用的方法为sendXXX();。
android.os.Handler对象通过下面的方法发送消息的:
sendEmptyMessage(int),发送一个空的消息;
sendMessage(Message),发送消息,消息中可以携带参数;
sendMessageAtTime(Message, long),未来某一时间点发送消息;
sendMessageDelayed(Message, long),延时Nms发送消息。
2、计划任务,在未来执行某任务,使用的方法为postXXX();。
android.os.Handler对象通过下面的方法执行计划任务:
post(Runnable),提交计划任务马上执行;
postAtTime(Runnable, long),提交计划任务在未来的时间点执行;
postDelayed(Runnable, long),提交计划任务延时Nms执行。
使用一个例子简单的来介绍一下Handler。
示例1:一个应用程序中有2个按钮(start、end),当点击start按钮时,执行一个线程,这个线程在控制台输出一串字符串,并且每隔3秒再执行一次线程,直到点击end按钮为止,线程停止。
下图为这个应用程序的界面:
下图为执行程序时控制台的输出:
开发步骤:
1、新建一个Android应用程序
2、在布局文件中添加2个Button控件标签,并为其设置属性和值
3、在Activity中,声明控件变量并根据id获得控件对象
4、在Activity中,创建一个Handler对象
5、在Activity中,创建一个Runnable对象
a) 以匿名内部类的方式
b) 将要执行的操作写在Runnable对象中的run()方法中
i. 打印出一句话
ii. 调用Runnable对象的postDelayed()方法
6、在Activity中,编写start按钮需要的监听器,并绑定。
在这个监听器的Onclick()方法中,调用Handler的post()方法,将要执行的线程对象放到队列当中。
7、在Activity中,编写end按钮需要的监听器,并帮定。
在这个监听器的Onclick()方法中,调用Handler的removeCallbacks ()方法,删除队列当中未执行的线程对象。
下面是Activity的代码:
package android.handler; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class HandlerTest extends Activity { /** Called when the activity is first created. */ private Button startButton; private Button endButton; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //根据id获得控件对象 startButton = (Button)findViewById(R.id.startButton); endButton = (Button)findViewById(R.id.endButton); //为控件设置监听器 startButton.setOnClickListener(new StartButtonListener()); endButton.setOnClickListener(new EndButtonListener()); } class StartButtonListener implements OnClickListener{ public void onClick(View v) { //调用Handler的post()方法,将要执行的线程对象放到队列当中 handler.post(updateThread); } } class EndButtonListener implements OnClickListener{ public void onClick(View v) { //调用Handler的removeCallbacks()方法,删除队列当中未执行的线程对象 handler.removeCallbacks(updateThread); } } //创建Handler对象 Handler handler = new Handler(); //新建一个线程对象 Runnable updateThread = new Runnable(){ //将要执行的操作写在线程对象的run方法当中 public void run(){ System.out.println("updateThread"); //调用Handler的postDelayed()方法 //这个方法的作用是:将要执行的线程对象放入到队列当中,待时间结束后,运行制定的线程对象 //第一个参数是Runnable类型:将要执行的线程对象 //第二个参数是long类型:延迟的时间,以毫秒为单位 handler.postDelayed(updateThread, 3000); } }; } |
示例2:一个应用程序中有一个进度条和一个按钮,当点击按钮后,每隔一秒钟进度条前进一部分。
开发步骤:
1、新建一个Android应用程序
2、在布局文件中添加一个progressBar和一个Button,并为其设置属性和值
3、在Activity中,声明控件变量并根据id获得控件对象
4、创建线程对象
a)通过匿名内部类的方式
b)在编写完了5、6步之后再来继续编写这个线程对象里的操作
i. 声明一个变量用来设置进度条的进度
ii. 重写线程类的run方法(),里面编写要执行的操作
1)打印一个字符串
2)进度条的值增加
3)得到一个消息对象
4)设置消息对象arg1的值
5)让线程休眠一秒钟
6)将消息对象放入到消息队列中
7)判断,如果进度条的值等于100,则将线程对象从队列中移除。
5、 创建Handler对象
a) 与示例1不同的地方是,这里是通过匿名内部类的方式来声明的,而示例1是直接new出来的对象。
b) 重写Handler对象的handlerMessage(Message msg)方法。
i. 这个方法传入了一个Message对象,即消息对象,首先设置进度条的进度(这个值是Messag对象里面的一个成员变量arg1)。
ii. 将要执行的线程对象放入到队列当中。
6、 编写Button需要的监听器,并绑定
a) 设置进度条为显示状态。
b) 将要执行的线程对象放入到队列当中。
下面是Activity的代码:
package android.handler; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ProgressBar;
public class ProgressBarHandlerTest extends Activity { /** Called when the activity is first created. */ private ProgressBar progressBar; private Button startButton; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); progressBar = (ProgressBar)findViewById(R.id.progressbar); startButton = (Button)findViewById(R.id.startButton); startButton.setOnClickListener(new ProgressBarOnClickListener()); } class ProgressBarOnClickListener implements OnClickListener{ public void onClick(View v) { //设置进度条为可见状态 progressBar.setVisibility(View.VISIBLE); updateBarHandler.post(updateThread); } } //使用匿名内部类来复写Handler当中的handlerMessage()方法 Handler updateBarHandler = new Handler(){ @Override public void handleMessage(Message msg) { progressBar.setProgress(msg.arg1); updateBarHandler.post(updateThread); //将要执行的线程放入到队列当中 } }; //线程类,该类使用匿名内部类的方式进行声明 Runnable updateThread = new Runnable(){ int i = 0; public void run() { // TODO Auto-generated method stub System.out.println("Begin Thread"); i+=10; //得到一个消息对象,Message类是android系统提供的 Message msg = updateBarHandler.obtainMessage(); //将Message对象的arg1参数的值设置为i msg.arg1 = i; //用arg1、arg2这两个成员变量传递消息,优点是系统性能消耗较少 try{ Thread.sleep(1000); //让当前线程休眠1000毫秒 }catch(InterruptedException ex){ ex.printStackTrace(); } //将Message对象加入到消息队列当中 updateBarHandler.sendMessage(msg); //如果i的值等于100 if (i == 100){ //将线程对象从队列中移除 updateBarHandler.removeCallbacks(updateThread); } } }; } |
9.4. PendingIntent(操作意图)
9.4.1. 认识PendingIntent
9.4.2. PendingIntent和Intent的区别
Intent的主要功能是完成:一个Activity跳转到其他的Activity或者是Service的操作,表示的是一种操作的意图。
PendingIntent表示的是暂不执行的一种意图。是一种在产生某一事件之后才操作的一种Intent对象。
- Intent:立即执行
- 表示的是暂缓执行,在遇到特殊条件后才执行
9.4.3. PendingIntemt的使用
9.4.3.1. 范例一:发送通知(Notification、NotificationManager的使用)
Notification---表示的是一种提示用户操作的组件
NotificationManager----表示发送通知的操作类
Notification表示通知,NotificationManager表示对通知的操作(发送通知)、管理
Activity:
package com.makyan.demo; import android.app.Activity; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.os.Bundle; public class NotificationManagerActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_notification_manager); NotificationManager notificationManager = (NotificationManager) super.getSystemService(NOTIFICATION_SERVICE); Notification notification = new PendingIntent intent = PendingIntent.getActivity(this,0, super.getIntent(), PendingIntent.FLAG_UPDATE_CURRENT); notification. notificationManager.notify("makyan", R.drawable.as, notification); } }
|
9.4.3.2. 范例二:发送短信(SMSManager的使用):
public final class SmsManager extends Object
|
发送文字信息:
public void sendTextMessage (String destinationAddress, String scAddress, String text, PendingIntent sentIntent, PendingIntent deliveryIntent) 参数详解: String destinationAddress:表示的是收件人的地址 String scAddress:设置短信的号码,如果为null默认为手机中心号码 String text:短信的内容 PendingIntent sendIntent:当信息发出之后,通过PendingIntent来接受成功或失败的信息报告,如果此参数为空,则检查所有未知的应用程序,这样会导致时间比较长一些 PendingIntent deliveryIntent:当信息发送到收件处时,该PendingIntent触发 |
Activity:
package com.makyan.demo; import java.util.Iterator; import java.util.List; import android.app.Activity; import android.app.PendingIntent; import android.os.Bundle; import android.telephony.SmsManager; import android.widget.Toast; public class SmsManagerActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sms_manager); String context = "新长征路上的摇滚" + "一 二 三 四" + "听说过没见过两万五千里" + "有的说没的做怎知不容易" + "埋着头向前走寻找我自己" + "走过来走过去没有根据地" + "想什么做什么是步枪和小米" + "道理多总是说是大炮轰炸机" + "汗也流泪也落心中不服气" + "藏一藏躲一躲心说别着急" + "噢一 二 三 四 五 六 七"; SmsManager smsManager = SmsManager.getDefault(); PendingIntent intent = PendingIntent.getActivity(this,0, super.getIntent(),PendingIntent.FLAG_UPDATE_CURRENT); if(context.length() > 70){ List<String> list = smsManager.divideMessage(context); Iterator it = list.iterator(); while(it.hasNext()){ String msg = (String) it.next(); smsManager.sendTextMessage("18798890583", null, msg, intent, null); } }else{ smsManager.sendTextMessage("18798890583", null, context, intent, null); } Toast.makeText(this, "短信发送成功", Toast.LENGTH_LONG).show(); } }
|
注意,需配置发送短信的权限:
<uses-permission android:name="android.permissions.SEND_SMS" /> |
9.5. AppWidget (桌面组件)
9.5.1. AppWidget的基本概念
就是将一些组件设置到桌面上进行显示,以后直接通过桌面上的一些软件窗口,实现对程序的控制。
开发AppWidget程序,需appWidget包中的几个类的支持:
范例一:添加自己的AppWidget
定义AppWidgetProvider的子类:
AppWidgetProvider的继承结构:
public class AppWidgetProvider extends BroadcastReceiver
|
根据AppWidgetProvider的继承结构,它是BroadcastReceiver的子类,所以,定义了AppWidgetProvider时,就必须要在AndroidMainfest.xml中注册
package com.makyan.demo; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; import android.content.Intent; public class MyAppWidgetProvider extends AppWidgetProvider { @Override public void onDeleted(Context context, int[] appWidgetIds) { // 删除时触发 System.out.println("*** MyAppWidget onDeleted()"); super.onDeleted(context, appWidgetIds); } @Override public void onDisabled(Context context) { // 删除时触发 System.out.println("*** MyAppWidget onDisabled()"); super.onDisabled(context); } @Override public void onEnabled(Context context) { // 启动时触发 System.out.println("*** MyAppWidget onEnabled()"); super.onEnabled(context); } @Override public void onReceive(Context context, Intent intent) { // 处理广播 System.out.println("*** MyAppWidget onReceive()"); super.onReceive(context, intent); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // 更新时触发 System.out.println("*** MyAppWidget onUpdate()"); super.onUpdate(context, appWidgetManager, appWidgetIds); } }
|
定义桌面组件makyan_appwidget.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ImageView android:id="@+id/img" android:src="@drawable/as" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/but" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="杨雄android工作室" android:layout_gravity="center_horizontal" /> </LinearLayout> |
设置桌面显示的appwidget-provider配置文件,定义在“res/xml”下面:
provider_appwidget.xml
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minHeight="80px" android:minWidth="300px" android:updatePeriodMillis="6000" android:initialLayout="@layout/makyan_appwidget"> </appwidget-provider> |
将AppWidgetProvider在AndroidMainfest中注册
<receiver android:name=".MyAppWidgetProvider"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/provider_appwidget" /> </receiver> |
9.5.2. 使用AppWidget跳转到Activity
也就是说,现在的桌面上的Appwidget组件如果要想设置事件处理操作,那么就必须依靠AppWidgetManager类完成,而且要在onUpdate()方法中完成,因为一旦AppWidget添加组件只有,一定会执行onUpdate()方法。
范例二:使用AppWidget跳转到Activity
AppWidgetProvider:
package com.makyan.demo; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; import android.content.Intent; import android.widget.RemoteViews; public class MyAppWidgetProvider extends AppWidgetProvider { @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // 更新时触发 for (int x = 0; x < appWidgetIds.length; x++) { // 更新所有显示的AppWidget Intent intent = new Intent(context, AppWidgetActivity.class); // 设置操作要执行的Activity PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); // 设置准备执行的Intent RemoteViews remote = new RemoteViews(context.getPackageName(), R.layout.makyan_appwidget); // 定义要操作的RemoteViews remote.setOnClickPendingIntent(R.id.but, pendingIntent); // 设置按钮的单击事件 appWidgetManager.updateAppWidget(appWidgetIds[x], remote); // 更新远程视图 } } } |
其他配置文件和范例一相同
范例三:使用AppWidget进行广播
AppWidgetProvider:
package com.makyan.demo; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.widget.RemoteViews; public class MyAppWidgetProvider extends AppWidgetProvider { @Override public void onReceive(Context context, Intent intent) { if ("org.lxh.action.MYAPPWIDGET_UPDATE". equals(intent.getAction())) { // 判断是否是指定的Action RemoteViews remote = new RemoteViews(context.getPackageName(), R.layout.makyan_appwidget); // 定义RemoteViews remote.setImageViewResource(R.id.img, R.drawable.as); // 设置图片 remote.setTextViewText(R.id.but, "www.makyan.cn"); // 更新组件文字 AppWidgetManager appWidgetManager = AppWidgetManager .getInstance(context); // 取得AppWidgetManager ComponentName componentName = new ComponentName(context, AppWidgetActivity.class); // 定义使用的组件 appWidgetManager.updateAppWidget(componentName, remote); // 更新组件 } else { super.onReceive(context, intent) ; // 父类onReceive() } } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // 更新时触发 Intent intent = new Intent(); // 设置操作要执行的Intent intent.setAction("org.lxh.action.MYAPPWIDGET_UPDATE"); PendingIntent pendingIntent = PendingIntent.getBroadcast( context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); // 设置准备执行的Intent RemoteViews remote = new RemoteViews(context.getPackageName(), R.layout.makyan_appwidget); // 定义要操作的RemoteViews remote.setOnClickPendingIntent(R.id.but, pendingIntent); // 设置按钮的单击事件 appWidgetManager.updateAppWidget(appWidgetIds, remote); // 更新远程视图 } } |
其他配置跟范例一相同