Android开发--掌握Activity隐式显示启动方法 scheme跳转协议 生命周期及启动模式配置

Activity

  • 定义
  • Activity生命周期:
    • activity的4种状态
    • activity生命周期
      • 生命周期回调方法
      • 回调步骤
      • Activity在横竖屏切换时如何回调
  • Android进程优先级
  • Android任务栈
  • Activity启动方式
    • 显示启动
    • 隐式启动
  • Activity启动模式
    • 在manifest定义启动模式
    • 使用Intent Flags 定义启动模式
    • taskAffinity参数
  • 清空返回栈
  • scheme跳转协议
    • 协议的完整格式
    • 在Android中使用
      • 在manifest里配置目标Activity
      • 在本地进行跳转
      • 在H5页面跳转
      • 在目标Activity接收
    • scheme使用注意

定义

Activity:是Android与用户进行交互的接口,一个跟用户交互的组件,它可以是布满整块屏幕,也可以悬浮与其它界面之上,它提供了一个界面供用户点击,滑动等操作,这就是Activity的意义

Android是通过栈的方式来管理Activity的,一个Activity是什么状态决定了它在栈中处于什么位置,你能看到的页面即处于前端的Activity,它总是位于栈顶,当它销毁后,处于第二层的Activity会被推到栈顶;如果有新的Activity进入到栈顶,那原Activity就会被压入到栈的第二层。一个Activity在栈中位置的变化反应了它不同状态的转换

Activity生命周期:

activity的4种状态

  • running:表明activity正活跃,用户可以点击屏幕,应用做出相应;它是一个处于Activity栈顶的一个状态
  • paused:表明Activity失去焦点的时候,或者被一个非全屏的activity占据,或者被一个透明的activity放置在栈顶;这时候Activity失去了和用户交互的能力;不是说整个Activity被销毁,这时候它的状态信息和成员变量都在;如果处于内存不足的时候会被回收
  • stopped:一个Activity被另一个Activity完全覆盖的时候,这个被覆盖的activity就处于stopped状态;已经不可见了;这时候它的状态信息和成员变量都在;如果处于内存不足的时候会被回收
  • killed:表面activity已经被系统回收掉了,它所保存的信息和成员变量都不在了。

activity生命周期

Android开发--掌握Activity隐式显示启动方法 scheme跳转协议 生命周期及启动模式配置_第1张图片

生命周期回调方法

  • onCreate:顾名思义,这是创建的意思,也就是创建一个activity,这是第一步,并且一个activity在一个完整的生命周期里只会回调一次onCreate。在这里主要进行一些初始化的操作,比如setcontentview加载布局,进行控件的初始化,绑定按钮事件,此时activity还在后台,不可见。切记不能在这里进行复杂耗时操作,要不然进去改activity可能会黑屏一些时间,然后才显示出来。

  • onStart:见名知意,这表示启动,这是回调的第二个方法,此时activity已经可见但未获取焦点,用户还不能与其交互,在一个完整的生命周期里最少会回调一次onStart。

  • onResume:当回调到这个方法,activity获得焦点并与我们眼对眼见面了,可以开始各种交互了。此时activity处于栈顶,在一个完整的生命周期里最少会回调一次onResume。

  • onPause:此时activity还可见但失去焦点,无法进行交互,在这里也不要做耗时操作,因为回调这个方法完才会跳转到其它activity,so。。。,而且android中指定如果onPause在500ms内没有执行完毕的话就会强制关闭Activity。

  • onStop:表示停止,此时activity已经不可见了,但是activity还在内存中,没有被回收,但是内存如果紧张,有可能被回收

  • onDestroy:到这activity就从内存销毁了,在这可以进行一些资源的释放

  • onRestart():表明activity正在重新启动,是不可见状态回到可见状态时候被调用

