Intent是一种消息传递机制,可以在应用程序内使用,也可以在应用程序间使用。可以用于:
- 使用类名显示启动一个特定的Service或者Activity。
- 启动Activity或者Service来执行一个动作的Intent,通常需要使用特定的数据,或者对特定的数据执行动作。
- 广播某个时间已经发生。
使用Intent来启动Activity
显式启动新的Activity
Intent intent = new Intent(MyActivity.this, SelectHorseActivity.class); startActivity(intent);
调用startActivity之后,新的Activity会被创建、启动和恢复运行,它会移动到Activity栈的顶部。
调用新的Activity的finish或按下设备的返回按钮将关闭该Activity,并把它从栈中移除。
隐式的Intent和运行时迟绑定
隐式的Intent提供了一种机制,可以让匿名的应用程序组件响应动作请求。这意味着可以要求系统启动一个可执行给定动作的Activity,而不必知道需要启动哪个应用程序或者Activity。
例如希望让用户从应用程序中拨打电话,那么可以实现一个新的拨号程序,也可以使用一个隐式的Intent来请求一个在电话号码(表示为一个URI)上执行动作。
// Create the implicit Intent to use to start a new Activity. Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:555-2368")); startActivity(intent);
当构建一个新的隐式的Intent时,需要指定一个要执行的动作,另外,也可以提供执行那个动作需要的数据的URI。还可以通过向Intent添加extra来向目标Activity发送额外的数据。
Extra是一种向Intent附加基本类型值的机制。可以在任何Intent上使用重载后的putExtra方法来附加一个新的键值对,以后在启动的Activity中使用对于的getExtra方法来检索它。
确定Intent能否解析
在调用startActivity之前,确定调用是否可以解析为一个Activity是一种很好的做法。通过调用Intent的resolveActivity方法,并向该方法传入包管理器,可以对包管理器进行查询,确定是否有Activity能够启动以响应该Intent。
if (somethingWeird && itDontLookGood) { // Create the implicit Intent to use to start a new Activity. Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:555-2368")); // Check if an Activity exists to perform this action. PackageManager pm = getPackageManager(); ComponentName cn = intent.resolveActivity(pm); if (cn == null) { // If there is no Activity available to perform the action // Check to see if the Market is available. Uri marketUri = Uri.parse("market://search?q=pname:com.myapp.packagename"); Intent marketIntent = new Intent(Intent.ACTION_VIEW).setData(marketUri); // If the Market is available, use it to download an application // capable of performing the required action. Otherwise log an // error. if (marketIntent.resolveActivity(pm) != null) startActivity(marketIntent); else Log.d(TAG, "Market client not available."); } else startActivity(intent); }
从Activity返回结果
通过startActivity启动的Activity独立于其父Activity,并且在关闭时不会提供任何反馈。
当需要反馈时,可以启动一个Activity作为另一个Activity的子Activity,用它向父Activity传递结果。子Activity只是以一种不同的方式启动的Activity。因此,必须在应用程序的manifest文件中注册它们,就像其他任何Activity一样。在manifest文件中注册的任何Activity都可以作为子Activity打开,包括系统Activity或者第三方应用程序的Activity。
当子Activity结束时,它会触发调用Activity内的事件处理程序onActivityResult。
启动子Activity
显示启动一个子Activity以返回结果
private static final int SHOW_SUBACTIVITY = 1; private void startSubActivity() { Intent intent = new Intent(this, MyOtherActivity.class); startActivityForResult(intent, SHOW_SUBACTIVITY); }
隐式启动一个子Activity以返回结果
private static final int PICK_CONTACT_SUBACTIVITY = 2; private void startSubActivityImplicitly() { Uri uri = Uri.parse("content://contacts/people"); Intent intent = new Intent(Intent.ACTION_PICK, uri); startActivityForResult(intent, PICK_CONTACT_SUBACTIVITY); }
返回结果
当准备好返回子Activity时,可以在调用finish以前调用setResult,以便向调用Activity返回一个结果。
setResult方法有两个参数:结果码和表示为Intent的结果数据本身。
结果码是运行子Activity的结果-通常是Activity.RESULT_OK或者Activity.RESULT_CANCELED。在某些情况下,当OK和CANCELED不足以精确描述可用的返回结果时,可以使用自己的响应码来处理应用程序特定的选择;setResult支持任意的整数值。
作为结果返回的Intent通常包含某段内容的URI和用于返回附加信息的一组extra。
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.selector_layout); final ListView listView = (ListView)findViewById(R.id.listView1); Button okButton = (Button) findViewById(R.id.ok_button); okButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { long selected_horse_id = listView.getSelectedItemId(); Uri selectedHorse = Uri.parse("content://horses/" + selected_horse_id); Intent result = new Intent(Intent.ACTION_PICK, selectedHorse); setResult(RESULT_OK, result); finish(); } }); Button cancelButton = (Button) findViewById(R.id.cancel_button); cancelButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { setResult(RESULT_CANCELED); finish(); } }); }
如果用户通过按下硬件返回键关闭Activity,或者在调用finish之前没有调用setResult,那么结果码将被设为RESULT_CANCELED,结果Intent将被设为null。
处理子Activity结果
当一个子Activity关闭的时候,它会触发其调用Activity的onActivityResult事件处理程序。
onActivityResult接受多个参数:
- 请求码 启动正在返回的子Activity时使用的请求码
- 结果码 子Activity设置的结果码,用来说明其结果
- 数据 Intent用来包装所有返回的数据。
private static final int SELECT_HORSE = 1; private static final int SELECT_GUN = 2; Uri selectedHorse = null; Uri selectedGun = null; @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch(requestCode) { case (SELECT_HORSE): if (resultCode == Activity.RESULT_OK) selectedHorse = data.getData(); break; case (SELECT_GUN): if (resultCode == Activity.RESULT_OK) selectedGun = data.getData(); break; default: break; } }
原生Android动作
Standard Activity Actions
ACTION_MAIN
ACTION_VIEW
ACTION_ATTACH_DATA
ACTION_EDIT
ACTION_PICK
ACTION_CHOOSER
ACTION_GET_CONTENT
ACTION_DIAL
ACTION_CALL
ACTION_SEND
ACTION_SENDTO
ACTION_ANSWER
ACTION_INSERT
ACTION_DELETE
ACTION_RUN
ACTION_SYNC
ACTION_PICK_ACTIVITY
ACTION_SEARCH
ACTION_WEB_SEARCH
ACTION_FACTORY_TEST
Linkify
Linkify是一个辅助类,它会自动地在TextView类(或者TextView的派生类)中通过RegEx模式匹配来创建超链接。
那些匹配一个制定的RegEx模式的文本都将会被转换为一个可以单击的超链接,这些超链接可以隐式使用匹配的文本作为目标URI来触发startActivity(new Intent(Intent.ACTION_VIEW,uri))。
可以制定任何字符串模式来作为可单击连接的处理;
原生Linkify链接类型
final TextView myTextView = (TextView)findViewById(R.id.text_view); Linkify.addLinks(myTextView, Linkify.WEB_URIS|Linkify.EMAIL_ADDRESSES);
或者
<TextView android:id="@+id/text_view" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" android:autoLink="phone|email" />
创建定制的链接字符串
要为自己的数据建立链接,需要定义自己的linkify字符串,可以通过创建一个新的RegEx模式来匹配希望显示为超链接的文本。
和本地类型一样,可以通过调用Linkify.addLinks来为目标TextView建立链接,只不过这次传入的是RegEx模式,而不是预设的常量。也可以给它传递一个前缀,当单击链接时,该前缀将会被添加到目标URI的前面。
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); final TextView myTextView = (TextView)findViewById(R.id.text_view); // Define the base URI. String baseUri = "content://com.paad.earthquake/earthquakes/"; // Contruct an Intent to test if there is an Activity capable of // viewing the content you are Linkifying. Use the Package Manager // to perform the test. PackageManager pm = getPackageManager(); Intent testIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(baseUri)); boolean activityExists = testIntent.resolveActivity(pm) != null; // If there is an Activity capable of viewing the content // Linkify the text. if (activityExists) { int flags = Pattern.CASE_INSENSITIVE; Pattern p = Pattern.compile("\\bquake[\\s]?[0-9]+\\b", flags); Linkify.addLinks(myTextView, p, baseUri); } }
使用Intent广播事件
作为一个系统级的消息传递机制,Intent可以在进程间发送结构化的消息。因此,可以通过实现Boradcast Receiver来监听和响应应用程序内的这些Broadcast Intent。
Broadcast Intent用于向监听器通知系统的应用程序或应用程序事件,从而可以扩展应用程序间的事件驱动的编程模型。
Broadcast Intent可以使应用程序更加开放;通过使用Intent来广播一个事件,可以在不用修改原始应用程序的情况下,让你和第三方开发人员对事件做出反应。在应用程序中,可以通过监听Broadcast Intent来对设备状态变化和第三方应用程序事件做出反应。
使用Intent来广播事件
在应用程序组件中,可以构建希望广播的Intent,然后使用sendBroadcast方法来发送它。
public final static String EXTRA_LIFEFORM_NAME = "EXTRA_LIFEFORM_NAME"; public final static String EXTRA_LATITUDE = "EXTRA_LATITUDE"; public final static String EXTRA_LONGITUDE = "EXTRA_LONGITUDE"; public static final String ACTION_BURN = "com.paad.alien.action.BURN_IT_WITH_FIRE"; public static final String NEW_LIFEFORM = "com.paad.alien.action.NEW_LIFEFORM"; // private void detectedLifeform(String detectedLifeform, double currentLongitude, double currentLatitude) { Intent intent = new Intent(NEW_LIFEFORM); intent.putExtra(EXTRA_LIFEFORM_NAME, detectedLifeform); intent.putExtra(EXTRA_LONGITUDE, currentLongitude); intent.putExtra(EXTRA_LATITUDE, currentLatitude); sendBroadcast(intent); }
使用Broadcast Receiver来监听广播
要使Broadcast Receiver能够接收广播,就需要对其进行注册,既可以使用代码,也可以在应用程序的manifest文件中注册(此时成为manifest接收器)。无论怎么注册,都需要使用一个Intent Filter来指定它要监听哪些Intent和数据。
对于包含manifest接收器的应用程序,在Intent被广播出去的时候,应用程序不一定非要处于运行状态才能执行接收。当匹配的Intent被广播出去的时候,它们会被自动的启动。
public class LifeformDetectedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // Get the lifeform details from the intent. } }
onReceive处理程序必须在5秒钟内完成,否则就会显示Force Close对话框。
一般情况下,Broadcast Receiver将会更新内容、启动Service、更新Activity UI,或者使用Notification Manager来通知用户。5秒钟的执行限制保证了主要的处理工作不能够、也不应该有Broadcast Receiver直接完成。
public class LifeformDetectedReceiver extends BroadcastReceiver { public final static String EXTRA_LIFEFORM_NAME = "EXTRA_LIFEFORM_NAME"; public final static String EXTRA_LATITUDE = "EXTRA_LATITUDE"; public final static String EXTRA_LONGITUDE = "EXTRA_LONGITUDE"; public static final String ACTION_BURN = "com.paad.alien.action.BURN_IT_WITH_FIRE"; public static final String NEW_LIFEFORM = "com.paad.alien.action.NEW_LIFEFORM"; @Override public void onReceive(Context context, Intent intent) { // Get the lifeform details from the intent. Uri data = intent.getData(); String type = intent.getStringExtra(EXTRA_LIFEFORM_NAME); double lat = intent.getDoubleExtra(EXTRA_LATITUDE, 0); double lng = intent.getDoubleExtra(EXTRA_LONGITUDE, 0); Location loc = new Location("gps"); loc.setLatitude(lat); loc.setLongitude(lng); if (type.equals("facehugger")) { Intent startIntent = new Intent(ACTION_BURN, data); startIntent.putExtra(EXTRA_LATITUDE, lat); startIntent.putExtra(EXTRA_LONGITUDE, lng); context.startService(startIntent); } } }
在代码中注册Broadcast Receiver
在代码中注册的接收器只会在包含它的应用程序组件运行时响应Broadcast Intent。
public class MyActivity extends Activity { private IntentFilter filter = new IntentFilter(LifeformDetectedReceiver.NEW_LIFEFORM); private LifeformDetectedReceiver receiver = new LifeformDetectedReceiver(); @Override public synchronized void onResume() { super.onResume(); // Register the broadcast receiver. registerReceiver(receiver, filter); } @Override public synchronized void onPause() { // Unregister the receiver unregisterReceiver(receiver); super.onPause(); } }
在应用程序的manifest中注册Broadcast Receiver
广播有序的Intent sendOrderedBroadcast
广播Sticky Intent
Sticky Intent是Broadcast Intent的有用变体,可以保存它们最后一次广播的值,并且当有一个新的接收器被注册为接收该广播时,它们会把这些值作为Intent返回。
要广播自己的Sticky Intent,应用程序必须具有BROADCAST_STICKY用户权限,然后需要调用sendStickyBroadcast并传入相关的Intent。
要删除一个Sticky Intent,可以调用removeStickyBroadcast,并传入要删除的Sticky Intent。
Local Broadcast Manager
Local Broadcast Manager(局部广播管理器)包含在Android Support Library中,用于简化注册Broadcast Intent,以及在应用程序内的组件之间发送Broadcast Intent的工作。
局部广播的作用域要小一些,所以使用Local Broadcast Manager比发送全局广播更加高效。而且使用Local Broadcast Manager也确保了应用程序外部的任何组件都收不到你广播的Intent,所以不会有私人数据或敏感数据泄露出去的风险。
//注册一个局部Broadcaset Receive LocalBroadcastManager lbm=LocalBroadcastManager.getInstance(this); lbm.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { //TODO } },new IntentFilter(LOCAL_ACTION)); //发送局部Broadcast Intent lbm.sendBroadcast(new Intent(LOCAL_ACTION)); lbm.sendBroadcastSync(new Intent(LOCAL_ACTION));
Pending Intent
PendingIntent类提供了一种创建可由其他应用程序在稍晚的时间触发的Intent的机制。
创建Intent Filter和Broadcast Receiver
使用Intent Filter为隐式Intent提供服务
通过使用Intent Filter,应用程序组件可以声明它们支持的动作和数据。
要把一个Activity或者Service注册为一个可能的Intent处理程序,可以在它的manifest节点中添加一个intent-filter标签并使用下面的标签
- action 使用android:name属性指定要为之服务的动作的名称。每个Intent Filter必须要有至少一个action标签。
- category 使用android:name属性来制定应该在哪种情况下为action提供服务。每个IntentFilter标签可以包含多个category标签。既可以制定自己的category也可以使用Android提供的标准值。
- data data标签允许制定组件可以执行的数据类型;根据情况,也可以包含多个数据标签。可以使用以下属性的任意组合来制定你的组件所支持的数据:
- android:host 指定一个有效的主机名
- android:mimetype 制定组件可以执行的数据类型。
- android:path 指定URI的有效路径值。
- android:port 指定主机的有效端口
- android:scheme 要求一种特定的模式
在以下的例子中,以http://blog.radioactiveyak.com形式开头的链接都将由这个Activity来处理。
<activity android:name=".MyBlogViewerActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="blog.radioactiveyak.com"/> </intent-filter> </activity>
监听电量变化
想要在一个Activity中监听电池电量或者充电状态的变化,可以使用Intent Filter注册一个Receiver来实现,该Intent Filter通过Battery Manager来监听Intent.ACTION_BATTERY_CHANGED广播。
包含当前电池电量和充电状态的Broadcast Intent是一个sticky Intent,因此不需要实现一个Broadcast Receiver就可以在任何时间获取到当前的电池状态。
IntentFilter batIntentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); Intent battery = context.registerReceiver(null, batIntentFilter); int status = battery.getIntExtra(BatteryManager.EXTRA_STATUS, -1); boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL;
监听链接变化
获取当前链接状态的详细信息,需要使用Connectivity Manager。
String svcName = Context.CONNECTIVITY_SERVICE; ConnectivityManager cm = (ConnectivityManager)context.getSystemService(svcName); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); boolean isConnected = activeNetwork.isConnectedOrConnecting(); boolean isMobile = activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE;
监听Dock变化
Android设备可以放在一个汽车的dock上或者桌子的dock上。通过注册一个Recevier来监听Intent.ACTION_DOCK_EVENT,可以确定docking的状态和类型。
和电池状态一样,dock事件的Broadcast Intent也是sticky的。
以下示例显示了当注册了一个监听dock事件的Receiver后,如何从返回的Intent中获得当前的docking状态。
IntentFilter dockIntentFilter = new IntentFilter(Intent.ACTION_DOCK_EVENT); Intent dock = context.registerReceiver(null, dockIntentFilter); int dockState = dock.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED ); boolean isDocked = dockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
在运行时管理Manifest Receiver
使用Package Manager的setComponentEnabledSetting方法,可以在运行时启动和禁用应用程序的manifest Receiver。
想要减少应用程序的开销,当应用程序不需要响应一些系统事件时,最好禁用监听这些常见系统时间的manifest Receiver。这项技术也能够让你定时执行一个基于系统事件的动作-如当设备链接到Wi-Fi时去下载一个大文件-而不用考虑每次应用程序启动后链接改变时广播的开销。
以下示例显示了如何在运行时启用和禁用一个manifest Receiver。
ComponentName myReceiverName = new ComponentName(this, MyReceiver.class); PackageManager pm = getPackageManager(); // Enable a manifest receiver pm.setComponentEnabledSetting(myReceiverName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); // Disable a manifest receiver pm.setComponentEnabledSetting(myReceiverName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); // View Output Log.d(TAG, "Is Charging? " + isCharging); Log.d(TAG, "Is Connected? " + isConnected); Log.d(TAG, "Is Mobile? " + isMobile); Log.d(TAG, "Is Docked? " + isDocked);