Android Activity启动方式

这里说的Activity启动方式,包括显式和隐式启动

Intent: 是启动一个Activity , service, broadcast时发出的一个意图。Intent主要用于代码中,包含一个Action ,多个Category , 一些数据。

Intent Filter:是定义一个Activity , service, broadcast时的过滤器。多用于AndroidManifest.xml方中定义组件。在代码中主要用于动态注册我们的广播(BroadcastReceiver)。

一、显式(设置Component)

显式Intent,通过Component可以直接设置需要调用的Activity类,可以唯一确定一个Activity,意图特别明确,所以是显式的。

1、构造方法传入Component

Intent intent = new Intent(IntentActivity.this, IntentSecondActivity.class);
startActivity(intent);

我们来看看Intent构造方法源码,我们可以看到,里面实质上还是转换成了Component

/**
     * Create an intent for a specific component.  All other fields (action, data,
     * type, class) are null, though they can be modified later with explicit
     * calls.  This provides a convenient way to create an intent that is
     * intended to execute a hard-coded class name, rather than relying on the
     * system to find an appropriate class for you; see {@link #setComponent}
     * for more information on the repercussions of this.
     *
     * @param packageContext A Context of the application package implementing
     * this class.
     * @param cls The component class that is to be used for the intent.
     *
     * @see #setClass
     * @see #setComponent
     * @see #Intent(String, android.net.Uri , Context, Class)
     */
    public Intent(Context packageContext, Class<?> cls) {
        mComponent = new ComponentName(packageContext, cls);
    }

2、setClass/setClassName

Intent intent = new Intent();
		intent.setClass(IntentActivity.this, IntentSecondActivity.class);
//		intent.setClassName(IntentActivity.this, "com.ulex.androiddemo.IntentSecondActivity");
//		intent.setClassName("com.ulex.androiddemo", "com.ulex.androiddemo.IntentSecondActivity");
		startActivity(intent);
setClass源码,发现里面也是setComponent

public Intent setClass(Context packageContext, Class<?> cls) {
        mComponent = new ComponentName(packageContext, cls);
        return this;
    }

3、setComponent方法

ComponentName componentName=new ComponentName(this, IntentSecondActivity.class);
// ComponentName componentName=new ComponentName(IntentActivity.this, "com.ulex.androiddemo.IntentSecondActivity");
// ComponentName componentName=new ComponentName("com.ulex.androiddemo", "com.ulex.androiddemo.IntentSecondActivity");
	
   Intent intent=new Intent();
   intent.setComponent(componentName);
   startActivity(intent);
Component源码,几个构造方法里面的都是为了获取到mPackage和mClass

 /**
     * Create a new component identifier from a Context and Class object.
     * 
     * @param pkg A Context for the package implementing the component, from
     * which the actual package name will be retrieved.
     * @param cls The Class object of the desired component, from which the
     * actual class name will be retrieved.
     */
    public ComponentName(Context pkg, Class<?> cls) {
        mPackage = pkg.getPackageName();
        mClass = cls.getName();
    }
根据上面的分析可以知道,显式Intent的跳转,不管用哪种方式,最终都会于setComponent的方式进行启动


二、隐式启动

隐式不明确指定启动哪个Activity,而是设置Action、Data、Category,让系统来筛选出合适的Activity。
筛选是根据所有的<intent-filter>来筛选。

(1) 设置Action和Category

<activity android:name=".IntentSecondActivity" >
            <intent-filter>
                <action android:name="com.ulex.second" />
                <!-- 必须加上category.DEFAULT -->
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
</activity>

Intent intent=new Intent();
intent.setAction("com.ulex.second");
//可以不加下面这行,因为intent的这个属性默认值即系Intent.CATEGORY_DEFAULT
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivity( intent );</span>
备注:

任何一个需要隐式启动的Activity都必须要有这项:

<category android:name="android.intent.category.DEFAULT"/>
例外情况是:android.intent.category.MAIN和android.intent.category.LAUNCHER的filter中没有必要加入android.intent.category.DEFAULT,当然加入也没有问题


通过setAction启动Activity很常用也很简单,但对于下面几个问题是否有考虑过?

1、当不存在匹配的Action时,启动会怎样?