回调步骤

  • 打开activity A 回调周期函数 onCreate->onStart->onResume
  • 按返回键回调周期函数 onPause->onStop->onDestroy
  • 按home键进入桌面回调周期函数 onPause->onSaveInstanceState->onStop 再返回此activity回调周期函数 onRestart->onStart->onResume
  • 按home键进入多任务界面回调周期函数 onPause->onSaveInstanceState->onStop 再返回此activity回调周期函数 onRestart->onStart->onResume
  • 下拉屏幕点击任一系统按钮进入回调周期函数 onPause->onSaveInstanceState->onStop 再返回此activity回调周期函数 onRestart->onStart->onResume
  • 按电源键回调周期函数 onPause->onSaveInstanceState->onStop 再返回此activity回调周期函数 onRestart->onStart->onResume
  • 从activity A 跳转到 activity B,首先A执行 onPause-> onSaveInstanceState-> 然后B执行 onCreate->onStart->onResume 最后A执行 onStop->(onStop是activity真正被挤出栈顶且不可见时调用),然后按返回键返回 首先B执行onPause-> 然后A执行onRestart->onStart->onResume 最后B执行 onStop->onDestroy

Activity在横竖屏切换时如何回调

  • 没有在AndroidManifest.xml里给Activity配置android:configChanges属性时(如果配置了但是没有配置screenSize属性也是下面一样情况)
    竖屏界面A:onCreate -> onStart -> onResume
    横屏时竖屏界面A:onPause -> onSaveInstanceState-> onStop -> onDestroy,再执行横屏界面B onCreate -> onStart -> onRestoreInstanceState - > onResume
    再竖屏时横屏界面B:onPause -> onSaveInstanceState-> onStop -> onDestroy 再执行竖屏界面A onCreate -> onStart -> onRestoreInstanceState -> onResume。

  • 设置Activity的android:configChanges=”orientation|keyboardHidden|screenSize”时

    竖屏界面A:onCreate -> onStart -> onResume

    横屏时回调onConfigurationChanged方法,竖屏时回调onConfigurationChanged方法,不会再执行生命周期函数

  • 如果设置了android:screenOrientation="portrait"属性,portrait表示竖屏,landscape表示横屏,那屏幕就不会旋转了,也就不会有生命周期变化了,也 不会回调onConfigurationChanged方法。

这里其实我用的是5.0版本的手机试的,可能低版本会有不一样,当orientation和screenSize只配一个的话,情况是跟第一种一样的,只有两个都配置,才是第 二种情况,keyboardHidden是为了让输入键盘隐藏,最好这三个属性都配置,以适应大多数版本,无奈,版本更新差别太大了。以后有不同机型测试后再来补 充


Android进程优先级

前台 / 可见 / 服务 / 后台 / 空

  • 前台进程 Active Process:一般是前台正在交互的activity、与前台activity绑定的service、正在执行onReceive事件处理的函数的BroadCast Receiver,这几种情况的进程就是可见进程,这些是android通过回收资源尽力保护的进程

  • 可见进程 Visible Process:比如一个activity处于可见但并不是处于前台或者不响应用户事件,处于暂停(OnPause)状态;还有一种就是被这种Activity绑定的Service,这种就是可见进程;这些情况一般发生在当一个activity被部分遮盖的时候(被一个非全屏或者透明的Activity)。可见进程只在极端的情况下,才会被杀死来保护前台进程的运行。

  • 服务进程 Service Process:包含已经启动的service,service以动态的方式持续运行但没有可见的界面。因为Service不直接和用户交互,它们拥有比Visible Process较低的优先级

  • 后台进程 Background Process:进程中的Activity不可见或进程中没有任何启动的service,这些进程都可以是后台进程;比如按home键,activity的前台进程就变成了后台进程;在系统中,拥有大量的后台进程,并且Android会按照后看见先杀掉的原则来杀掉后台进程以获取系统资源给前台进程

  • 空进程 Empty Process:为了改善整个系统的性能,android经常在内存中保留那些已经走完生命周期的应用程序。android维护这些缓存来改善应用程序重新启动的时间,这些进程在资源需要的时候会被杀掉


Android任务栈

Android开发--掌握Activity隐式显示启动方法 scheme跳转协议 生命周期及启动模式配置_第2张图片

任务是一个activity的集合,它用栈的方式来管理activity;这个栈被称为返回栈(back stack),栈里的activity顺序是按打开顺序放置。

