URL Scheme 的作用

客户端应用可以向操作系统注册一个 URL Scheme,该 Scheme 用于从浏览器或其他应用中启动本应用。

通过指定的 URL 字段,可以让应用在被调起后直接打开某些特定页面,比如:书籍封面页,书城页面,原创页面,订单详情页、充值页,促销广告页等等。也可以执行某些指定动作,如订单支付等。也可以在应用内或者应用外,通过 html 页来直接调用显示 app 内的某个页面。

URL Scheme 的格式

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

一个普通的 URL 分为几个部分,schemehost、port、relativePathquery、fragment, 

URL语法:

URL由三部分组成:资源类型、存放资源的主机域名、资源文件名。

URL的一般语法格式为:
(带方括号[]的为可选项):
protocol :// hostname[:port] / path / [;parameters][?query]#fragment
指定使用的传输协议,下表列出 protocol 属性的有效方案名称。 最常用的是HTTP协议,它也是目前WWW中应用最广的协议。
file 资源是本地计算机上的文件。格式file:///,注意后边应是三个斜杠。
ftp 通过 FTP访问资源。格式 FTP://
gopher 通过 Gopher 协议访问该资源。
http 通过 HTTP 访问该资源。 格式 HTTP://
https 通过安全的 HTTPS 访问该资源。 格式 HTTPS://
mailto 资源为电子邮件地址,通过 SMTP 访问。 格式 mailto:
MMS 通过 支持MMS(流媒体)协议的播放该资源。(代表软件:Windows Media Player)格式 MMS://
ed2k 通过 支持ed2k(专用下载链接)协议的P2P软件访问该资源。(代表软件:电驴) 格式 ed2k://
Flashget 通过 支持Flashget:(专用下载链接)协议的P2P软件访问该资源。(代表软件:快车) 格式 Flashget://
thunder 通过 支持thunder(专用下载链接)协议的P2P软件访问该资源。(代表软件:迅雷) 格式 thunder://
news 通过 NNTP 访问该资源。
hostname(主机名)
是指存放资源的服务器的域名系统(DNS) 主机名或 IP 地址。有时,在主机名前也可以包含连接到服务器所需的用户名和密码(格式:username:password@hostname)。
port(端口号)
整数,可选,省略时使用方案的默认端口,各种传输协议都有默认的端口号,如http的默认端口为80。如果输入时省略,则使用默认端口号。有时候出于安全或其他考虑,可以在服务器上对端口进行重定义,即采用非标准端口号,此时,URL中就不能省略端口号这一项。
path(路径)
由零或多个“/”符号隔开的字符串,一般用来表示主机上的一个目录或文件地址。
parameters(参数)
这是用于指定特殊参数的可选项。
query(查询)
可选,用于给动态网页(如使用CGI、ISAPI、PHP/JSP/ASP/ASP。NET等技术制作的网页)传递参数,可有多个参数,用“&”符号隔开,每个参数的名和值用“=”符号隔开。
fragment(信息片断)
字符串,用于指定网络资源中的片断。例如一个网页中有多个名词解释,可使用fragment直接定位到某一名词解释。

下面举一个例子:

比如:http://www.sina.com/s?rsv_bp=1&rsv_spt=1&wd=NSurl&inputT=2709,这个URL中,scheme为 httphostwww.sina.comrelativePath为 /squery 为 rsv_bp=1&rsv_spt=1&wd=NSurl&inputT=2709

再列举了一个在应用中使用的 URL 例子(该 URL 会调起书籍封面页):sqreader://com.sq.controller/readbook?book_id=123456,其中 scheme 为 sqreaderhost 为 com.sq.controllerrelativePath 为 /readbookquery 为 book_id=123456

在AndroidManifest.xml中设置了data属性,data代表数据源,是中最复杂的标签,因为不同的Activity支持的数据来源和类型多种多样,所以需要通过详细的data标签信息来指明。

Data的语法如下:

<data android:host="string"
      android:mimeType="string"
      android:path="string"
      android:pathPattern="string"
      android:pathPrefix="string"
      android:port="string"
      android:scheme="string" />

Uri的格式:scheme://host:port/path or pathPrefix or pathPattern

如果scheme没有指定,那其它的属性均无效;

