android产品研发(十一)-->应用内跳转scheme协议

转载请标明出处:一片枫叶的专栏

上一篇文章中我们讲解了在Android App的实际开发中,尽量不在静态变量、全局变量中保存数据,这是因为App的进程可能是不安全的,在部分手机中其有可能被系统杀死,从而造成静态全局变量重新初始化。而这时候App当前页面的Activity还会被保存在内存中,从而造成App并没有被重启的假象,但是这只是显示的页面没有被杀死,而进程实际上是被重新启动了的。这时候在使用已被重新初始化的静态变量就会发生一些不可预知的错误,具体关于不在静态变量中保存数据的问题,可以参考这里的:android产品研发(十)–>不使用静态变量保存数据

而本文讲解的是一种App内页面跳转协议,这里的跳转包括应用内跳转、H5与Native跳转,服务器通知客户端如何跳转等。

在讲解应用内跳转协议之前我们先讲解一下H5与Native相互跳转的相关知识点。现在越来越多的App采用了Native + H5方式开发,其中Native与H5页面如何交互?google提供了一个公共的方式:js与native互调,即js可以调用Native方法,Native同样也可以调用js方法;

但是这种交互方式存在着不少问题:

1、Java 调用 js 里面的函数、效率并不是很高、估计要200ms左右吧、做交互性很强的事情、这种速度很难让人接受、而js去调Java的方法、速度很快、50ms左右、所以尽量用js调用Java方法

2、Java 调用 js 的函数、没有返回值、调用了就控制不到了

3、Js 调用 Java 的方法、返回值如果是字符串、你会发现这个字符串是 native 的、转成 locale 的才能正常使用、使用 toLocaleString() 函数就可以了、不过这个函数的速度并不快、转化的字符串如果很多、将会很耗费时间

4、网页中尽量不要使用jQuery、执行起来需要5-6秒、最好使用原生的js写业务脚本、以提升加载速度、改善用户体验

5、android4.2以下的系统存在着webview的js对象注入漏洞…(不清楚的可以google)

基于这种种的原因,我们并未采用这种方式用于Native与webview交互,而是采用scheme + cookie的方式;

这里的scheme是一种页面内跳转协议,主要用于支持一下几种场景:

  • 服务器下发跳转路径,客户端根据服务器下发跳转路径跳转相应的页面;
  • H5页面点击锚点,根据锚点具体跳转路径App端跳转具体的页面;
  • App端收到服务器端下发的PUSH通知栏消息,根据消息的点击跳转路径跳转相关页面

下面我将简单介绍一下scheme的基本概念以及以上三种场景下scheme的具体应用。

URL scheme 概述

URL scheme 的作用

客户端应用可以向操作系统注册一个 URL scheme,该 scheme 用于从浏览器或其他应用中启动本应用。通过指定的 URL 字段,可以让应用在被调起后直接打开某些特定页面,比如车辆详情页、订单详情页、消息通知页、促销广告页等等。也可以执行某些指定动作,如订单支付等。也可以在应用内通过 html 页来直接调用显示 app 内的某个页面。

URL scheme 的格式

客户端自定义的 URL 作为从一个应用调用另一个的基础,遵循 RFC 1808 (Relative Uniform Resource Locators) 标准。这跟我们常见的网页内容 URL 格式一样。

一个普通的 URL 分为几个部分,schemehostrelativePathquery

比如:http://www.baidu.com/s?rsv_bp=1&rsv_spt=1&wd=NSurl&inputT=2709,这个URL中,schemehttphostwww.baidu.comrelativePath/squeryrsv_bp=1&rsv_spt=1&wd=NSurl&inputT=2709

一个应用中使用的 URL 例子(该 URL 会调起车辆详情页):uumobile://mobile/carDetail?car_id=123456,其中 schemeuumobilehostmobilerelativePath/carDetailquerycar_id=123456

Scheme定义Activity

1)在androidmanifest.xml中定义scheme

<!-- scheme协议 -->
        <activity
            android:name=".UI.translate.NativeAppActivity"
            android:label="@string/app_name">

            <!-- 要想在别的App上能成功调起App,必须添加intent过滤器 -->
            <intent-filter>

                <!-- 协议部分,随便设置 -->
                <data android:scheme="uumobile" />
                <!-- 下面这几行也必须得设置 -->
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <action android:name="android.intent.action.VIEW" />
            </intent-filter>
        </activity>