当用户在home界面点击应用图标时候,这个应用的任务就会被转移到前台,如果这个应用的任务是空的,说明最近这个应用没有被启动过,系统就会去创建一个新的任务,将该应用的主activity放入到返回栈中。

当一个activity启动了一个新activity的时候,新的activity会被放置到返回栈的栈顶并获取焦点;前一个activity仍然保留在任务栈,但处于停止状态。
当用户按下返回键的时候,处于栈顶的activity会被移除掉,前一个activity就会重新回到栈顶的位置。我们只能向栈顶添加activity或者将栈顶的activity移除掉。这说明返回栈是一个典型的后进先出的数据结构
如果一直按返回键,返回栈中的activity会一个一个的被移除,最终返回到主屏幕,这时候返回栈中activity全被清空,对应的任务也就不存在了。

当打开一个应用,对应的任务处于前台;这时候点击home键回到主屏幕,任务就被转移到后台;当任务处于后台状态的时候,返回栈中的activity都进入停止状态,但在返回栈中的顺序不会变,每个activity的信息和数据都在;当处于内存不足的情况下有可能会被销毁回收
Android开发--掌握Activity隐式显示启动方法 scheme跳转协议 生命周期及启动模式配置_第3张图片

如果你的应用程序有多个入口可以启动同一个activity,默认情况下每次启动都会创建一个该activity的新的实例,并不是将下面的activity移动到栈顶;带来的一个问题就是同一个activity在一个返回栈里存在多个实例;势必会引起一些问题。
Android开发--掌握Activity隐式显示启动方法 scheme跳转协议 生命周期及启动模式配置_第4张图片

Android系统管理任务和返回栈的方式就是把所有的activity都放入到一个相同的任务当中,通过一个后进先出的栈来管理;开发者可以通过修改activity启动模式来修改这一行为。

获取栈顶应用

public class AppUtils {

    private static String PACKAGE_NAME_UNKNOWN = "unknown";

       /**
     * 获取栈顶应用包名
     * 要做版本兼容
     *  在Android5.0以前,系统允许我们通过ActivityManager的getRunningTasks()函数,直接获取当前运行的App
     *  从Android5.0开始,系统为了安全起见,开始重重设限,要求我们通过用户手动授权的方式,获得USAGE_STATS_SERVICE的访问权限,才能读到数据
     *  但是,这种方式有两个问题:
     *      要求用户手动授权,不能自动运行
     *      在碎片化系统中表现不一,有的系统无法给出权限,例如某些小米手机就无法真正获取权限
     * @param context
     * @return
     */
    public static String getTopActivityPackageName(@NonNull Context context) {

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            ActivityManager am = ((ActivityManager) context.getSystemService(ACTIVITY_SERVICE));
            List taskInfo = am.getRunningTasks(5);
            PACKAGE_NAME_UNKNOWN = taskInfo.get(0).topActivity.getPackageName();
            return PACKAGE_NAME_UNKNOWN;
        }

        final UsageStatsManager usageStatsManager = (UsageStatsManager)context.getSystemService(Context.USAGE_STATS_SERVICE);
        if(usageStatsManager == null) {
            return PACKAGE_NAME_UNKNOWN;
        }

        String topActivityPackageName = PACKAGE_NAME_UNKNOWN;
        long time = System.currentTimeMillis();
        // 查询最后十秒钟使用应用统计数据
        List usageStatsList = usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - 1000*10, time);
        // 以最后使用时间为标准进行排序
        if(usageStatsList != null) {
            SortedMap sortedMap = new TreeMap();
            for (UsageStats usageStats : usageStatsList) {
                sortedMap.put(usageStats.getLastTimeUsed(),usageStats);
            }
            if(sortedMap.size() != 0) {
                topActivityPackageName =  sortedMap.get(sortedMap.lastKey()).getPackageName();
                Log.e(TAG,"Top activity package name = " + topActivityPackageName);
            }
        }
        return topActivityPackageName;
    }

    /**
     * 获取栈顶应用前需要检查权限
     * @param context
     */
    public static void checkUsageStateAccessPermission(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if(!checkAppUsagePermission(context)) {
                requestAppUsagePermission(context);
            }
        }
    }

    public static boolean checkAppUsagePermission(Context context) {
        UsageStatsManager usageStatsManager = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
        if(usageStatsManager == null) {
            return false;
        }
        long currentTime = System.currentTimeMillis();
        // try to get app usage state in last 1 min
        List stats = usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, currentTime - 60 * 1000, currentTime);
        if (stats.size() == 0) {
            return false;
        }
        return true;
    }

    /**
     * 跳转到设置页面让用户同意
     * 同时要配置权限
     * 
     * @param context
     */
    public static void requestAppUsagePermission(Context context) {
        Intent intent = new Intent(android.provider.Settings.ACTION_USAGE_ACCESS_SETTINGS);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        try {
            context.startActivity(intent);
        } catch (ActivityNotFoundException e) {
            Log.e(TAG,"Start usage access settings activity fail!");
        }
    }

}