2、若存在多个可匹配的Action时,会如何启动?(包括相同App里面的多个相同action(这种情况应该禁止,在此只是拿来说明问题)以及不同app间相同action问题)

3、既然应用程序间可以互相调用,那我们怎么保护我们Activity的安全问题?


不急,我们一个一个问题来,先看不存在可匹配的Action时,会怎样,代码就不附了,只要setAction 那里随便写一个运行程序就可以看出效果:


可以看到,但程序发现没有可匹配的Action时,采取了最简单粗暴的方法,崩溃~

这是一种很影响用户体验的事情,该如何避免或者说更优雅的处理这种问题?若要避免,自然不用多少,保证跳转action在AndroidMainfest.xml有注册,废话。Android提供了一个方法resolveActivity()可以判断能否匹配到要跳转的action:

Intent intent = new Intent("com.ulex.first");
 if (intent.resolveActivity(getPackageManager()) == null) {
    // TODO:没有可匹配的Action
    return;
  }
startActivity(intent);
来看看resolveActivit源码:

public ComponentName resolveActivity(PackageManager pm) {
        if (mComponent != null) {
            return mComponent;
        }

        ResolveInfo info = pm.resolveActivity(
            this, PackageManager.MATCH_DEFAULT_ONLY);
        if (info != null) {
            return new ComponentName(
                    info.activityInfo.applicationInfo.packageName,
                    info.activityInfo.name);
        }

        return null;
 }
resolveActivity方法返回值就是显式Intent上面讲到的ComponentName对象,一般情况下也就是系统找到的那个Activity。但是如果有多个Activity可供选择的话,则返回的Component就是用户选择Activity的那个界面对应的Activity,这里不再深究。

现在来看看第二个问题,其实上面那段话也基本透露了,有多个action时,系统可能会弹层让用户选择要跳转的Activity,为什么说可能呢,看了下面自然会明白。我们先来看一下下面这段代码会出现什么情况

<activity android:name=".IntentActivity" >
            <intent-filter>
               <action android:name="com.ulex" />
               <category android:name="com.ulex.b" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        <activity android:name=".IntentSecondActivity" >
            <intent-filter>
                <action android:name="com.ulex" />
               <category android:name="com.ulex.a" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
 Intent intent = new Intent("com.ulex");
// intent.addCategory("com.ulex.a");
  startActivity(intent);
注册的action相同,但category不一样,若只是setAction会发现这时系统会弹层让用户选择,而加上注释的代码及addCategory后,不弹层会直接跳转到IntentSecondActivity。这样是category的主要用途,给注册Activity添加过滤。

另外对于category,有几点想强调:

1、不能单独依靠category启动Activity,必须依赖于activity,注册时必须加上default的category

2、可以添加多个category,启动时,可以不addCategory,或者匹配一个就可以,但addCategory了就必须与已经注册的想对应,否则不能匹配

ok,现在我们来看看第三个问题,如果保护我们的Activity。其实也很简单,android提供了一个android:export属性,目的就是设置进程间的访问权限,默认true,也就是默认是可以进行跨进程间访问的,所以平时不注意的话,根本注意不到这个属性。对于我们现在的问题,我们只要在注册activit是将这个属性关闭即可,其他人便访问不了,对于做第三方sdk的同学会比较敏感,大家看情况设定啦。

<activity android:name=".IntentSecondActivity" 
            android:exported="false">
            <intent-filter>
                <action android:name="com.ulex" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
  </activity>


(2)Action和Data

现在我们来看看另一种隐式启动方式,与Data想结合。先来看个例子:

<activity android:name=".IntentSecondActivity" >
            <intent-filter>
                <action android:name="com.ulex" />

                <category android:name="android.intent.category.DEFAULT" />

                <data android:scheme="a" />
            </intent-filter>
 </activity>

Intent intent = new Intent("com.ulex");
intent.setData(Uri.parse("a://"));
startActivity(intent);
有没有觉得很熟悉、也很简单,这就是我们平时用setData的最简版,先说个好玩的,这里setData是用的是“a://”而不是“a”,随便写的?当然不是,那是为什么?只是因为,直接写成“a"是匹配不到的,
intent.setData(Uri.parse("a://"));或
intent.setData(Uri.parse("a:"));

 

这两种情况都可以匹配,但下面这就不行,具体原因我也没有深究

intent.setData(Uri.parse("a"));

