android菜鸟学习笔记10----Intent及<intent-filter>
关于Bundle:
注意到Activity的onCreate()方法的签名是protected void onCreate(Bundle savedInstanceState),其参数是一个Bundle实例。
Bundle以键值对的形式来存储数据,类似于Map,以便在Activity之间传递数据、状态信息。Bundle的键均为String类型,值可以是各种基本类型及可序列化的对象类型。
Bundle的常用方法:
构造方法:
Bundle()、Bundle(Bundle b)等
get系列根据键获取对应值的方法:
Object get(String key)
boolean getBoolean(String key)
boolean getBoolean(String key, boolean defaultValue)
Bundle getBundle(String key)
char getChar(String key)
char getChar(String key, char defaultValue)
int getInt(String key)
int getInt(String key, int defaultValue)
Serializable getSerializable(String key)
等等
注:根据键到Bundle实例中查找对应值,若存在,则返回对应的值。不存在时,带有默认值的方法,返回传入的默认值;不带默认值的方法返回响应的零值,如false、null、0.0f、0等。
put系列存放键值对的方法:
void putAll(Bundle map)
void putBoolean(String key, boolean value)
void putBundle(String key, Bundle value)
void putChar(String key, char value)
void putDouble(String key, double value)
void putSerializable(String key, Serializable value)
等等
其他方法:
boolean isEmpty() 判断Bundle实例是否为空。
Set<String> keySet() 返回当前Bundle实例中key的集合。
int size() 返回Bundle实例键值对的数目。
关于Intent:
注意到,在前面的程序中,从一个Activity启动另一个Activity,总是这样做:
1 Intent intent = new Intent(FirstActivity.this, SecondActivity.class); 2 3 startActivity(intent);
可知,Intent可以用来表示一种启动Activity的意图,用来启动另一个Activity。
其实,Intent是一种消息传递机制,可以在应用程序中使用,也可以在应用程序之间使用。不仅可以用来启动Activity还可以用来启动Service和BroadcastReceiver。而且,还可以利用Intent与被启动的组件进行数据传输,信息交换。
Intent的构造方法:
Intent()
Intent(Intent o)
Intent(String action)
Intent(String action, Uri uri)
Intent(Context packageContext, Class<?> cls)
Intent(String action, Uri uri, Context packageContext, Class<?> cls)
Intent中的主要属性:
private String mAction;
private HashSet<String> mCategories;
private Uri mData;
private String mType;
private ComponentName mComponent;
private Bundle mExtras;
注意到,源码中属性的命名方式都是以m打头,表示这是一个member。
1)Component属性:指明了该Intent要启动的组件。该属性是一个ComponentName类型的对象,其构造方法有:
public ComponentName(String pkg, String cls):
pkg应为Manifest.xml中声明的Manifest节点的package属性声明的包名
cls应为要启动组件的完整类名:包名.类名的形式。
public ComponentName(Context pkg, String cls)
pkg为要启动的上下文环境,可以传入当前Activity实例的引用,如this。若是在内部类中,则应为MainActivity.this(注:MainActivity为当前Activity类名)
cls为要启动组件的完整类名:包名.类名的形式。
public ComponentName(Context pkg, Class<?> cls)
pkg为要启动的上下文环境,可以传入当前Activity实例的引用,如this。若是在内部类中,则应为MainActivity.this(注:MainActivity为当前Activity类名)
cls为要启动组件,如SecondActivity.class(注:SecondActivity为要启动的组件名)
总结:这三个构造方法都是要指定启动的上下文环境和一个要启动组件的完整类限定名,即传入包名.类名。由于在Android应用中包名是作为应用的唯一标识,所以包名与代表应用上下文环境的Context是一一对应的。
如:
第一个构造代码:
1 Intent intent1 = new Intent(); 2 3 intent1.setComponent(new ComponentName("cn.csc.lifecycle", "cn.csc.lifecycle.NormalActivity")); 4 5 startActivity(intent1);
第二个构造代码:
1 Intent intent1 = new Intent(); 2 3 intent1.setComponent(new ComponentName(this, "cn.csc.lifecycle.NormalActivity")); 4 5 startActivity(intent1);
第三个构造代码:
1 Intent intent1 = new Intent(); 2 3 intent1.setComponent(new ComponentName(this, NormalActivity.class)); 4 5 startActivity(intent1);
Intent提供了一种简化的设置Component属性的方法,即:
Intent(Context packageContext, Class<?> cls)
这也是我们之前一直用的一种方式。
1 startActivity(new Intent(this, NormalActivity.class));
注意:设置了Component属性的Intent因为已然明确指定要启动的组件,故而被称之为显式Intent。当然,有显式Intent,肯定就有隐式Intent。
隐式Intent不指定Component属性,而是通过设置其他属性,来制定所要启动的组件应该具备的条件,然后,符合Intent其他属性所指定的条件的组件就会被启动。这时,运行时会使用一个称为“Intent解析”的过程来动态选择符合条件的组件。在Intent中设置要启动组件所需要具备的条件,就要用到Intent的mAction和mCategories等属性。
2)Action和Category属性:
源代码中属性的声明如下:
private String mAction;
private HashSet<String> mCategories;
可知,Action属性是一个普通的字符串,只能有一个,Category属性也是字符串,可以有多个,共同存放在一个名为mCategories的HashSet中。
其中,Action表示该Intent所要启动的组件应该能完成的抽象动作,比如Intent.ACTION_VIEW,可以完成查看动作,Intent.ACTION_DIAL可以完成拨号的动作等等。Category属性则用于为Action增加额外的附加类别信息。
如何让自己的组件具备Intent所要求的条件呢?
这里就需要在Manifest.xml中,注册应用所需组件,如Activity时,给Activity节点添加上<action>和<category>子节点,设置这两个节点的name属性,使其值与Intent中Action属性和Category属性一一对应即可。
如:
1 Intent intent1 = new Intent(); 2 3 intent1.setAction("myaction"); 4 5 startActivity(intent1);
要使此时自己定义的NormalActivity能被启动,则应在Manifest.xml中这样配置:
1 <activity android:name=".NormalActivity"> 2 3 <intent-filter > 4 5 <action android:name="myaction"/> 6 7 </intent-filter> 8 9 </activity>
运行程序,发现如下错误:
查看LogCat错误信息:
android.content.ActivityNotFoundException: No Activity found to handle Intent { act = myaction}
找不到能够接收处理action为”myaction”的Intent意图。
但是,我们明明在<intent-filter>中声明了的。
其实,这里只要修改一下<intent-filter>即可:
1 <intent-filter > 2 3 <action android:name="myaction"/> 4 5 <category android:name="android.intent.category.DEFAULT"/> 6 7 </intent-filter>
这样,就能正常启动NormalActivity。
原因在于:当我们不明确设定Intent的Category属性时,其值默认为Intent.CATEGORY_DEFAULT常量,该常量值为"android.intent.category.DEFAULT"。
注意:Intent最多只能设置一个Action以及0~n个Category(注:0个其实,也是一个Intent.CATEGORY_DEFAULT常量);但是在Manifest.xml中为组件注册时,在<intent-filter>中可以指定多个<action>,多个<category>。只要该组件所注册的<action>和<category>能涵盖在Intent中设置的所有Action和Category即可被启动。
如上例中,修改<intetn-filter>如下:
1 <intent-filter > 2 3 <action android:name="myaction"/> 4 5 <action android:name="myaction1"/> 6 7 <category android:name="mycategory"/> 8 9 <category android:name="android.intent.category.DEFAULT"/> 10 11 </intent-filter>
仍能被上面的Intent启动。
如,修改Intent如下:
1 Intent intent1 = new Intent(); 2 3 intent1.setAction("myaction"); 4 5 intent1.addCategory("mycategory"); 6 7 startActivity(intent1);
仍然符合条件。
注意,设置Action的方式是setAction(),而由于Category可以有多个,设置的方式不是setCategory()而是addCategory()。
Intent也提供了对应的构造方法来简化Action的设置:
Intent(String action)
Intent(String action, Uri uri)
其中第二个构造函数的第二个参数为Uri对象,这就涉及到Intent的Data属性。
3)Data属性:
Data属性用于向Action属性所表示的动作提供所要操作的数据,如Action为Intent.ACTION_CALL表示要拨号,它对应的Data就应当为要拨打的号码。
Data属性接受一个Uri对象,通常以字符串”scheme://host:port/path”的形式表示:
如:”http://www.baidu.com” ,”tel:110”等。
如,修改Intent代码:
1 Intent intent1 = new Intent(); 2 3 intent1.setAction(Intent.ACTION_CALL); 4 5 intent1.setData(Uri.parse("tel:110")); 6 7 startActivity(intent1);
将启动模拟器的拨号器,并拨打110
当然,在运行程序之间需要在Manifest.xml中Manifest节点下,添加<uses-permisson>子节点,配置该程序所需要的拨打电话的权限:
<uses-permission android:name="android.permission.CALL_PHONE"/>
此时,如果修该NormalActivity的<intent-filter>
添加如下Action: <action android:name="android.intent.action.CALL"/>,想要让它也能被Intent启动,发现还是只启动拨号器。
这是因为,Intent设置了Data属性,要想让NormalActivity能被该Intent启动,也要在<intent-filter>中设置<data>子节点:<data android:scheme="tel"/>
此时,由于有多个组件具备Intent启动所要求的条件,故而出现选择列表,让用户来选择要启动的组件。
运行效果:
注意到,上面<data>子节点只设置了scheme属性,则所有scheme属性为tel的Data属性都能符合条件。
若修改Intent:
1 Intent intent1 = new Intent(); 2 3 intent1.setAction(Intent.ACTION_VIEW); 4 5 intent1.setData(Uri.parse("http://www.baidu.com")); 6 7 startActivity(intent1);
此时,会启动浏览器,浏览http://www.baidu.com
修改NormalActivity的<intent-filter>添加
<action android:name="android.intent.action.VIEW"/>
修改<data>子节点: <data android:scheme="http"/>
重新运行:
若在<data>节点中设置android:host=“www.baidu.com”此时,也是可以启动NormalActivity的。但是,若Intent的Data属性换成”http://www.taobao.com”此时就不能启动NormalActivity了,因为它的<data>不符合条件。
只有<data>的属性与Data一致时,才能被Intent所启动。一般只指定android:scheme属性即可,不会给出太具体的属性。
注意:<intent-filter>节点中可以有0~N个<action>、0~N个<category>、0或1个<data>。
4)Type属性:用于指定该Data所指定的Uri对应的MIME类型,可以是任何自定义的MIME类型,只要符合abc/xyz格式的字符串就行。如:text/html等。
Data属性与Type属性会相互覆盖:谁最后被设置,谁就起作用,之前被设置的就被覆盖。若Data被覆盖,则getData()返回null,若Type被覆盖,getType()返回null。
1 Intent intent1 = new Intent(); 2 3 intent1.setAction(Intent.ACTION_VIEW); 4 5 intent1.setData(Uri.parse("http://www.baidu.com")); 6 7 intent1.setType("text/html");
此时,调用intent1.getData()返回值为null。data节点若设置如下:
<data android:mimeType="text/html"/> 则能被intent1启动。但是若:
<data android:scheme="http" android:mimeType="text/html"/> ,这时就不能启动了,因为比Type值多出来个scheme属性。
若是希望同时设置Data和Type属性,则必须要使用setDataAndType()方法,如:
intent1.setDataAndType(Uri.parse("http://www.baidu.com"), "text/html");
此时,浏览器一定会被启动,若要NormalActivity也能被启动,其<data>节点,一定要同时设置android:scheme和android:mimeType属性:
<data android:scheme="http" android:mimeType="text/html"/>
5)Extras属性:
注意到private Bundle mExtras; mExtras的类型是Bundle。所以Intent可以借助其Bundle类型的mExtras属性在Activity之间进行数据传递。
Bundle getExtras() 获取mExtras属性。
Intent putExtras(Bundle extras) 设置mExtras属性。
可以调用Bundle的相关方法设置好要保存的键值对,然后再把Bundle实例传给putExtras()方法。Intent提供了更简单的键值对操作方式:
putExtra(String name, XXX value) :向Intent中存入name-value的键值对,实际上是存入mExtras中。XXX表示不同的数据类型,如同Bundle中的putInt(),putDouble()等putXXX()。
getXxxExtra(String name):从Intent中按照key获取对应的值。实际上是到mExtras这个Bundle实例中去获取值。如同Bundle中的getInt(),getDouble()等getXxx()。
利用Intent在Activity之间传递数据:
前面曾经提到过Activity中有个名为getIntent()的方法,可以用来获取启动该Activity的Intent实例。
如,在MainActivity中获取启动它的Intent实例,并调用其toString()方法,将其设置为第一个按钮的text属性:
1 protected void onCreate(Bundle savedInstanceState) { 2 3 super.onCreate(savedInstanceState); 4 5 setContentView(R.layout.main_layout); 6 7 Log.i("LIFECYCLE","onCreate"); 8 9 Button btnNormal = (Button) findViewById(R.id.normal); 10 11 Button btnDialog = (Button) findViewById(R.id.dialog); 12 13 btnNormal.setOnClickListener(this); 14 15 btnDialog.setOnClickListener(this); 16 17 btnNormal.setText(getIntent().toString()); 18 19 }
从MainActivity向NormalActivity传递一个名为”data”,值为” data given by MainActivity”的数据:
1 Intent intent1 = new Intent(this, NormalActivity.class); 2 3 intent1.putExtra("data", "data given by MainActivity"); 4 5 startActivity(intent1);
在NormalActivity中获取这个值,并将其设置为TextView的text属性:
1 protected void onCreate(Bundle savedInstanceState) { 2 3 super.onCreate(savedInstanceState); 4 5 setContentView(R.layout.first_layout); 6 7 TextView tv = (TextView) findViewById(R.id.tv); 8 9 tv.setText(getIntent().getStringExtra("data")); 10 11 }
如果想要让被启动的Activity返回数据给启动它的那个Activity要怎么做呢?
startActivity()启动别的Activity之后就完全不再理它了,此时,就需要另外一个方法:
startActivityForResult(Intent intent, int requestCode):该方法用于根据intent参数启动一个Activity,并期望在被启动的Activity结束时,获得一个返回结果。
调用startActivityForResult()之后,被启动的Activity结束后返回前一个Activity时,会回调它的onActivityResult(int requestCode, int resultCode, Intent intent),其中,requestCode代表请求码,resultCode代表被启动Activity设置的返回结果码,intent里面包含了返回的数据。
被启动的Activity要给启动它的Activity返回数据时,需要调用setResult()方法设置要返回的数据。
如:MainActivity中使用startActivityForResult()启动NormalActivity,并重写onActivityResult()方法,接收NormalActivity传回的数据。
1 Intent intent1 = new Intent(this, NormalActivity.class); 2 3 intent1.putExtra("data", "data given by MainActivity"); 4 5 startActivityForResult(intent1,0);
1 protected void onActivityResult(int requestCode, int resultCode, Intent intent) { 2 3 // TODO Auto-generated method stub 4 5 super.onActivityResult(requestCode, resultCode, intent); 6 7 if(intent == null){ 8 9 Log.i("tag","null"); 10 11 }else{ 12 13 Log.i("tag",intent.getStringExtra("res")); 14 15 } 16 17 }
NormalActivity重写返回按钮按下的回调函数onBackPressed()向MainActivity返回数据:
1 public void onBackPressed() { 2 3 Log.i("tag","back pressed"); 4 5 setResult(Activity.RESULT_OK,getIntent().putExtra("res", "resultxxx")); 6 7 super.onBackPressed(); 8 9 }
此时,要注意,如果super.onBackPressed()放在该方法的第一句,无论后面设置什么值都不会被成功传递回去。
查看Activity的onBackPressed()方法的源代码,即可发现原因:
1 public void onBackPressed() { 2 3 if (!mFragments.popBackStackImmediate()) { 4 5 finish(); 6 7 } 8 9 }
这个方法会直接调用finish()直接结束该Activity实例。
1 public void finish() { 2 3 if (mParent == null) { 4 5 int resultCode; 6 7 Intent resultData; 8 9 synchronized (this) { 10 11 resultCode = mResultCode; 12 13 resultData = mResultData; 14 15 } 16 17 if (false) Log.v(TAG, "Finishing self: token=" + mToken); 18 19 try { 20 21 if (resultData != null) { 22 23 resultData.prepareToLeaveProcess(); 24 25 } 26 27 if (ActivityManagerNative.getDefault() 28 29 .finishActivity(mToken, resultCode, resultData)) { 30 31 mFinished = true; 32 33 } 34 35 } catch (RemoteException e) { 36 37 // Empty 38 39 } 40 41 } else { 42 43 mParent.finishFromChild(this); 44 45 } 46 47 }
阅读finish()的源码可以发现:若发现当前Activity需要返回数据,则直接传递null回去,然后结束掉当前Activity实例。
所以,无论我们在super.onBackPressed()后设置任何返回数据都不会被真正传回。
当然,也可以不调用super.onBackPressed(),在设置完成要返回的数据之后,自己调用一个finish()方法即可。