Activity启动方式

在Activity启动中涉及到Intent这个API,Intent的中文意思是“意图,意向”,在Android中提供了Intent机制来协助应用间的交互与通讯,Intent负责对应用中一次操作的动作、动作涉及数据、附加数据进行描述,Android则根据此Intent的描述,负责找到对应的组件,将 Intent传递给调用的组件,并完成组件的调用。Intent不仅可用于应用程序之间,也可用于应用程序内部的Activity/Service之间的交互。因此,可以将Intent理解为不同组件之间通信的“媒介”,专门提供组件互相调用的相关信息

Intent有七大属性:

  • component(组件):目的组件

  • action(动作):用来表现意图的行动

  • category(类别):用来表现动作的类别

  • data(数据):表示与动作要操纵的数据

  • type(数据类型):对于data范例的描写

  • extras(扩展信息):扩展信息

  • Flags(标志位):期望这个意图的运行模式

显示启动

这应该用的是最多的一个启动方式了,先在AndroidManifest.xml里配置activity,然后进行如下操作:

Intent intent = new Intent(MainActivity.this,CloneActivity.class); 
startActivity(intent);

隐式启动

它与显示启动最大区别就是不需要在Intent的实例化中传参, 但是需要在AndroidManifest.xml给该activity配置intentfilter属性,启动代码如下


    
    

Intent intent = new Intent();
intent.setAction("com.mangoer.activityreview");
startActivity(intent);

隐式启动的过滤过程如图

一般显示启动用于同一APP内,隐式启动用于启动APP外部活动,如果有多个组件被匹配成功,就会以对话框列表的方式让用户进行选择。每个Intent中只能指定一个action,但却能指定多个category;类别越多,动作越具体,意图越明确 在Intent添加类别可以添加多个类别,那就要求被匹配的组件必须同时满足这多个类别,才 能匹配成功。操作Activity的时候,如果没有类别,须加上默认类别
比如打开百度:

Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
Uri data=Uri.parse("http://www.baidu.com");
intent.setData(data);
startActivity(intent);

有时候我们跳转到别的activity后,希望能够携带一些数据返回到原activity,那就需要用到startActivityForResult,具体见Github
有时候可能需要携带数据跳转到其它activity,具体用法见Github

Activity启动模式

启动模式允许你去定义如何将一个activity的实例和当前任务栈进行关联,有两种方式来定义启动模式

  1. 使用manifest文件:在manifest文件中声明一个activity的时候指定launchMode属性
  2. 使用Intent flag参数:当使用Intent启动activity的时候,可以在Intent中加入flag来指定新启动的activity如何与当前任务进行关联

如果一个被启动的activity在manifest里定义了启动模式,然后在使用Intent启动的时候也设置了flag定义启动模式,那么Intent中的定义模式将会覆盖manifest中的定义

在manifest定义启动模式