还是来看data都有哪些属性;

android:host: 指定主机名,例如:google.com
android:port:  制定主机端口,例如: 80 
android:path:  指定URL的有效路径值,例如: /index/examples 
android:mimeType: 指定组件可以执行的数据类型,例如:image/jpeg,video/* 
android:scheme: 指定特定的模式,例如:content,http 

Uri格式:scheme://host:port/path

备注:如果scheme没有指定,那其他的属性均无效,如果host没有指定,那port,path均无效

来个例子好让大家对data标签更熟悉掌握:假设需要做一个视频播放器app,支持多种数据来源,包括:本地视频文件,本地媒体URL,网络视频流(HTTP、RTMP、RTSP协议),只支持mp4和3gpp两种文件格式。

我们先来解决支持多种数据来源这个问题,

data android:scheme="xxx"/>
这里的xxx可以是:file,content,网络协议(HTTP,RTMP、RTSP等)

<data android:scheme="file"/> 
<data android:scheme="content"/>
<data android:scheme="http"/>
<data android:scheme="rtsp"/>
数据来源URL是以“file://”、“content://”、“http://”、“rtsp://”开头的URL资源。

Intent intent = new Intent(Intent.ACTION_VIEW);        
intent.setData(Uri.fromFile(new File("/sdcard/test.3gp")));
startActivity(intent);
Uri.fromFile这条语句会把指定的文件位置转换为以“file://”开头的Uri对象,如上述例子最终得到的URL为:“file:///sdcard/test.3gp”
//网络链接
intent.setData(Uri.parse("http://ticktick.blog.51cto.com/test.mp4"));
通过这种方式,便可以播放多种数据来源的视频了,再来看看怎么限制播放的文件格式:

data android:mimeType="xxx"/>
mimeType用来设置数据类型,例如图像数据(image/png或者image/*),视频数据(video/mp4或者video/*),如果使用*代表匹配所有的子类型。
<data android:mimeType="video/3gpp"/>
<data android:mimeType="video/mp4" />

Intent intent = new Intent(Intent.ACTION_VIEW);        
intent.setDataAndType(Uri.fromFile(newFile("/sdcard/test.3gp")),"video/3gpp");
startActivity(intent);
注意,当<Intent-filter>已经添加了mimeType之后,隐式Intent必须设置Type参数才能匹配到该Activity,所以建议使用setDataAndType方法,而不是单一的setData方法。当然,这里setDataAndType的"video/3gpp"也可以写成:"video/*",但这样可能会匹配到一些不支持3gpp的播放器

下面来讲一下data使用中的注意点:

<data android:scheme="something" android:host="project.example.com" android:port="80"/>
//等同于这样写:
<data android:scheme="something"/>
<data android:host="project.example.com"/>
<data android:port="80"/>
Uri uri = Uri.parse("something://project.example.com:80"); 才可以匹配

Uri uri = Uri.parse("something://"); 类似于这样是不能匹配的

1、对于一个Activity可以有多个Data,只需匹配其中一个即可,若有注册,setdata时必须匹配到其中一个才能启动

2、跟category一样不能单纯靠data启动,需要依赖action

3、若是程序主入口(即注册了category.main和category.launcher的activity),data标签注册的<intent-filter>不能跟launcher的放一起,否则会导致应用图标在桌面消失等问题。

对于data的的使用,我们更多的是通过网络的链接跳转到app里面对应的activity,那我们应该怎样设置才能达到这样的效果?

<activity android:name=".IntentSecondActivity" >
            <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:host="ulex"
                    android:scheme="app" >
                </data>
            </intent-filter>
在html中添加链接即可:<a href="app://ulex">测试</a>

很多情况下我们希望通过隐式跳转时传递参数,那我们又该如何获取?Android提供了一系列的方法供我们选择

public String getAction(); //获取Action
public Uri getData(); //Data Uri
public String getScheme(); //Scheme
public String getType(); //MineType
public String getCategories();//Category
public String getHost();//Host

关于Intent的显式和隐式跳转就讲到这,有什么没讲到位的欢迎大家补充~~

参考资料:

http://www.bubuko.com/infodetail-684083.html

http://blog.csdn.net/rwecho/article/details/6683573

http://blog.csdn.net/xiao__gui/article/details/11392987



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值