这样我们便定义了能够接受scheme请求的activity实例,当网页或者是android代码发送这种规则scheme的请求的时候就能够吊起NativeAppActivity了。

2)当然就是实现NativeAppActivity

/**
 * Created by admin
 */
public class NativeAppActivity extends Activity{
    public String tag = "NativeAppActivity";
    public Activity mContext = null;

    public void onCreate(Bundle b)
    {
        super.onCreate(b);
        mContext = this;
        Uri uri = getIntent().getData();
        if (uri != null)
        {
            List<String> pathSegments = uri.getPathSegments();
            String uriQuery = uri.getQuery();
            Intent intent;
            if (pathSegments != null && pathSegments.size() > 0) {
                // 解析SCHEME
                if (someif) {
                  dosomething();
                }
                else {
                    // 若解析不到SCHEME,则关闭NativeAppActivity;
                    finish();
                }
            } else {
                finish();
            }
        } else {
            finish();
        }
    }

}

NativeAppActivity这个类中主要用于实现对scheme的解析,然后做出相应的动作,比如请求scheme跳转登录页面,我们可以这样定义

uumobile://appname/gotoLogin

然后我们解析出scheme如果是这样的结构就跳转登录页面。。。

这里简单说一下,我们可以通过Intent对象获取调用的scheme的host等信息

this.getIntent().getScheme();//获得Scheme名称
this.getIntent().getDataString();//获得Uri全部路径 

3)通过服务器下发跳转路径跳转相应页面

startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("uumobile://yongche/123123123")));

这里的”uumobile://yongche/123123123”就是服务器下发的跳转路径,当我们执行startActivity的时候就会调起NativeAppActivity,然后我们通过在NativeAppActivity解析scheme的内容,跳转相应的页面。

4)通过在H5页面的锚点跳转相应的页面

@Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        //解析scheme
        if (url.indexOf(H5Constant.SCHEME) != -1) {
            try {
                Uri uri = Uri.parse(url);
                String[] urlSplit = url.split("\\?");
                Map<String, String> queryMap = new HashMap<String, String>();
                String h5Url = null;
                if (urlSplit.length == 2) {
                    queryMap = H5Constant.parseUriQuery(urlSplit[1]);
                    h5Url = queryMap.get(H5Constant.MURL);
                }
                // 跳转NativeAppActivity解析
                {
                    // 若设置刷新,则刷新页面
                    if (queryMap.containsKey(H5Constant.RELOADPRE) && "1".equals(queryMap.get(H5Constant.RELOADPRE))) {
                        h5Fragment.isNeedFlushPreH5 = true;
                    }
                    Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                    h5Activity.startActivityForResult(intent, H5Constant.h5RequestCode);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return true;
        }
        // 打电话
        else if (url.indexOf("tel://") != -1) {
            final String number = url.substring("tel://".length());
            Config.callPhoneByNumber(h5Activity, number);
            return true;
        } else if (url.indexOf("tel:") != -1) {
            final String number = url.substring("tel:".length());
            Config.callPhoneByNumber(h5Activity, number);
            return true;
        }
        // 其他跳转方式
        else {
            view.loadUrl(url);
            //如果不需要其他对点击链接事件的处理返回true,否则返回false
            return false;
        }
    }

可以发现我们为Webview设置了WebViewClient,并重写了WebViewClient的shouldOverrideUrlLoading方法,然后我们解析锚点的url,并根据解析的内容调起NativeAppActivity的scheme Activity,然后在NativeAppActivity中解析scheme的内容并跳转相应的页面。

5)根据服务器下发通知栏消息,App跳转相应的页面