通过设置launchMode属性来定义,有四种可选参数:

  1. standard:这也是默认启动模式,即标准模式;如果在该文件中声明activity的时候不指定这个参数值,这个activity就模式使用这种模式;它的意思是每启动一个activity都会重新创建一个该activity的实例加入到当前任务中,会走完整的生命周期函数,就算任务中已经存在了这个activity的实例还是会创建。这样就会出现上面说的一个activity在返回栈中存在多个实例,这其实是非常消耗资源的。
  2. singleTop:即栈顶复用模式,这个中文翻译很有意思,给出了两个很重要的点,栈顶和复用;也就是说如果要启动的activity在返回栈内已经存在了一个实例并且还处于栈顶的位置,那么就不会在重新创建一个实例了,而是复用这个activity;复用体现在哪呢,就是调用这个activity的onNewIntent方法,而不会走onCreate-onStart-onResume这个创建逻辑了;如果这个activity不在栈顶,那么还是会重新创建的。这种模式一般运用场景是一个Activity被频繁推到栈顶的情况,比如IM聊天,有很多消息过来了,不可能每点击一个消息就去新建一个Activity;新闻推送,也不可能每次点击推送消息,就去新建一个Activity
  3. singleTask:即栈内复用模式,也是一种单例模式;当启动的activity如果在栈内有实例,不管在不在栈顶都会复用这个实例,将其置于栈顶,调用这个activity的onNewIntent方法,并且将其上面的所有activity进行出栈处理,全部销毁。
    这里有个注意点:android系统会检测要启动的activity的affinity和当前任务的affinity是否相同,如果相同就会把这个activity放入到当前任务中,不同的话就会创建一个新的任务。而同一个程序中所有的activity的affinity默认都是相同的,不同的程序是不同的,这样启动别的应用的这种模式的activity会创建一个新的任务,启动自己应用的activity不会创建新的任务。这种模式运用场景比较少,一般用在比较特殊的页面,比如用户被签退了,需要重新跳转到登陆页面,这时候需要将之前的页面全部销毁,因为跟session相关的数据需要重新初始化
  4. singleInstance:即单例模式,即这个activity自己独享一个任务,所在的返回栈里面只有这个activity。
    比如给activity2设置singleInstance模式,从activity1跳转到activity2,activity2跳转到activity3,这时候按返回键会返回到activity1.因为Activity2独自占用一个任务栈,而activity1和activity2共用一个任务栈
    再举一个例子,Android系统内置的浏览器程序声明自己浏览网页的Activity始终应该在一个独立的任务当中打开,也就是通过在元素中设置"singleInstance"启动模式来实现的。这意味着,当你的程序准备去打开Android内置浏览器的时候,新打开的Activity并不会放入到你当前的任务中,而是会启动一个新的任务。而如果浏览器程序在后台已经存在一个任务了,则会把这个任务切换到前台,不会再重新创建。

使用Intent Flags 定义启动模式

使用方法就是在使用startActivity的时候构建Intent,对Intent加入一个flag来改变Activity与任务的关联模式

  1. FLAG_ACTIVITY_NEW_TASK:当Intent对象包含这个标记时,系统会寻找或创建一个新的task来放置目标Activity,寻找时依据目标Activity的taskAffinity属性进行匹配,如果找到一个task的taskAffinity与之相同,就将目标Activity压入此task中,如果查找无果,则创建一个新的task,并将该task的taskAffinity设置为目标Activity的taskActivity,将目标Activity放置于此task。注意,如果同一个应用中Activity的taskAffinity都使用默认值或都设置相同值时,应用内的Activity之间的跳转使用这个标记是没有意义的,因为当前应用task就是目标Activity最好的宿主
  2. FLAG_ACTIVITY_SINGLE_TOP:设置了这个flag,如果要启动的Activity在当前任务中已经存在了,并且还处于栈顶的位置,那么就不会再次创建这个Activity的实例,而是直接调用它的onNewIntent()方法;否则会再创建这个activity;这种flag和在launchMode中指定"singleTop"模式所实现的效果是一样的。
  3. FLAG_ACTIVITY_CLEAR_TOP:设置了这个flag,如果要启动的Activity在当前任务的栈顶,就会销毁这个实例并重新创建这个activity;如果不在栈顶,还是销毁已存在的实例并清空这个实例上面的所有activity,最后重新创建这个activity。
  4. 如果将Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP两个结合用,如果要启动的Activity已经存在任务栈而且不在栈顶,那么会清空这个activity以上的所有activity,并回调这个activity的onNewIntent,不会重新创建这个activity;如果处于栈顶,就直接回调onNewIntent