如果host没有指定,那么port,path,pathPrefix,pathPattern均无效;

如果在manifest里这样写:<data android:scheme="something" android:host="project.example.com" />

那么Uri uri = Uri.parse("something://project.example.com"); 才可以匹配

再如:
<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"); 才可以匹配

可以有多个data,只需匹配其中一个即可
<activity android:name=".MyActivityTwo" android:label="@string/activityTwo">
<intent-filter>
             <action android:name="android.intent.action.leo"></action>
                <category android:name="android.intent.category.DEFAULT"></category>
                <data android:scheme="x-id"/>
                <data android:scheme="something"/>
</intent-filter>
</activity>

Intent in = new Intent();
in.setAction("android.intent.action.leo");
in.addCategory(Intent.CATEGORY_DEFAULT);            in.setData(Uri.parse("something:"));//或者用这个亦可in.setData(Uri.parse("x- id:"));
startActivity(in);  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

Scheme定义Activity

当我们通过Intent启动一个Activity的时候,一般分为显式跳转和隐式跳转,隐式跳转我们可以通过setAction方法就可以实现,但有时我们需要通过设置URi的方式来进行页面的跳转,隐式启动则是不明确指定启动哪个Activity或者Service,而是通过设置Action、Data、Category,让系统来筛选出合适的目标。

例如:拨打电话:

Intent intent = new Intent(Intent.ACTION_DIAL,Uri.parse(“tel:10086”));
startActivity(intent);
  • 1
  • 2
  • 1
  • 2

系统接收到隐式启动请求后,会根据系统中各个Activity在AndroidManifest.xml文件中声明的来比较和判断是否匹配当前的Intent请求的。

1)在Androidmanifest.xml中定义scheme

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

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

                <!-- 协议部分,随便设置 -->
                <data Android:scheme="sqreader"  />
                <!-- 下面这几行也必须得设置 -->
                <category Android:name="Android.intent.category.DEFAULT" />                <!-- 设置了BROWSABLE属性后,从外部html页面就可以唤起当前配置的页面了 -->
                <category Android:name="Android.intent.category.BROWSABLE" />

                <action Android:name="Android.intent.action.VIEW" />
            </intent-filter>
        </activity>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

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

2)当然就是实现MyAppActivity

/**
 * Created by Charlies
 */
public class MyAppActivity extends Activity{
    public String tag = "MyAppActivity";
    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,则关闭MyAppActivity;
                    finish();
                }
            } else {
                finish();
            }
        } else {
            finish();
        }
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

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

sqreader://com.sq.controller/readbook
  • 1
  • 1

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

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

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

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

startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("sqreader://com.sq.controller/readbook")));
  • 1
  • 1