public class NotificationActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        L.i("接收到通知点击事件...");
        Intent realIntent = getIntent().getParcelableExtra(NotifeConstant.REAL_INTENT);
        // 解析scheme并跳转
        gotoRealScheme(this, realIntent);
    }

    /**
     * notification中跳转SCHEME,根据有效时间判断跳转URL地址
     *  跳转之后更具网络请求判断用户当前状态
     */
    private void gotoRealScheme(Context context, Intent realIntent) {
        if (realIntent == null || context == null) {
            finish();
            return;
        }
        try {
            L.i("开始解析通知中的参数...");
            long startShowTime = realIntent.getLongExtra(NotifeConstant.START_SHOW_TIME, 0);
            // 有效期时间,单位:s(秒)
            long validTime = realIntent.getLongExtra(NotifeConstant.VALID_TIME, 0);
            long currentTime = System.currentTimeMillis();
            String validActionUrl = realIntent.getStringExtra(NotifeConstant.VALID_ACTION_URL);
            String invalidActionUrl = realIntent.getStringExtra(NotifeConstant.INVALID_ACTION_URL);
            Intent schemeIntent;
            L.i("开始根据URL构建Intent对象...");
            if ((currentTime - startShowTime) / 1000L <= validTime) {
                schemeIntent = H5Constant.buildSchemeFromUrl(validActionUrl);
            } else {
                schemeIntent = H5Constant.buildSchemeFromUrl(invalidActionUrl);
            }
            if (schemeIntent != null) {
                // 设置当前页面为通知栏打开
                Config.isNotificationOpen = true;
                context.startActivity(schemeIntent);
                finish();
                //对通知栏点击事件统计
                MobclickAgent.onEvent(context, UMCountConstant.PUSH_NOTIFICATION_CLICK);
            } else {
                finish();
            }
        } catch (Exception e) {
            // 异常情况下退出当前Activity
            finish();
        }
    }
}

服务器下发的所有的通知都是先跳转这里的NotificationActivity,然后在这里执行跳转其他Activity的逻辑,而这里的H5Constant的buildSchemeFromUrl方法就是构造跳转页面Intent对象的,我们可以看一buildSchemeFromUrl方法的具体实现:

/**
     * 从scheme的url中构建出Intent,用于界面跳转
     *
     * @param url
     * @return
     */
    public static Intent buildSchemeFromUrl(String url) {
        if (url != null && url.indexOf(H5Constant.SCHEME) != -1) {
            Uri uri = Uri.parse(url);
            String[] urlSplit = url.split("\\?");
            Map<String, String> queryMap = new HashMap<String, String>();
            String h5Url = null;
            if (urlSplit.length == 2) {
                queryMap = H5Constant.parseUriQuery(urlSplit[1]);
                h5Url = queryMap.get(H5Constant.MURL);
            }
            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
            if (!TextUtils.isEmpty(h5Url)) {
                intent.putExtra(H5Constant.MURL, h5Url);
            }
            return intent;
        }
        return null;
    }

这样我们就搞构造除了跳转NativeAppActivity的Intent对象,并将scheme字符串传递给了NativeAppActivity,这样在NativeAppActivity中就可以解析scheme字符串并执行相应的跳转逻辑了。

总结:

android中的scheme是一种非常好的实现机制,通过定义自己的scheme协议,可以非常方便跳转app中的各个页面;

通过scheme协议,服务器可以定制化告诉App跳转那个页面,可以通过通知栏消息定制化跳转页面,可以通过H5页面跳转页面等。

另外对产品研发技术,技巧,实践方面感兴趣的同学可以参考我的:

android产品研发(一)–>实用开发规范

android产品研发(二)–>启动页优化

android产品研发(三)–>基类Activity

android产品研发(四)–>减小Apk大小

android产品研发(五)–>多渠道打包

android产品研发(六)–>Apk混淆

android产品研发(七)–>Apk热修复

android产品研发(八)–>App数据统计

android产品研发(九)–>App网络传输协议

android产品研发(十)–>不使用静态变量保存数据



本文以同步至github中:https://github.com/yipianfengye/androidProject,欢迎star和follow


时间: 2024-09-30 09:46:27

android产品研发(十一)-->应用内跳转scheme协议的相关文章

android产品研发(九)--&gt;App网络传输协议

转载请标明出处:一片枫叶的专栏 上一篇文章中我们讲解了App的数据统计,其主要分为两种:使用第三方服务统计和自身实现数据统计.一般而言我们使用第三方统计服务已经可以很好的满足我们的也无需求了,只是部分数据敏感性App,可能自身实现数据统计服务是一个更好的选择. 而本文中将要介绍的是App端的网络传输协议.那么这里首先需要明确一点的是什么是网络传输协议呢?这里首先套用一段百度百科的定义: 网络传输协议或简称为传送协议(Communications Protocol[1] ),是指计算机通信的共同语

android产品研发(二十一)--&gt;UI优化

转载请标明出处:一片枫叶的专栏 上一篇文章中我们讲解了android产品研发过程中的代码Review.通过代码Review能够提高产品质量,增强团队成员之间的沟通,提高开发效率,所以良好的产品开发迭代过程中,代码Review是一个必不可少的步骤.那么如何进行代码Review呢?我们主要讲解了团队成员之间的代码Review,代码lint检查,开发规范等方面的知识点,更多关于代码Review相关的知识可参考我的:android产品研发(二十)–>代码Review 本文我们将讲解一下android U