taskAffinity参数

affinity可以用于指定一个Activity更加愿意依附于哪一个任务,在默认情况下,同一个应用程序中的所有Activity都具有相同的affinity,所以,这些Activity都更加倾向于运行在相同的任务当中。当然了,你也可以去改变每个Activity的affinity值,通过元素的taskAffinity属性就可以实现了。
taskAffinity属性接收一个字符串参数,你可以指定成任意的值(经我测试字符串中至少要包含一个.),但必须不能和应用程序的包名相同,因为系统会使用包名来作为默认的affinity值。
通常可以与allowTaskReparenting属性配合使用,设置为true时,Activity就拥有了一个转移所在任务的能力。具体点来说,就是一个Activity现在是处于某个任务当中的,但是它与另外一个任务具有相同的affinity值,那么当另外这个任务切换到前台的时候,该Activity就可以转移到现在的这个任务当中。


清空返回栈

如果用户将任务切换到后台之后过了很长一段时间,系统会将这个任务中除了最底层的那个Activity之外的其它所有Activity全部清除掉。当用户重新回到这个任务的时候,最底层的那个Activity将得到恢复。这个是系统默认的行为,因为既然过了这么长的一段时间,用户很有可能早就忘记了当时正在做什么,那么重新回到这个任务的时候,基本上应该是要去做点新的事情了。

当然,既然说是默认的行为,那就说明我们肯定是有办法来改变的,在元素中设置以下几种属性就可以改变系统这一默认行为:

  1. alwaysRetainTaskState:如果将最底层的那个Activity的这个属性设置为true,那么上面所描述的默认行为就将不会发生,任务中所有的Activity即使过了很长一段时间之后仍然会被继续保留。
  2. clearTaskOnLaunch:如果将最底层的那个Activity的这个属性设置为true,那么只要用户离开了当前任务,再次返回的时候就会将最底层Activity之上的所有其它Activity全部清除掉。简单来讲,就是一种和alwaysRetainTaskState完全相反的工作模式,它保证每次返回任务的时候都会是一种初始化状态,即使用户仅仅离开了很短的一段时间。
  3. finishOnTaskLaunch:这个属性和clearTaskOnLaunch是比较类似的,不过它不是作用于整个任务上的,而是作用于单个Activity上。如果某个Activity将这个属性设置成true,那么用户一旦离开了当前任务,再次返回时这个Activity就会被清除掉。

scheme跳转协议

scheme 是一种页面之间跳转的协议,不仅可以用于app之间进行跳转,还可以用于 H5 页面跳转到app页面,通过定义自己的scheme协议,可以非常方便跳转app中的各个页面,主要用于支持以下几种场景

  1. 通过scheme协议,可以通过一个App跳转到另一个App
  2. 通过scheme协议,可以通过通知栏消息定制化跳转页面
  3. 通过scheme协议,可以由h5页面跳转app原生页面

协议的完整格式

scheme 协议定义和 http 协议类似,都是标准的 URI 结构

