在Anroid中,通信技术包括多个层面,在UI层设计多种事件(如触控事件、案件事件、轨迹球事件等);在框架层设计Intent、Meaage等;在内核层则涉及Bundle、RPC、共享内存等技术。本章将重点介绍UI层和框架层的通信机制。
1.Intent通信
Intent通信实际上是对Open-Binder通信机制的封装。在Linux中,存在D-Bus和open-Binder两种进程间通信机制,其中D-Bus应用得更广泛。但在Android中,采用的是open-Binder通信机制,D-Bus只在开源BT协议栈BlueZ中得到应用。
每个Intent消息可以由component、action、data、category,extras、flags等几个属性构成。
按是否有返回值Intent通信可以分为有返回值和无返回值通信,按接收对象可以分为群发消息和个体消息。
但在通常情况下,Intent仅能传递基本的数据类型,对于复杂的数据类型,则要借助Serializable、Parcelable两个接口进行。
(1)Intent常用用法
下面针对网络、地图、电话、消息、电子邮件、多媒体、系统等几个方面介绍Intent的常见用法。
1)网络相关
为了使用网络功能,需要拥有android.permission.INTERNET权限。与网络相关的常用Intent通信包括显示网页、Google地图、搜索等。
在由HTTP定义的与服务器交互的方法中,发起请求的方式有两种,即GET和POST,其中GET方式通过RUL提交数据,数据在URL中可以看到,无法保证私密性,且提交的数据最多只能为1024字节;而POST方式是将数据放置在HTML HEADER中提交,且在数据长度上没有限制,通常用于传递敏感数据。
在Intent中目前尚不支持POST方式提交数据,仅支持GET方式。为了打开网址,其采用的ACTION为ACTION_VIEW,方法如下:
Uri uri=Uri.parse("http://www.163.com:);
Intent it=new Inent(Intent.ACTION_VIEW,uri);
startActivity(it);
如果希望传递敏感数据,在WebView中采用的方法如下:
public void postUrl( String url, byte[] postData);
在众多的应用中找到自己想要的应用。就需要用到搜索功能。实现搜索的方法如下:
Uri uri=Uri.parse("market://seatch?q=pname:com.miaozl.hello");
Intent intent=new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
在网络中搜索相关信息,实现的方法如下:
Intent intent=new Intent();
intent.setAction(Intent.ACTION_WEB_SEARCH);
intent.putExtra(SearchManager.QUERY, "android123")
startActivity(intent);
2)地图相关
为了显示Google地图,需要提供地理位置的经纬度信息,注意,在进行定位时,由于定位算法的原因,移动终端至少要能捕获3颗卫星的数据,才能提供基本的定位,根据GPS芯片的性能和场景的不同,实际GPS芯片能捕获到的卫星数量差异较大,下面是显示Google地图的方法:
Uri uri=Uri.parse("geo:38.899533, -77.036476");
Intent it=new Intent(Intent.ACTION_VIEW,uri);
startActivity(it);
采用GPS导航的目的通常在于希望能找到两点间的最佳路线,这种查找最佳路线的过程在数学上称为路径规划,下面是GoogleMap路径规划的方法:
Uri uri=Uri.parse(http://maps.google.com/maps?f=d&saddr=srartLat%20startLng&daddr=engLat%20endLng&hl=en);
Intent it=new Intent(Intent.ACTION_VIEW,uri);
startActivity(it);
3)电话相关
为了查询联系人信息,需要拥有android.permission.READ_CONTACTS权限,如果希望修改联系人信息,则需要拥有android.permission.WEITE_CONTACTS权限。为了执行呼叫,需要拥有android.permission.CALL_PHONE权限。
作为移动终端最基本的功能,通话自然是必不可少的,与通话相关的还有电话薄等功能,下面是进入电话薄的方法:
Intent intent=new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(People.CONTENT_URI);
startActivity(intent);
如果希望查看具体的联系人,需要制定相关联系人的ID,方法如下:
Uri personUri=ContextUris.withAppendedId(People.CONTENT_URI,ID);
Intent intent=new Intent();
intent.setAction(Intent.ACTION_VIRW);
intent.setData(persionUri);
startActivity(intent);
完成联系人查询后,即可进行拨号了,拨号的方法如下:
Uri uri=Uri.parse("tel:xxxxxx");
Intent it=new Intent(Intent.ACTION_DIAL, uri);
startActivity(it);
在Android中,拨号和呼叫时两个过程。下面是呼叫的方法:
Uri uri=Uri.parse("tel:xxxxxx");
Intent it=new Intent(Intent.ACTION_CALL, uri);
startActivity(it);
4)消息相关
除了通话外,传统移动终端的另一个基本功能就是短消息及彩信。短消息的成功是一个奇迹,其技术并不先进,但由于其深刻把握住了用户需求,极受用户的亲睐,进而成就了其移动终端第一应用的地位。
为了查看短信,需要具有android.permission.READ_SMS权限,其方法如下:
Intent it =new Intent(Intent.ACTION_VIEW);
it.setType("vnd.android-dir/mms-sms");
startActivity(it);
为了发送短信,需要具有android.permission.WEITE_SMS权限,其方法如下:
Uri uri=Uri.parse(“smsto:10086");
Intent it=new Intent(Intent.ACTION_SENDTO, uri);
it.putExtra("sms_body","hello");
startActivity(it);
彩信的发送则稍微复杂些,除了基本的文本信息外,还涉及附件。下面是一个发送附件图片的示例:
Uri uri=Uri.parse("context://media/exernal/images/media/10");
Intent it=new Intent(Intent.ACTION_SEND);
it.putExtra("sms_body","hello");
it.putExtra(Intent.EXTRA_STREAM, uri);
it.setType("image/png");
startActivity(it);
5)电子邮件相关
自有互联网以来,电子邮件功能就是互联网的一个基本应用,电子邮件的出现要早于万维网。邮件协议是一个比较复杂的协议,其主要的协议包括简单邮件传输协议(Simple Mail Transfer Protocal, SMTP)、邮局协议(Post Office Protocal, POP3)、互联网信息接入协议(Internet Message Access Protocol,IMAP)等。
邮件的发送需要拥有android.permission.INTENRNET权限。
发送邮件的最简单方法如下:
Uri uri=Uri.parse("maito:[email protected]");
Intent it=new Intent(Intent.ACTION_DENGTO, uri);
startActivity(it);
邮件具有多种不同形式,下面是各种不同形式的邮件的发送方法。
直接发送文字信息的邮件的方法如下:
Intent it=new Intent(Intent.ACTION_SEND);
it.putExtra(Intent.EXTRA_EMAIL, [email protected]);
it.putExtra(Intent.EXTRA_TEXT,"NEIRONG");
it.setType("text/plain");
startActivity(Intent.createChooser(it,"选择一个EMAIL客户端”));
在发送给接收人的同时抄送给其它人的方法如下:
Intent it=new Intent(Intent.ACTION_SENG);
String[] [email protected]);
String[] [email protected]}
it.putExtra(Intent.EXTRA_EMAIL, tos);
it.putExtra(Intent.EXTRA_CC, ccs);
it.putExtra(Intent.EXTRA_TEXT, "正文“);
it.putExtra(Intent.EXTRA_SUBJECT,”标题“);
it.setType("message/rfc822");
startActivity(Intent.createChooser(it, "选择一个Email客户端"));
发送带附件的邮件的方法如下:
Intent it=new Intent(Intent.ACTION_SEND);
it.putExtra(Intent.EXTRA_SUBJECT,"正文");
it.putExtra(Intent.EXTRA_STREAM, file:///sdcard/miaozl.mp3);
sendIntent.setType("audio/mp3");
startActivity(Intent.createChooser(it,”选择一个Emai客户端“));
从文件管理器等应用中选取特定图片进行发送的方法如下:
Intent intent=new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
startActivityForResult(intent, 0);
6)多媒体相关
自移动互联网流行以来,移动终端就担负着四大基本功能,即通信、社交、网络、多媒体。作为传统终端不可或缺的多媒体功能,在移动终端上也很重要的应用,本节仅介绍本地音乐的播放及设置拍照存放位置的实现方法。
直接播放本地音频文件的方法如下:
Intent it=new Intent(Intent.ACTION_VIEW);
Uri uri=Uri.parse(file:///sdcard/nobody.mp3);
it.setDataAndType(uri, "audio/mp3");
startActivity(it);
播放系统多媒体数据库中的文件的方法如下:
Uri uri=Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, "1"); //从系统内部的MediaPrivider索引中调用播放
Intent it=new Intent(Intent.ACTION_VIEW, uri);
startActivity(it);
设置拍照后存放位置的方法如下:
Intent intent=new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(Environment.geExtrnalStorageDirectory().getAbsolutePath()+"/hello”, miaozl+".jpg")));
startActivityForResult(intent, 0);
7)系统相关
本节主要介绍卸载、安装应用以及创建、删除快捷方式的方法。其中卸载应用的方法如下:
Uri uri=Uri.fromParts("package", strPackageName, null);
Intent it=new Intent(Intent.ACTION_DELETE, uri);
startActivity(it);
安装应用的方法如下:
Uri installUri=Uri.fromParts("package", "xxx", null);
retuen it=new Intent(Intent.ACTION_PACKAGE_ADDED, installUri);
为了使用户可以更方便地启动应用,避免在主菜单的众多应用中查找,可以为应用创建快捷方式,方法如下:
Intent shortcut=new Intent("com.android.laucher.action.INSTALL_SHORTCUT");
shortcut.putExtra(Inent.EXTRA_SHORTCUT_NAME, "shortcutname");
shortcut.putExtra("duplicate", false); //不允许重复创建
ShortcutIconResource iconRes=Intent.ShortcutIconResource.fromContext(this.R.drawable.background);
shortcut.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconRes); //资源图片
String action="con.android.action.START";
Intent respondIntent=new Intent(this, this.getClass());
respondIntent.setAction(action);
shortcut.putExtra(Intent,EXTRA_SHORTCUT_INTENT, respondIntent);
sendBroadcast(shortcut);
删除快捷方式的方法如下:
Intent shortcut=new Intent("com.android.launcher.action.UNINSTALL_SHORTCUT");
shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, "shortcutname");
String action="com.android.action.START");
String appClass=this.getPackageName()+"."+this.getLoaclClassName();
ComponentName comp=new ComponentName(this.getPackageName(), appClass);
shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTEXT,new Intent(action).setComponent(comp));
sendBroadcast(shortcut);
需要注意,创建和删除快捷方式需要如下权限
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUS"/>
<uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"/>
(2)Serializable接口
Serializable接口是一个标准的Java接口,要实现某个类支持序列化,可以使该类继承Serizlizable接口。Serializable接口通常在Intent中使用。
Serializable接口本质上是基于ObjectOutputStream和ObjectInputStream对象进行的,具备Serializable序列化的类的实现非常简单,实例如下:
public final class CodeSigner implements Serializable{
private static final long serialVersionUID=6819288105193937581L;
}
serialVersionUID用于在运行时判断类版本的一致性,其可由JDK中的serialver工具生成,也可以采用默认方式来定义。如果是在Eclipse中进行开发,那么Eclipse会自动提示用户者两种方式进行选择。默认的serialVersionUID值如下:
private static final long serialVersionUID=1L;
在Intent中,可以通过Bundle来设置序列化数据,具体方法如下:
putSerializable(String key, Serializable value);
(3)Parcelable接口
Parcelable接口是在Android特定的序列化接口,它比Serializable接口更有效,还可以避免Serializable接口存在的一些问题。Parcelable接口通常用于Binder和AIDL场景中。
Parcelable接口序列化的数据可以存储在Parcel中,继承Parcelable接口的类必须有一个CREATOR的静态变量。下面是一个Parcelable接口序列化的示例:
public class CatCmdMessage implements Parcelable{
public CatCmdMessage(Parcel in){
mCmdDet=in.readParcelable(null);
mTextMsg=in.readParcelable(null);
mMenu=in.readParcelable(null);
mInput=in.readParcelable(null);
}
public void writeToParcel(Parcel dest. int flags){
dest.writeParcelable(mCmdDet, 0);
dest.writeParcelable(mTextMsg, 0);
dest.writeParcelable(mMenu, 0);
dest.writeParcelable(mInput, 0);
}
public static final Parcelable.Creator<CatCmdMessage> CREATOR=new Parcelable.Creator<CatCmdMessage>(){
public CatCmdMessage createFromParcel(Parcel in){
return new CatCmdMessage(in);
}
public CatCmdMeaage[] new Array(int size){
return new CatCmdMeaage[size];
}
};
}
通过Intent传递Parcelable接口序列化数据的方法如下:
public Intent putExtra(String name, Parcelable value)
另外,Parcelable也可以通过Bundle在Intent中传递。在Bundle中设置Parcelable的方法如下:
public void putParcelable(String key, Parcelabel value) //Parcelable方法
public Intent putExtra(String name, Bundle value) //Intent方法
当然Bundle还支持对Parcelable接口数组,Parcelabale接口列表和基本数据类型的传递。
2.UI事件处理
UI事件主要包括事件监听器(Event Listteners)相关事件、事件句柄(Event Handlers)相关事件、焦点相关事件触控事件、按键事件、轨迹球事件等。UI事件均是基于View来实现的。在Anroid中,触控事件为主要UI事件。
对于普通控件,当发生触控和单击事件时,UI主线程将先接收到相关事件,接着根据事件发生区域,将相关事件加入事件队列,发送给处理事件的控件。
在Android中,根据执行的事件不同,相应的事件派发机制可以分为多种,输入事件中,比较重要的几个组件为EventHub、InputManager、InputDispatcher、InputReader,组件间的相互关系如下图:
而在框架层,WindowManagerService是事件处理的神经中枢,它控制着事件如何进一步向UI层派发。
(1)事件监听器、事件句柄及焦点处理
1)事件监听器
事件监听器本质上是基于接口的回调函数,包括onClick()、onLongClick()、onFocusChange()、onKey()、onTouch()、onCreateContextMenu()等。
在执行监听时,一旦收到监听事件,Android会将事件放置在ViewRoot的事件队列中等待派发。
以单击事件为例,事件监听器处理事件的过程如下:
public boolean performClick(){
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if(mOnClickListener != null){
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListtener.onClick(this);
return true;
}
return false;
}
2)事件句柄
当实现自定义的视图时,可能需要UI事件自定义的响应。
当事件处理完成后,如果不希望事件继续在处理对象链上传递,那么应设其返回值为true.
3)焦点处理
基于某种场景的需要,需要监听控件的变化,其方法如下:
public void setOnFocusChangeListtener(OnFocusChangeListener 1);
(2)触控事件处理
UI事件的处理通常是通过MotionEvent(如鼠标、触笔、手指、轨迹球等产生的事件)来进行的,其Action包括ACTION_UP、ACTION_MOVE、ACTION_CANCEL、ACTION_OUTSIDDE、ACTION_POINTER_DOWN、ACTION_POINTER_UP、ACTION_POINTER_INDEX_MASK、ACTION_POINTER_INDEX_SHIFT等。
触控事件根据触控方式的不同,分为单点触控和多点触控两类。部分场景可能需要根据手势(即运行轨迹)进行处理。手势识别是通过GestureDerector实现的。
1)单点触控
单点触控是自电阻屏以来就有的触控方式,其实现很简单,示例如下:
public boolean onTouchEvent(MotionEvent ev){
}
2)多点触控
Android的多点触控在本质上需要LCD驱动和程序本身设计上的支持。模拟器无法实现多点触控的测试。
3)手势识别
GestureDetector支持多种手势,如按下、快速滚动、长按、常规滚动、ShowPress(用户按下时提示用户某些信息)、单击、双击等。
(3)按键事件处理
目前支持多个类型的按键:12-key、QWERTY、五向键、Home等。
当按键事件发生后,Linux驱动会将捕捉到的输入事件传递给EventHub.cpp。EventHub是Linux驱动和Android框架层的接口。
(4)轨迹球事件处理
轨迹球事件的处理是由Activity执行的。和其他事件一样,轨迹球事件的描述需要通过MotionEvent进行。
3.任务调度
针对定时调度或者周期性执行的任务,Android引入了Java的java,util.Timer和java.util.timerTask。需要注意的是,TimerTask会阻塞主线程,对资源的消耗比另起一个线程要好。对于比较耗时的计算,不建议放在TimerTask中运行。
下面是TimerTask实现任务调度的方法:
private Timer mTimer=new Timer(true);
private TimerTask mTimerTask;
mTimerTask=new TimerTask(){
public void run(){
Log.d("TimerTask");
}
}
如果希望该任务只执行单次,则执行如下调度:mTimer.schedule(mTimerTask, 5000) //在5秒后执行调度
如果希望该任务周期执行,则进行如下调度:mTimer.schedule(mTimerTask, 5000, 1000) //在1秒后每5秒执行一次调度
为了取消任务调度,需要执行以下调度:mTimerTask.cancel();