这里的”sqreader://com.sq.controller/readbook”就是服务器下发的跳转路径,当我们执行startActivity的时候就会调起MyAppActivity,然后我们通过在MyAppActivity解析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);
                }
                // 跳转MyAppActivity解析
                {
                    // 若设置刷新,则刷新页面
                    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;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

可以发现我们为Webview设置了WebViewClient,并重写了WebViewClient的shouldOverrideUrlLoading方法,然后我们解析锚点的URL,并根据解析的内容调起MyAppActivity的Scheme Activity,然后在MyAppActivity中解析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 {
            Log.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;
            Log.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();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

服务器下发的所有的通知都是先跳转这里的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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

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

总结: 
    Android中的Scheme是一种非常好的实现机制,通过定义自己的Scheme协议,可以非常方便跳转App中的各个页面; 
通过Scheme协议,服务器可以定制化告诉App跳转那个页面,可以通过通知栏消息定制化跳转到指定的页面,也可以通过H5页面中的链接,唤起APP中的指定页面等。一句话,Scheme是一个解决应用内页面跳转,外部调起应用内指定页面的一种非常好的解决方案。


Android Scheme协议与应用全解析的更多相关文章

  1. Android图片载入框架最全解析(一),Glide的基本使用方法

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/53759439 本文同步发表于我的微信公众号.扫一扫文章底部的二维码或在微信搜索 郭 ...

  2. 对于Android Service 生命周期进行全解析

    应用程序组件有一个生命周期——一开始Android实例化他们响应意图,直到结束实例被销毁.在这期间,他们有时候处于激活状态,有时候处于非激 活状态:对于活动,对用户有时候可见,有时候不可见.组件生命周 ...

  3. 我的Android进阶之旅------>(全解析)屏幕尺寸,分辨率,像素,PPI之间到底什么关系?

    作者:马忠信,作者授权早读课发表,转载请联系作者. 原文链接:http://www.jianshu.com/p/c3387bcc4f6e#  互联网早读课:http://zaodula.com/arc ...

  4. Android异步载入全解析之使用多线程

    异步载入之使用多线程 初次尝试 异步.异步,事实上说白了就是多任务处理.也就是多线程执行.多线程那就会有各种问题,我们一步步来看.首先.我们创建一个class--ImageLoaderWithoutC ...

  5. Android异步载入全解析之IntentService

    Android异步载入全解析之IntentService 搞什么IntentService 前面我们说了那么多,异步处理都使用钦定的AsyncTask.再不济也使用的Thread,那么这个Intent ...

  6. RTP协议全解析(H264码流和PS流)(转)

    源: RTP协议全解析(H264码流和PS流)

  7. syslog之二:syslog协议及rsyslog服务全解析

    目录: <syslog之一:Linux syslog日志系统详解> <syslog之二:syslog协议及rsyslog服务全解析> <syslog之三:建立Window ...

  8. Android Activity启动流程源码全解析(1)

    前言 Activity是Android四大组件的老大,我们对它的生命周期方法调用顺序都烂熟于心了,可是这些生命周期方法到底是怎么调用的呢?在启动它的时候会用到startActivty这个方法,但是这个 ...

  9. Android Activity启动流程源码全解析(2)

    接上之前的分析 ++Android Activity启动流程源码全解析(1)++ 1.正在运行的Activity调用startPausingLocked 一个一个分析,先来看看startPausing ...

随机推荐

  1. 【转】高精度GPS测量中框架基准的统一

    一.地面基准点的坐标基准转换 一般情况下,我们可以从IERS或IGS等机构获取最新的站坐标和速度场,这些站坐标和速度场是在某一特定基准框架和历元下的坐标值,若要提供高精度GPS网的分析使用,还需要作框 ...

  2. Eclipse 配置运行Spark

    本文主要记录Spark 在 Eclipse中报 找不到主类的问题.在新建Spark工程,然后add external jars 选择 spark-assembly-1.4.0-hadoop2.6.0. ...

  3. 统一网络控制器Func

    一.简介 二.安装 三.测试 一.简介 什么是Func? Func是由红帽子公司以Fedora平台构建的统一网络控制器,是为解决集群管理.监控问题而设计开发的系统管理基础框架.它是一个能有效简化多服务 ...

  4. 利用rsync+inotify实现数据实时同步脚本文件

    将代码放在Server端,实现其它web服务器同步.首先创建rsync.shell,rsync.shell代码如下: #!/bin/bash host1=133.96.7.100 host2=133. ...

  5. Linux下使用skipfish扫描网站漏洞步骤

    skipfish是谷歌开发的网站安全扫描工具. 下载地址:http://pan.baidu.com/s/1kTC66lL svn更新地址(一般链接不上,网速很慢): http://skipfish.g ...

  6. ABP官方文档翻译 3.4 领域服务

    领域服务 介绍 IDomainService接口和DomainService类 示例 创建接口 服务实现 使用应用服务 一些探讨 为什么只有应用服务? 如何强制使用领域服务? 介绍 领域服务(或者在D ...

  7. Kafka Consumer

    Push VS Pull An initial question we considered is whether consumers should pull data from brokers or ...

  8. HDU 2141 Can you find it? [二分]

    Can you find it? Give you three sequences of numbers A, B, C, then we give you a number X. Now you n ...

  9. Markdown 安装图解(破解汉化教程)

    http://blog.csdn.net/taokai_110/article/details/72934818 终于解决了问题

  10. Windows Server 2016-部署额外域控制器

    我们一般所提到的所谓域外控制器,是指除域内第一台域控制器之外的其他域控制器.额外域控制器很多时候也有辅助域控一称.那么在同一域内安装多台域控制器有什么优点呢: 1.提高用户登录效率.多台域控制器可以同 ...