[scheme:][//host:port][path][?query][#fragment]
  • scheme : 协议名称 - 必须
  • host : 协议地址 - 必须
  • port : 协议的端口,选填
  • path : 协议路径,可用 / 连接多个,选填
  • query : 携带的参数可用 & 连接多个,选填
  • fragment : 锚点,选填

我们从一个例子来看

mango://appwork:8080/man?id=8897&name=angel

  1. mango:这个代表scheme协议名,通过协议名去筛选需要调起的activity;可以通过uri.getScheme()获取;比如是http://开头的,就会调起浏览器程序
  2. appwork:8080:代表Scheme协议域名,由host 和port组成;可以通过uri.getAuthority()获取完整格式数据,或者uri.getHost()获取appwork,uri.getPort()获取8080
  3. /man:表示指定页面(路径),可以通过uri.getPath()获取;这个值可以拼接多个,比如/man/man/man
  4. id=8897&name=angel:就是具体的参数名对应具体值,所有的参数名可以通过uri.getQueryParameterNames()获取到一个Set集合,可以通过uri.getQueryParameter(“id”)获取具体参数值

在Android中使用

在manifest里配置目标Activity

 
     
     
     
 

匹配规则:

  • 如果只指定了scheme,那么所有带有该sheme的URI都能匹配到该intent-filter,也就是能跳转到该Activity
  • 如果只指定了scheme和authority(authority包括host和port两部分)而没有指定path,那么所有具有相同scheme和authority的URI都能匹配到该intent-filter,而不用考虑path为何值
  • 如果同时指定了scheme、authority和path,那么只有具有相同scheme、authority和path的URI才能匹配到该intent-filter

注意:如果将data标签加在了APP的启动Activity里,那么该App的桌面图标将会隐藏;否则需要另外添加一个intent-filter,将其添加到里面;如果希望在浏览器的网页上或者H5里通过scheme协议唤起应用,那么需要添加一个标签



data的完整配置如下:



在使用scheme协议跳转到具体页面时需要先检查下目的页面是否存在

public boolean valldCheck(Uri uri){
        PackageManager packageManager = getPackageManager();
        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
        List activities = packageManager.queryIntentActivities(intent, 0);
       return activities.isEmpty();
}

在本地进行跳转

Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("mango://appwork/man?id=8897&name=angel"));
startActivity(intent);

在H5页面跳转

写一个html,放在assets目录下




    
    Title


打开应用


然后通过webview加载这个文件

webview.loadUrl("file:///android_asset/index.html");

这时候点击后就可以跳转到指定的Activity了

我们也可以通过WebViewClient的shouldOverrideUrlLoading方法去解析H5页面中的链接,然后进行相应的跳转,也方便我们判断要跳转的应用是否安装了

mWebView.setWebViewClient(new WebViewClient() {
                  @Override
                  public boolean shouldOverrideUrlLoading(WebView view, String url) {

                      Log.e(TAG,"shouldOverrideUrlLoading url="+url);
                      // 根据协议的参数,判断是否是所需要的url
                      // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
                      //假定传入进来的 url = "mango://appwork/man?id=8897&name=angel"(同时也是约定好的需要拦截的)
                      Uri uri = Uri.parse(url);
                      // 如果url的协议 = 预先约定的 mango 协议
                      // 就解析往下解析参数
                      if ( uri.getScheme().equals("mango")) {

                          // 如果 authority  = 预先约定协议里的 appwork,即代表都符合约定的协议
                          // 所以拦截url,下面JS开始跳转到native指定页面
                          if (uri.getAuthority().equals("appwork")) {

                              Intent intent = new Intent(Intent.ACTION_VIEW,uri);
                              startActivity(intent);
                          }
                          return true;
                      }
                      return super.shouldOverrideUrlLoading(view, url);
                  }
              }
        );
    }

在目标Activity接收

Uri data = getIntent().getData();
if (data != null) {
    Log.e(TAG,"data="+data.toString());
    String scheme = data.getScheme();
    String authority = data.getAuthority();
    String host = data.getHost();
    int port = data.getPort();
    String path = data.getPath();
    Set queryParameterNames = data.getQueryParameterNames();
    for (String key : queryParameterNames) {
        Log.e(TAG,"key="+key);
        String value = data.getQueryParameter(key);
        Log.e(TAG,"value="+value);
    }
}

scheme使用注意

  1. 当别人使用scheme协议调用我们的activity的时候,如果使用FLAG_ACTIVITY_NEW_TASK启动activity,由上面使用Intent Flags 定义启动模式可知,这时候系统可能为这个activity新建一个任务,导致与被调用的应用的其它activity不处于同一个任务,这时候需要在manifest里给activity设置taskAffinity值,通常是应用包名,强制处于同一个任务中。

  2. URI中的参数如果包含特殊字符,需要先进行url编码

  3. 当应用A启动应用B的activity时候,可能应用B所在的任务还是处于后台,这样需要在应用B被启动的activity里做处理,当收到启动请求后强制把当前任务移到前台

    ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);  
    activityManager.moveTaskToFront(getTaskId(), ActivityManager.MOVE_TASK_WITH_HOME);
    

更多Activity知识总结请详见我的Github

你可能感兴趣的:(【Android常用开发】)