android产品研发(十七)--&gt;Hybird开发

转载请标明出处:一片枫叶的专栏 上一篇文章中我们介绍了android开发中经常会涉及到但又常常被忽视掉的开发者模式.主要讲解了包括如何打开手机的开发者模式,开发者模式中各个菜单的意义和作用,如何清除手机App数据,以及清除手机App数据具体清除那些数据等知识点,具体关于android中开发者模式的知识,可参考我的:android产品研发(十六)–>开发者选项 本文将介绍android中hybird开发相关的知识点.hybird开发实际上是混合开发的意思,这里的混合是H5开发与Native开发混合

android产品研发(十六)--&gt;开发者选项

转载请标明出处:一片枫叶的专栏 上一篇文章中我们讲解了android中内存对象的序列化方式.由于android开发涉及到不同Activity的数据传递,对于基本数据类型数据的传递是没有问题的,但是一旦涉及到复杂数据类型,就需要将数据序列化以便传输,在文章中我们主要讲解了两种数据序列化的方式:实现Serializable接口和实现Parcelable接口,同时也比较了它们各自的优缺点和实现方式.具体关于内存对象序列化方面的知识可参考:android产品研发(十五)–>内存对象序列化 本文主要介绍A

android产品研发(十四)--&gt;App升级与更新

转载请标明出处:一片枫叶的专栏 上一篇文章中我们讲解了android app中的轮训操作,讲解的内容主要包括:我们在App中使用轮训操作的情景,作用以及实现方式等.一般而言我们使用轮训操作都是通过定时任务的形式请求服务器并更新用户界面,轮训操作都有一定的使用生命周期,即在一定的页面中启动轮操作,然后在特定的情况下关闭轮训操作,这点需要我们尤为注意,我们还介绍了使用Timer和Handler实现轮训操作的实例,更多关于App中轮训操作的信息,可参考我的:android产品研发(十三)–>App轮训

android产品研发(十五)--&gt;内存对象序列化

转载请标明出处:一片枫叶的专栏 上一篇文章中我们讲解了android app中的升级更新操作,app的升级更新操作算是App的标配了,升级操作就是获取App的升级信息,更新操作是下载,安装,更新app,其中我们既可以使用app store获取应用的升级信息,也可以在应用内通过请求本地服务器获取应用的升级信息,并通过与本地app的版本号对比判断应用是否需要升级. 升级信息是app更新的基础,只有我们的app的升级信息指明需要更新,我们才可以开始后续的更新操作–也就是下载安装更新app.这里强调一点

android产品研发(十二)--&gt;App长连接实现

转载请标明出处:一片枫叶的专栏 上一篇文章中我们讲解了android应用内页面跳转协议-scheme协议,通过该协议我们可以跳转至指定的Activity,并在该Activity中解析scheme用于跳转到指定的页面,我们可以利用scheme协议实现应用内页面跳转.H5页面与Native页面相互跳转.通知栏消息跳转相应页面等,具体可参考:android产品研发(十一)–>使用scheme实现页面跳转. 而本文中我们将讲解一下App的长连接实现.一般而言长连接已经是App的标配了,推送功能的实现基础

android产品研发(十九)--&gt;android studio中的单元测试

转载请标明出处:一片枫叶的专栏 上一篇文章中我们讲解了webview中问题集锦,讲解了webview的性能优化.webview种入Cookie信息.activity退出的时候清除webview信息报错.如何通过java代码和js代码相互交互.webview如何下载文件以及腾讯的X5浏览服务等知识,这些都是我在使用webview中遇到的问题,难点,实践等,更多关于这些问题的说明,可以参考我的:android产品研发(十八)–>webview趟过的坑 本文我们将讲解如何在android studio

android产品研发(十三)--&gt;App轮训操作

转载请标明出处:一片枫叶的专栏 上一篇文章中我们讲解了android app实现长连接的几种方式,各自的优缺点以及具体的实现,一般而言使用第三方的推送服务已经可以满足了基本的业务需求,当然了若是对技术有追求的可以通过NIO或者是MINA实现自身的长连接服务,但是自己实现的长连接服务一来比较复杂耗时比较多,而且可能过程中有许多坑要填,一般而言推荐使用第三方的推送服务,稳定简单,具体管理长连接部分的模块可参考:android产品研发(十二)–>App长连接实现. 而本文将讲解app端的轮训请求服务,