Quantcast
Channel: CSDN博客推荐文章
Viewing all 35570 articles
Browse latest View live

洛谷3939 数颜色

$
0
0

标签:二分

http://www.yjjr.org/nd.jsp?id=15#_np=2_327

题目背景

大样例下发链接:http://pan.baidu.com/s/1c0LbQ2 密码:jigg

题目描述

小 C 的兔子不是雪白的,而是五彩缤纷的。每只兔子都有一种颜色,不同的兔子可能有相同的颜色。小 C 把她标号从 1 到 nnn 的 nnn 只兔子排成长长的一排,来给他们喂胡萝卜吃。 排列完成后,第iii 只兔子的颜色是 aia_iai​。

俗话说得好,“萝卜青菜,各有所爱”。小 C 发现,不同颜色的兔子可能有对胡萝卜的 不同偏好。比如,银色的兔子最喜欢吃金色的胡萝卜,金色的兔子更喜欢吃胡萝卜叶子,而绿色的兔子却喜欢吃酸一点的胡萝卜……为了满足兔子们的要求,小 C 十分苦恼。所以,为了使得胡萝卜喂得更加准确,小 C 想知道在区间 [lj,rj][l_j,r_j][lj​,rj​] 里有多少只颜色为 cjc_jcj​ 的兔子。

不过,因为小 C 的兔子们都十分地活跃,它们不是很愿意待在一个固定的位置;与此同时,小 C 也在根据她知道的信息来给兔子们调整位置。所以,有时编号为 xjx_jxj​ 和 xj+1x_j+1xj​+1 的两 只兔子会交换位置。 小C 被这一系列麻烦事给难住了。你能帮帮她吗?

输入输出格式

输入格式:

从标准输入中读入数据。 输入第 1 行两个正整数 nnn,mmm。

输入第 2 行 nnn 个正整数,第iii 个数表示第 iii 只兔子的颜色 aia_iai​。

输入接下来 mmm 行,每行为以下两种中的一种:

  • “1 lj rj cj1\ l_j\ r_j\ c_j1 lj​ rj​ cj​” :询问在区间 [lj,rj][l_j,r_j][lj​,rj​] 里有多少只颜色为 cjc_jcj​ 的兔子;
  • “2 xj2\ x_j2 xj​”: xjx_jxj​ 和 xj+1x_j+1xj​+1 两只兔子交换了位置。

输出格式:

输出到标准输出中。

对于每个 1 操作,输出一行一个正整数,表示你对于这个询问的答案。

输入输出样例

输入样例#1: 复制

6 5

1 2 3 2 3 3 

1 1 3 2

1 4 6 3 

2 3

1 1 3 2 

1 4 6 3

输出样例#1: 复制

1

2

2

3

说明

【样例 1 说明】

前两个 1 操作和后两个 1 操作对应相同;在第三次的 2 操作后,3 号兔子和 4 号兔子

交换了位置,序列变为 1 2 2 3 3 3。

【数据范围与约定】

子任务会给出部分测试数据的特点。如果你在解决题目中遇到了困难,可以尝试只解 决一部分测试数据。 对于所有测试点,有 1≤lj<rj≤n,1≤xj<n1 \le l_j < r_j \le n,1 \le x_j < n1≤lj​<rj​≤n,1≤xj​<n。每个测试点的数据规模及特点如下表:


特殊性质 1:保证对于所有操作 1,有∣rj−lj∣≤20|r_j - l_j| \le 20∣rj​−lj​∣≤20 或 ∣rj−lj∣≤n−20|r_j - l_j| \le n - 20∣rj​−lj​∣≤n−20。

特殊性质 2:保证不会有两只相同颜色的兔子。

 

这题适合那些学高级数据结构学傻的那些人,一堆人用主席树,可持久化线段树……,很适合给那些人练练题

还有一点不明白为什么我的暴力可以写到55分qwq

然而这是一场NOIP模拟赛,正解是二分查找

可以将每个颜色和其位置用结构体封装,然后按照这两个域大小排序

接着询问的时候二分查找,修改的时候只需要将两个兔子位置的域调换即可

Code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define dep(i,a,b) for(int i=a;i>=b;i--)
#define ll long long
#define mem(x,num) memset(x,num,sizeof x)
#ifdef WIN32
#define LL "%I64d\n"
#else
#define LL "%lld\n"
#endif
using namespace std;
inline ll read()
{
    ll f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
const int maxn=4e5+6; 
int a[maxn],n,m;
vector<int>b[maxn];
int main()
{
	n=read(),m=read();
    rep(i,1,n){
    	a[i]=read();
    	b[a[i]].push_back(i);
    }
    rep(i,1,n)sort(b[i].begin(),b[i].end());
    rep(i,1,m){
    	int opt=read();
    	if(opt==1){
    		int l=read(),r=read(),c=read();
    		printf("%d\n",(int)(upper_bound(b[c].begin(),b[c].end(),r)-lower_bound(b[c].begin(),b[c].end(),l)));
    	}
    	else{
    		int x=read();
    		if(a[x]!=a[x+1]){
    			(*lower_bound(b[a[x]].begin(),b[a[x]].end(),x))++;
    			(*lower_bound(b[a[x+1]].begin(),b[a[x+1]].end(),x+1))--;
    			swap(a[x],a[x+1]);
    		}
    	}
    }
    return 0;
}
    



作者:qwerty1125 发表于2017/11/9 15:42:59 原文链接
阅读:0 评论:0 查看评论

Unity 多物体混合动画、值变动画控制器

$
0
0

前言

因为工作中有用到,所以我抽出空闲把之前的LinkageAnimation优化了一下,如果有类似的需求(比如场景中有大量的物体,都按照同一频率在运动),那么这个工具可能适合你,当然如果你的环境是2017,TimeLine会是一个更好的解决方案。
不过,LinkageAnimation应该被称作值变动画才更合适,因为他支持针对所有组件(包括自定义组件)的属性做值变动画,属性满足以下要求:
1、该属性类型必须是被LinkageAnimation所识别的类型,目前有:Bool,Color,Float,Int,Quaternion,String,Vector2,Vector3,Vector4,Sprite,可以自行添加任意类型。
2、该属性必须是可读可写属性(不包括字段)。
3、该属性必须是实例属性(Instance)。
只要是满足以上要求的属性,将他所属脚本挂在场景物体上,就可以监听该物体,通过关键帧动画操控其值。

示例

1、4个Cube的联动动画

动画帧面板:(控制Transform组件的localRotation属性)
这里写图片描述

效果图:
这里写图片描述

2、UGUI Text文本动画

动画帧面板:(控制Text组件的text属性、fontSize属性)
这里写图片描述
效果图:
这里写图片描述

3、UGUI Image图片动画

动画帧面板:(控制Image组件的sprite属性)
这里写图片描述
效果图:
这里写图片描述

4、物体消隐动画

动画帧面板:(控制MeshRenderer组件的enabled属性)
这里写图片描述
效果图:
这里写图片描述

使用与解析

1、挂载LinkageAnimation脚本至场景中

这里写图片描述
一个LinkageAnimation实例对应一个动画组,点击Edit Animation按钮可以打开动画编辑界面,编辑整个动画组。

2、控制多个监听物体

这里写图片描述
1、添加新的监听物体:
① 动画编辑窗口右上角 -> Add Target按钮;
② 鼠标右键 -> Add Target选项;
2、删除监听物体:
① 物体的可移动窗口右上角 -> ‘x’按钮;
3、查找监听物体:
① 按住鼠标中间拖动视野;
② 动画编辑窗口右上角 -> Find Target按钮(查找由于拖动等原因消失在视野内的监听物体);

3、监听物体的属性

这里写图片描述
1、添加新的属性:
① 物体的可移动窗口下方 -> Add Property按钮(可以添加任意组件的任意已知、可读、可写属性);
2、删除属性:
① 属性左边的‘x’按钮;

源码解析

使用反射提取目标组件的对应属性:

if (GUI.Button(new Rect(5, h, _width - 10, 16), "Add Property"))
                {
                    GenericMenu gm = new GenericMenu();
                    //获取所有组件
                    Component[] cps = lat.Target.GetComponents<Component>();
                    for (int m = 0; m < cps.Length; m++)
                    {
                        //获取组件类型
                        Type type = cps[m].GetType();
                        //获取组件的所有属性
                        PropertyInfo[] pis = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
                        for (int n = 0; n < pis.Length; n++)
                        {
                            PropertyInfo pi = pis[n];
                            string propertyType = pi.PropertyType.Name;
                            //替换属性名称为标准名称
                            propertyType = LinkageAnimationTool.ReplaceType(propertyType);
                            //检测属性类型是否为合法类型
                            bool allow = LinkageAnimationTool.IsAllowType(propertyType);

                            if (allow)
                            {
                                //属性为可读可写的属性
                                if (pi.CanRead && pi.CanWrite)
                                {
                                    gm.AddItem(new GUIContent(type.Name + "/" + "[" + propertyType + "] " + pi.Name), false, delegate ()
                                    {
                                        //添加属性成功
                                        LAProperty lap = new LAProperty(type.Name, propertyType, pi.Name);
                                        AddProperty(lat, lap);
                                    });
                                }
                            }
                        }
                    }
                    gm.ShowAsContext();
                }
4、使用关键帧制作动画

这里写图片描述
1、添加新的关键帧:
① 动画编辑窗口右上角 -> Add Frame按钮;
② 鼠标右键 -> Add Frame选项;
2、删除关键帧:
① 选中某一关键帧 -> Delete Frame按钮;
3、复制关键帧:
① 选中某一关键帧 -> Clone Frame按钮;
4、记录关键帧的值:
① 选中某一关键帧 -> Get Value In Scene按钮(将当前所有监听物体的被监听属性值记录到当前选中的关键帧);
5、提取关键帧的值:
① 选中某一关键帧 -> Set Value To Scene按钮(将当前选中关键帧的值赋予到场景中所有监听物体的被监听属性中);

源码解析

每一个关键帧中都有属性值仓库,可以通过索引提取属性值或是存储属性值,核心代码也是使用反射:

/// <summary>
    /// 获取目标属性值并记录到当前关键帧
    /// </summary>
    private void GetPropertyValue(int index)
    {
        for (int i = 0; i < _LA.Targets.Count; i++)
        {
            LinkageAnimationTarget lat = _LA.Targets[i];
            if (lat.Target)
            {
                LAFrame laf = lat.Frames[index];
                for (int j = 0; j < lat.Propertys.Count; j++)
                {
                    //通过名称获取组件
                    Component cp = lat.Target.GetComponent(lat.Propertys[j].ComponentName);
                    if (cp != null)
                    {
                        //通过名称获取属性
                        PropertyInfo pi = cp.GetType().GetProperty(lat.Propertys[j].PropertyName);
                        if (pi != null)
                        {
                            //获取属性值
                            object value = pi.GetValue(cp, null);
                            //重新记录到关键帧仓库
                            laf.SetFrameValue(j, value);
                        }
                        else
                        {
                            Debug.LogWarning("目标物体 " + lat.Target.name + " 的组件 " + lat.Propertys[j].ComponentName + " 不存在属性 " + lat.Propertys[j].PropertyName + "!");
                        }
                    }
                    else
                    {
                        Debug.LogWarning("目标物体 " + lat.Target.name + " 不存在组件 " + lat.Propertys[j].ComponentName + "!");
                    }
                }
            }
        }
    }
/// <summary>
    /// 设置当前关键帧数据至目标属性值
    /// </summary>
    private void SetPropertyValue(int index)
    {
        for (int i = 0; i < _LA.Targets.Count; i++)
        {
            LinkageAnimationTarget lat = _LA.Targets[i];
            if (lat.Target)
            {
                LAFrame laf = lat.Frames[index];
                for (int j = 0; j < lat.Propertys.Count; j++)
                {
                    //通过名称获取组件
                    Component cp = lat.Target.GetComponent(lat.Propertys[j].ComponentName);
                    if (cp != null)
                    {
                        //通过名称获取属性
                        PropertyInfo pi = cp.GetType().GetProperty(lat.Propertys[j].PropertyName);
                        if (pi != null)
                        {
                            //为属性设置值
                            pi.SetValue(cp, laf.GetFrameValue(j), null);
                        }
                        else
                        {
                            Debug.LogWarning("目标物体 " + lat.Target.name + " 的组件 " + lat.Propertys[j].ComponentName + " 不存在属性 " + lat.Propertys[j].PropertyName + "!");
                        }
                    }
                    else
                    {
                        Debug.LogWarning("目标物体 " + lat.Target.name + " 不存在组件 " + lat.Propertys[j].ComponentName + "!");
                    }
                }
            }
        }
    }
5、控制动画

这里写图片描述
1、播放动画:

        LinkageAnimation la;
        la.Playing = true;

2、暂停动画:

        LinkageAnimation la;
        la.Playing = false;

3、停止动画:

        LinkageAnimation la;
        la.Stop();

4、重新播放动画:

        LinkageAnimation la;
        la.RePlay();

5、添加帧回调:
① 属性面板 -> Add CallBack按钮(例:当动画执行到第一帧时会呼叫Translate函数);
6、删除帧回调:
① 属性面板 -> CallBack List -> ‘x’按钮;

源码解析

针对被监听目标的组件和属性,我这里选择只将组件名称和属性名字做序列化,在运行时才会动态去获取组件和属性,如果获取失败,则这个动画无效,这样做的好处是降低了数据结构的耦合性、序列化的复杂度:

    /// <summary>
    /// 初始化运行时控件
    /// </summary>
    private void InitComponent()
    {
        for (int i = 0; i < Targets.Count; i++)
        {
            LinkageAnimationTarget lat = Targets[i];

            if (lat.Target)
            {
                if (lat.PropertysRunTime == null)
                {
                    lat.PropertysRunTime = new List<LAPropertyRunTime>();
                }

                for (int j = 0; j < lat.Propertys.Count; j++)
                {
                    LAProperty lap = lat.Propertys[j];
                    //获取组件
                    Component cp = lat.Target.GetComponent(lap.ComponentName);
                    //获取属性
                    PropertyInfo pi = cp ? cp.GetType().GetProperty(lap.PropertyName) : null;
                    //该属性动画是否有效
                    bool valid = (cp != null && pi != null);
                    LAPropertyRunTime laprt = new LAPropertyRunTime(valid, cp, pi);
                    lat.PropertysRunTime.Add(laprt);
                }
            }
        }
    }

播放动画时,每种类型的属性都会采用线性插值算法进行播放(当然有些类型无法做到线性插值,比如bool,所以这取决于具体的实现代码):

    /// <summary>
    /// 更新动画帧
    /// </summary>
    private void UpdateFrame(LinkageAnimationTarget lat, int currentIndex, int nextIndex)
    {
        if (lat.Target)
        {
            LAFrame currentLAF = lat.Frames[currentIndex];
            LAFrame nextLAF = lat.Frames[nextIndex];

            for (int i = 0; i < lat.PropertysRunTime.Count; i++)
            {
                //当前属性名
                LAProperty lap = lat.Propertys[i];
                //当前属性运行时实例
                LAPropertyRunTime laprt = lat.PropertysRunTime[i];

                //属性动画有效
                if (laprt.IsValid)
                {
                    //根据播放位置进行插值
                    object value = LinkageAnimationTool.Lerp(currentLAF.GetFrameValue(i), nextLAF.GetFrameValue(i), lap.PropertyType, _playLocation);
                    //重新设置属性值
                    laprt.PropertyValue.SetValue(laprt.PropertyComponent, value, null);
                }
            }
        }
    }

关于插值方法Lerp的实现,其实很简单,很多类型可以直接调用官方的插值方法,如果要添加自定义的类型,这里必须要实现他的插值算法:

    /// <summary>
    /// 根据类型在两个属性间插值
    /// </summary>
    public static object Lerp(object value1, object value2, string type, float location)
    {
        object value;
        switch (type)
        {
            case "Bool":
                value = location < 0.5f ? (bool)value1 : (bool)value2;
                break;
            case "Color":
                value = Color.Lerp((Color)value1, (Color)value2, location);
                break;
            case "Float":
                float f1 = (float)value1;
                float f2 = (float)value2;
                value = f1 + (f2 - f1) * location;
                break;
            case "Int":
                int i1 = (int)value1;
                int i2 = (int)value2;
                value = (int)(i1 + (i2 - i1) * location);
                break;
            case "Quaternion":
                value = Quaternion.Lerp((Quaternion)value1, (Quaternion)value2, location);
                break;
            case "String":
                string s1 = (string)value1;
                string s2 = (string)value2;
                int length = (int)(s1.Length + (s2.Length - s1.Length) * location);
                value = s1.Length >= s2.Length ? s1.Substring(0, length) : s2.Substring(0, length);
                break;
            case "Vector2":
                value = Vector2.Lerp((Vector2)value1, (Vector2)value2, location);
                break;
            case "Vector3":
                value = Vector3.Lerp((Vector3)value1, (Vector3)value2, location);
                break;
            case "Vector4":
                value = Vector4.Lerp((Vector4)value1, (Vector4)value2, location);
                break;
            case "Sprite":
                value = location < 0.5f ? (Sprite)value1 : (Sprite)value2;
                break;
            default:
                value = null;
                break;
        }
        return value;
    }

源码链接

github源码链接:https://github.com/SaiTingHu/LinkageAnimation

一起学习和进步

作者:qq992817263 发表于2017/11/9 15:51:34 原文链接
阅读:2 评论:0 查看评论

Java多线程下载框架02:观察者模式通知下载内容状态更新

$
0
0

场景描述

在Java多线程下载框架中,我们需要知道下载状态比如暂停下载,恢复下载,取消下载等状态的通知,而且不仅仅是更新当前页面,在任意页面都能接收到状态变化的更新,所以这里要用到观察者模式。
观察者模式

关于设计模式的详细介绍,我这里有几本电子书籍推荐,公号后台回复”设计模式”,即可获取下载链接。

####那么什么是观察者模式(Observer)?
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,让他们能够自动更新自己。

举一个例子来说明,牛奶送奶站就是主题,订奶客户为监听者,客户从送奶站订阅牛奶后,会每天收到牛奶。如果客户不想订阅了,可以取消,以后就不会收到牛奶。

为什么要使用观察者模式?为什么不用广播,EventBus,RxBus呢?

广播的劣势

广播是相对消耗时间、空间最多的一种方式,但是大家都知道,广播是四大组件之一,许多系统级的事件都是通过广播来通知的,比如说网络的变化、电量的变化,短信发送和接收的状态,所以,如果与android系统进行相关的通知,还是要选择本地广播;在BroadcastReceiver的 onReceive方法中,可以获得Context 、intent参数,这两个参数可以调用许多的sdk中的方法。

应用发送某个广播时,系统会将广播中的intent与系统中所有注册的BroadcastReceiver进行匹配,如果能匹配成功则调用相关的onReceive函数进行处理。这里存在2个问题:
a、性能问题。每个广播都会与所有BroadcastReceiver进行匹配。
b、安全问题。广播发出去的数据可能被其他应用监听。

因此广播相对于其他的方式而言,广播是重量级的,消耗资源较多的方式。他的优势体现在与sdk连接紧密,如果需要同 android 交互的时候,广播的便捷性会抵消掉它过多的资源消耗,但是如果不同android交互,或者说,只做很少的交互,使用广播是一种浪费。

为什么不使用EventBus,RxBus?

这里不对二者的优缺点进行分析,各有各的好处,看实际需要。因为我们是封装自己的多线程下载框架,所以不能依赖第三方的一些库,因为你不知道用户会使用RxJava还是EventBus。比如你这里用到了RxJava的库,而别人使用你的SDK的之前就集成了EventBus,那不是又要集成RxJava?或者说你这里使用的是Rx1.0,而用户使用的是Rx2.0,所以为了避免不必要的麻烦,我们尽量不被依赖外部资源。

为什么使用观察者模式?
  • 松耦合,观察者增加或删除无需修改主题的代码,只需调用主题对应的增加或者删除的方法即可。
  • 主题只负责通知观察者,但无需了解观察者如何处理通知。举个例子,送奶站只负责送递牛奶,不关心客户是喝掉还是洗脸。
  • 观察者只需等待主题通知,无需观察主题相关的细节。还是那个例子,客户只需关心送奶站送到牛奶,不关心牛奶由哪个快递人员,使用何种交通工具送达。
具体实践

一、创建一个Observable

public class DataChanger extends Observable{

    /**
     * 对外提供一个单列引用,用于注册和取消注册监听
     */
    private static DataChanger mDataChanger;

    public static synchronized DataChanger getInstance(){
        if (null == mDataChanger){
            mDataChanger = new DataChanger();
        }
        return mDataChanger;
    }

    public void notifyDataChange(DownloadEnty mDownloadEnty){
        //Marks this <tt>Observable</tt> object as having been changed
        setChanged();
        //通知观察者 改变的内容 也可不传递具体内容 notifyObservers()
        notifyObservers(mDownloadEnty);
    }
}

主要用于提供注册和删除观察者对象以及通知更新的方法,此处直接继承的是Java提供的Observable,其内部已经实现了

 * addObserver
 * deleteObserver
 * notifyObservers()
 * notifyObservers(Object arg)
 * deleteObservers()
 * setChanged()
 * clearChanged()
 * hasChanged()
 * countObservers()

当内容变化的时候,使用setChanged()和notifyObservers(mDownloadEnty)通知观察者。

二、setChanged和notifyObservers为何物

上述代码中存在这样一处代码setChanged();,如果在通知之前没有调用这个方法,观察者是收不到通知的,这是为什么呢?

这里我们看一下setChanged的源码

 /**
     * Marks this <tt>Observable</tt> object as having been changed; the
     * <tt>hasChanged</tt> method will now return <tt>true</tt>.
     */
    protected synchronized void setChanged() {
        changed = true;
    }

此处把boolen变量changed改为了true

再看notifyObservers源码

 /**
     * If this object has changed, as indicated by the
     * <code>hasChanged</code> method, then notify all of its observers
     * and then call the <code>clearChanged</code> method to indicate
     * that this object has no longer changed.
     * <p>
     * Each observer has its <code>update</code> method called with two
     * arguments: this observable object and the <code>arg</code> argument.
     *
     * @param   arg   any object.
     * @see     java.util.Observable#clearChanged()
     * @see     java.util.Observable#hasChanged()
     * @see     java.util.Observer#update(java.util.Observable, java.lang.Object)
     */
    public void notifyObservers(Object arg) {
        /*
         * a temporary array buffer, used as a snapshot of the state of
         * current Observers.
         */
        Observer[] arrLocal;

        synchronized (this) {
            /* We don't want the Observer doing callbacks into
             * arbitrary Observables while holding its own Monitor.
             * The code where we extract each Observable from
             * the ArrayList and store the state of the Observer
             * needs synchronization, but notifying observers
             * does not (should not).  The worst result of any
             * potential race-condition here is that:
             *
             * 1) a newly-added Observer will miss a
             *   notification in progress
             * 2) a recently unregistered Observer will be
             *   wrongly notified when it doesn't care
             */
            if (!hasChanged())
                return;

            arrLocal = observers.toArray(new Observer[observers.size()]);
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            arrLocal[i].update(this, arg);
    }

可以看到

  if (!hasChanged())
       return;

所以这就是为什么通知更新前一定要调用setChanged的原因

但是为什么要加入这样一个开关呢?可能原因大致有三点

1.筛选有效通知,只有有效通知可以调用setChanged。比如,我的微信朋友圈一条状态,好友A点赞,后续该状态的点赞和评论并不是每条都通知A,只有A的好友触发的操作才会通知A。

2.便于撤销通知操作,在主题中,我们可以设置很多次setChanged,但是在最后由于某种原因需要取消通知,我们可以使用clearChanged轻松解决问题。

3.主动权控制,由于setChanged为protected,而notifyObservers方法为public,这就导致存在外部随意调用notifyObservers的可能,但是外部无法调用setChanged,因此真正的控制权应该在主题这里。

三、创建Observer

/**
 * Created by chenshouyin on 2017/10/25.
 * 我的博客:http://blog.csdn.net/e_inch_photo
 * 我的Github:https://github.com/chenshouyin
 */

public abstract class DataWhatcher implements Observer {
    @Override
    public void update(Observable observable, Object data) {
        if (data instanceof DownloadEnty){
            notifyDataChange(data);
        }
    }

    public abstract void notifyDataChange(Object data);

}

为那些在目标发生改变时需获得通知的类定义个更新的接口,这里对接口再进行了判断,对外提供了notifyDataChange抽象方法,外部可在此抽象方法在获取到更新的回调以及更新的对象。

四、添加和取消观察者

private DataWhatcher dataWhatcher = new DataWhatcher() {

        @Override
        public void notifyDataChange(Object data) {
            downloadEnty = (DownloadEnty) data;
            if (downloadEnty.downloadStatus == DownloadEnty.DownloadStatus.downloading){
                LogUtil.e("download","===notifyDataChange===downloading"+downloadEnty.currentLenth);
            }else if (downloadEnty.downloadStatus == DownloadEnty.DownloadStatus.downloadcomplete){
                LogUtil.e("download","===notifyDataChange===downloadcomplete");
            }else if (downloadEnty.downloadStatus == DownloadEnty.DownloadStatus.downloadcansel){
                downloadEnty = null;
                LogUtil.e("download","===notifyDataChange===downloadcansel");
            }else if (downloadEnty.downloadStatus == DownloadEnty.DownloadStatus.downloadpause){
                LogUtil.e("download","===notifyDataChange===downloadpause");
            }else{
                LogUtil.e("download","===notifyDataChange===下载进度"+downloadEnty.currentLenth);
            }

        }
    };
  @Override
    protected void onResume() {
        super.onResume();
        DownloadManager.getInstance().addObserve(dataWhatcher);
    }

    @Override
    protected void onStop() {
        super.onStop();
        DownloadManager.getInstance().removeObserve(dataWhatcher);
    }

五、运行效果

运行效果

六、观察者模式使用总结

从上面可以看出,实际上观察者和被观察者是通过接口回调来通知更新的,首先创建一个观察者(数据监听)实例并实现数据变化接口,通过注册监听将实例传入被观察者(数据变化),当被观察者数据变化的时候使用该实例的接口回传状态。了解原理之后,我们可以利用观察者模式自定义实现。

拿微信公众号来举例,假设微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了 陈守印同学 这个公众号,当这个公众号更新时就会通知这些订阅的微信用户。我们来看看用代码如何实现:

1.抽象观察者(Observer)

public interface Observer {
    public void update(String message);
}

2.具体观察者(ConcrereObserver)

微信用户是观察者,里面实现了更新的方法:

里面定义了一个更新的方法:

public class WeixinUser implements Observer {
    // 微信用户名字
    private String name;
    public WeixinUser(String name) {
        this.name = name;
    }
    @Override
    public void update(String message) {
        System.out.println(name + ":" + message);
    }
}

3.抽象被观察者(Subject)

抽象主题,提供了attach、detach、notify三个方法:

public interface Subject {
    /**
     * 增加订阅者
     * @param observer
     */
    public void attach(Observer observer);
    /**
     * 删除订阅者
     * @param observer
     */
    public void detach(Observer observer);
    /**
     * 通知订阅者更新消息
     */
    public void notify(String message);
}

4.具体被观察者(ConcreteSubject)

微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法:

public class SubscriptionSubject implements Subject {
    //储存订阅公众号的微信用户
    private List<Observer> weixinUserlist = new ArrayList<Observer>();

    @Override
    public void attach(Observer observer) {
        weixinUserlist.add(observer);
    }

    @Override
    public void detach(Observer observer) {
        weixinUserlist.remove(observer);
    }

    @Override
    public void notify(String message) {
        for (Observer observer : weixinUserlist) {
            observer.update(message);
        }
    }
}

5.客户端调用

java
public class Client {
public static void main(String[] args) {
SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject();
//创建微信用户
WeixinUser user1=new WeixinUser("陈守印同学公众号粉丝A");
WeixinUser user2=new WeixinUser("陈守印同学公众号粉丝B");
WeixinUser user3=new WeixinUser("陈守印同学公众号粉丝C");
WeixinUser user4=new WeixinUser("陈守印同学公众号粉丝D");
//订阅公众号
mSubscriptionSubject.attach(user1);
mSubscriptionSubject.attach(user2);
mSubscriptionSubject.attach(user3);
mSubscriptionSubject.attach(user4);
//公众号更新发出消息给订阅的微信用户
mSubscriptionSubject.notify("陈守印同学公众号的文章更新啦");
}
}

6.运行结果

陈守印同学公众号粉丝A:陈守印同学公众号的文章更新啦
陈守印同学公众号粉丝B:陈守印同学公众号的文章更新啦
陈守印同学公众号粉丝C:陈守印同学公众号的文章更新啦
陈守印同学公众号粉丝D:陈守印同学公众号的文章更新啦

6.观察者模式优缺点
* 解除耦合,让耦合的双方都依赖于抽象,从而使得各自的变换都不会影响另一边的变换。
* 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。

公号后台回复”设计模式”,获取设计模式书籍

设计模式

后续文章持续更新中,微信扫码下方二维码免费关注!上一篇:Java多线程下载01:多线程的好处以及断点续传原理
点此查看全部最新文章


我的博客
我的简书
我的GitHub,喜欢的话给个star吧

作者:e_Inch_Photo 发表于2017/11/9 16:30:41 原文链接
阅读:25 评论:0 查看评论

React-Native生命周期的触发场景和一些小建议

$
0
0

转载请注明出处:王亟亟的大牛之路

把王者荣耀删了后这几天回到了举铁,遛鸟,打球,睡觉的正常节奏,然后卡了看之前写的一些东西,发现生命周期没写,那么就补一篇(虽然搜搜一大堆,但是残缺总不合适,再加点建议点那就和别人的不同了)

老规矩案例地址:https://github.com/ddwhan0123/Useful-Open-Source-Android(最近把时间选择器/日历这一块更新了好多内容)


React-Native控件的生命周期

方法名 作用 调用次数
constructor 构造函数,初始化需要的state 1次
componentWillMount 控件渲染前触发 1次
rander 渲染控件的方法 多次
componentDidMount 控件渲染后触发 1次
componentWillReceiveProps 组件接收到新的props时被调用 多次
shouldComponentUpdate 当组件接收到新的props和state时被调用 多次
componentWillUpdate props或者state改变,并且此前的shouldComponentUpdate方法返回为 true会调用该方法 多次
componentDidUpdate 组件重新渲染完成后会调用此方法 多次
componentWillUnmount 组件卸载和销毁之前被调用 1次

各个生命周期触发过程

这里写图片描述

上图为demo效果图

初次加载
这里写图片描述

依次触发了父控件的构造函数,componentwillMount,render,子控件的构造函数,子控件的componentwillMount,render,componentwillMount,最后才是父控件的componentwillMount

可以看出,初次的渲染周期是从外向内逐步渲染,内部完成后才算整体结束。


UI 刷新
这里写图片描述

点击事件触发了页面的状态机放生了变化,我们来看看每一步做了什么

1.首先是用户的点击触发onPress={this.addPress}

2.这方法做了一个事,把事件和值传递给reducer this.props.dispatch(add(this.state.intvalue));

3.reducer把必然的结果算完后有了个新的nextProps.result并且触发shouldComponentUpdate(nextProps, nextState)方法

4.比对值确实不同所以shouldComponentUpdate(nextProps, nextState)方法的返回值为true

5.因为返回值为true所以主控件触发render()方法 (主控件没复写componentWillUpdate()和componentDidUpdate()两个方法)

6.因为父控件给子控件传递的值正好也变了也就触发了子控件的刷新方法

 <SonComponent sonValue={this.state.showText.data + this.state.intvalue}/>

然后走了一圈一摸一样的流程完成了刷新


卸载姿势

这里写图片描述
卸载方法也是从外向内触发,点Home键不会触发(至少当前不触发),双击返回键会触发(任务中心关闭也没触发)。

这次的demo在上次redux的demo基础上做的修改,主要是阐明子组件和父组件的关系,源码地址:https://github.com/ddwhan0123/ReduxDemo


在各个生命周期建议做的事

constructor()方法里初始化state
componentDidMount()方法里跑网/耗时操作
componentWillMount()可在方法里对state进行最后的修改

注意,不要在 constructor 或者 render 里 setState(),這是因为 constructor 已含 this.state={} ,而 render 里 setState 会造成setState -> render -> setState -> render
能做的setState,只要是render前,就放在componentWillMount,render后,就放在 componentDidMount。這两个 function 是 react lifecycle 中,最常使用的两个。当然啦,还有其它的部分,那就交给客官们自行研究和推敲它们的使用时机咯!

有问题可以微信联系,当然得注明来意,不添加备注不会通过,谢谢(私人微信 非诚勿扰)
这里写图片描述

以后会同步微信发布,扫麦麦的码可以关注
这里写图片描述

作者:ddwhan0123 发表于2017/11/9 17:54:25 原文链接
阅读:62 评论:0 查看评论

洛谷3941 入阵曲

$
0
0

标签:模拟,前缀和

题目背景
pdf题面和大样例链接:http://pan.baidu.com/s/1cawM7c 密码:xgxv
丹青千秋酿,一醉解愁肠。
无悔少年枉,只愿壮志狂。

题目描述
小 F 很喜欢数学,但是到了高中以后数学总是考不好。

有一天,他在数学课上发起了呆;他想起了过去的一年。一年前,当他初识算法竞赛的 时候,觉得整个世界都焕然一新。这世界上怎么会有这么多奇妙的东西?曾经自己觉得难以解决的问题,被一个又一个算法轻松解决。

小 F 当时暗自觉得,与自己的幼稚相比起来,还有好多要学习的呢。

一年过去了,想想都还有点恍惚。

他至今还能记得,某天晚上听着入阵曲,激动地睡不着觉,写题写到鸡鸣时分都兴奋不 已。也许,这就是热血吧。

也就是在那个时候,小 F 学会了矩阵乘法。让两个矩阵乘几次就能算出斐波那契数列的第 10^100项,真是奇妙无比呢。

不过,小 F 现在可不想手算矩阵乘法——他觉得好麻烦。取而代之的,是一个简单的小问题。他写写画画,画出了一个 n \times mn×m 的矩阵,每个格子里都有一个不超过 kk 的正整数。

小 F 想问问你,这个矩阵里有多少个不同的子矩形中的数字之和是 kk 的倍数? 如果把一个子矩形用它的左上角和右下角描述为(x1,y1,x2,y2)(x1 ,y1,x2 ,y2 ),其中x1<=x2,y1<=y2 ;那么,我们认为两个子矩形是不同的,当且仅当他们以(x1,y1,x2,y2)表示时不同;也就是 说,只要两个矩形以 (x1,y1,x2,y2)表示时相同,就认为这两个矩形是同一个矩形,你应该 在你的答案里只算一次。

输入输出格式

输入格式:
从标准输入中读入数据。

输入第一行,包含三个正整数 n,m,k。

输入接下来 n 行,每行包含 m个正整数,第 i行第 j 列表示矩阵中第 i 行第 j列 中所填的正整数 a[ i ][ j ]
输出格式:
输出到标准输出中。
输入一行一个非负整数,表示你的答案。

输入输出样例
输入样例#1:
2 3 2
1 2 1
2 1 2
输出样例#1:
6
说明
【样例 1 说明】
这些矩形是符合要求的: (1, 1, 1, 3),(1, 1,2, 2),(1, 2, 1, 2),(1, 2, 2, 3),(2, 1, 2, 1),(2, 3, 2, 3)。
子任务会给出部分测试数据的特点。如果你在解决题目中遇到了困难,可以尝试只解 决一部分测试数据。每个测试点的数据规模及特点如下表:


 

分析:

实质上K倍区间的方法

O(n^4)用双重前缀和优化

O(n^3)可以枚举x1,x2两行并枚举y列   把这些压成一个数,之后进行枚举统计

对于任意一段区间[l,r]的和就是s[r]-s[l-1].

(sum[r]-sum[l-1])%k 保证了[l,r]这段区间要么%k等于0 要么比k小.

等于0说明这段区间正好是k的倍数然后通过前缀和相同的数据来判断出剩下的k的倍数:(sum[r]-sum[l-1])%k== 0.

变形后就是:sum[r]%k==sum[l-1]%k

Code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rep(i,a,b) for(register int i=a;i<=b;i++)
#define dep(i,a,b) for(register int i=a;i>=b;i--)
#define ll long long
#define mem(x,num) memset(x,num,sizeof x)
#ifdef WIN32
#define LL "%I64d\n"
#else
#define LL "%lld\n"
#endif
using namespace std;
inline ll read()
{
    ll f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
const int maxn=406,maxx=1e6+6;
ll n,m,k,x,s[maxn][maxn],b[maxx],cnt[maxx]={0},ans=0;
int main()
{
	n=read(),m=read(),k=read();
	rep(i,1,n)
		rep(j,1,m)x=read(),s[i][j]=s[i-1][j]+s[i][j-1]+x-s[i-1][j-1];
	rep(i,0,n-1)
		rep(j,i+1,n){
			cnt[0]=1;
			rep(p,1,m){
				b[p]=(s[j][p]-s[i][p]+k)%k;
				ans+=cnt[b[p]]++;
			}
			rep(p,1,m)cnt[b[p]]=0;
		}
	printf(LL,ans);
	return 0;
}
	


作者:qwerty1125 发表于2017/11/9 17:58:33 原文链接
阅读:56 评论:0 查看评论

洛谷3942 将军令

$
0
0

标签:贪心,树状DP

题目背景

pdf题面和大样例链接:http://pan.baidu.com/s/1cawM7c 密码:xgxv

 历史/落在/赢家/之手

 至少/我们/拥有/传说

 谁说/败者/无法/不朽

 拳头/只能/让人/低头

 念头/却能/让人/抬头

 抬头/去看/去爱/去追

 你心中的梦  

题目描述

又想起了四月。

如果不是省选,大家大概不会这么轻易地分道扬镳吧? 只见一个又一个昔日的队友离开了机房。

凭君莫话封侯事,一将功成万骨枯。

梦里,小 F 成了一个给将军送密信的信使。

现在,有两封关乎国家生死的密信需要送到前线大将军帐下,路途凶险,时间紧迫。小 F 不因为自己的祸福而避趋之,勇敢地承担了这个任务。

不过,小 F 实在是太粗心了,他一不小心把两封密信中的一封给弄掉了。

小 F 偷偷打开了剩下的那封密信。他 发现一副十分详细的地图,以及几句批文——原来 这是战场周围的情报地图。他仔细看后发现,在这张地图上标记了 n 个从 1 到 n 标号的 驿站,n − 1 条长度为 1 里的小道,每条小道双向连接两个不同的驿站,并且驿站之间可以通过小道两两可达。

小 F 仔细辨认着上面的批注,突然明白了丢失的信的内容了。原来,每个驿站都可以驻扎一个小队,每个小队可以控制距离不超过 k 里的驿站。如果有驿站没被控制,就容易产 生危险——因此这种情况应该完全避免。而那封丢失的密信里,就装着朝廷数学重臣留下的 精妙的排布方案,也就是用了最少的小队来控制所有驿站。

小 F 知道,如果能计算出最优方案的话,也许他就能够将功赎过,免于死罪。他找到了你,你能帮帮他吗? 当然,小 F 在等待你的支援的过程中,也许已经从图上观察出了一些可能会比较有用的 性质,他会通过一种特殊的方式告诉你。

输入输出格式

输入格式:

从标准输入中读入数据。

输入第 1 行一个正整数 n,k,t,代表驿站数,一支小队能够控制的最远距离,以及特殊性质所代表的编号。关于特殊性质请参照数据范围。

输入第 2 行至第 n 行,每行两个正整数ui​,表示在 ui 和 vi 间,有一条长度为一里的小道。

输出格式:

输出到标准输出中。

输出一行,为最优方案下需要的小队数。

输入输出样例

输入样例#1: 复制

4 1 0

1 2

1 3

1 4

输出样例#1: 复制

1

 

输入样例#2: 复制

6 1 0

1 2

1 3

1 4

4 5

4 6

输出样例#2: 复制

2

说明

【样例 1 说明】

如图。由于一号节点到周围的点距离均是 1,因此可以控制所有驿站。

【样例 2 说明】

如图,和样例 1 类似。


子任务会给出部分测试数据的特点。如果你在解决题目中遇到了困难,可以尝试只解 决一部分测试数据。

关于 t 的含义如下: t = 0:该测试点没有额外的特殊性质; t = 1:保证最多 8 个点的所连接的小道超过 1 条; t = 2:保证所有点到 1号点的距离不超过 2。

每个测试点的数据规模及特点如下表

 

题意:给定一棵树,选择一个点可以控制距离不超过

作者:qwerty1125 发表于2017/11/9 18:55:47 原文链接
阅读:36 评论:0 查看评论

【GitChat】达人课推荐:React 技术栈|Gradle 从入门到实战|GitHub 入味儿

$
0
0

达人课是GitChat的一款轻阅读产品,由特约讲师独家发布。每一个课程你都可获得6-12篇的深度文章,同时可在读者圈与讲师互动交流。GitChat达人课,让技术分享更简单。


如何从零学习 React 技术栈

余博伦 · 前端颜值担当

这里写图片描述

  • 本课程共六篇文章

在学会 React 之后,你的能力将不止局限于浏览器,React 还可以拓宽到使用 React Native 开发原生应用,以及使用 ReactVR 开发虚拟现实等各个领域。除了示例代码的讲解之外,本课程还会对个别核心概念的原理进行讲解介绍,并用实际案例 TodoList 应用来展示。

点击阅读全文


Gradle 从入门到实战

杨彪 · 技术总监及合伙人

这里写图片描述

本期达人课,我将带大家从最基础的 Groovy 语法开始学习,再一起探讨 Gradle 强大的特性,最后使用 Jenkins 和 Gradle 实现自动化的集成、打包和部署的实战练习。

通过本课程学习,可以全面了解 Gradle 的具体使用方法和实际项目的实战经验。

点击阅读全文


GitQ: GitHub 入味儿

Zoom.Quiet · Python 中文社区联合创始人

这里写图片描述

笔者从 GitHub 发布之前就一直在使用各种代码托管平台, 对于这类服务有足够的体验,愿意同大家分享,共同享受这个世界。

所以,本课程本质上是一老程序猿的私人吐糟集锦,目标不是教授使用 GitHub ,而是希望以一种聊天的形式,将使用 GitHub 的感觉传达给从未注册过的新人,以便每个人能自然的进入、享受、成长,分享编程的乐趣。

点击阅读全文


GitChat 是一款基于微信平台的 IT 阅读/写作互动产品。我们的目的是通过这款产品改变 IT 知识的学习方式,让专业读者获得自主选择权,让知识分享者获得收益。

关于GitChat 你想知道的都在这

作者:blogdevteam 发表于2017/11/9 19:11:28 原文链接
阅读:92 评论:0 查看评论

吴恩达Coursera深度学习课程 DeepLearning.ai 编程作业——Gradients_check(2-1.3)

$
0
0

这里写图片描述

import numpy as np
from testCases import *
from gc_utils import sigmoid,relu,dictionary_to_vector,vector_to_dictionary,gradients_to_vector
from testCases import gradient_check_n_test_case

Gradient_check.py

import numpy as np
from testCases import *
from gc_utils import sigmoid,relu,dictionary_to_vector,vector_to_dictionary,gradients_to_vector
from testCases import gradient_check_n_test_case
def gradient_check(x,theta,epsilon= 1e-7):
    J=x*theta
    dtheta=x
    gradapprox=(x*(theta+epsilon)-x*(theta-epsilon))/(2*epsilon)
    grad=dtheta
    numerator=np.linalg.norm(grad-gradapprox)
    denomitor=np.linalg.norm(grad)+np.linalg.norm(gradapprox)
    difference=numerator/denomitor
    if difference < epsilon:
        print "the gradient is correct"
    elif difference >= epsilon:
        print "the gradient is not so ideal"
    return J,difference

x,theta=2,4
J,difference = gradient_check(x,theta)
print("difference = "+str(difference))


def forward_propagation_n(X,Y,parameters):
    m=X.shape[1]
    W1=parameters["W1"]
    b1=parameters["b1"]
    W2=parameters["W2"]
    b2=parameters["b2"]  
    W3=parameters["W3"]
    b3=parameters["b3"]

    Z1=np.dot(W1,X)+b1
    A1=relu(Z1)
    Z2=np.dot(W2,A1)+b2
    A2=relu(Z2)
    Z3=np.dot(W3,A2)+b3
    A3=sigmoid(Z3)
    cost=(-1.0/m)*(np.sum(Y*np.log(A3)+(1-Y)*np.log(1-A3)))
    cache=(Z1,A1,W1,b1,Z2,A2,W2,b2,Z3,A3,W3,b3)
    return cost,cache

def backward_propagation_n(X,Y,cache):
    (Z1,A1,W1,b1,Z2,A2,W2,b2,Z3,A3,W3,b3)=cache
    m=X.shape[1]
    grads={}
    dZ3=A3-Y
    dW3=(1.0/m)*np.dot(dZ3,A2.T)
    db3=(1.0/m)*np.sum(dZ3,axis=1,keepdims=True)

    dA2=np.dot(W3.T,dZ3)   
    dZ2=np.multiply(dA2,np.int64(Z2>0))
    dW2=(1.0/m)*np.dot(dZ2,A1.T)
    db2=(1.0/m)*np.sum(dZ2,axis=1,keepdims=True)

    dA1=np.dot(W2.T,dZ2)   
    dZ1=np.multiply(dA1,np.int64(Z1>0))
    dW1=(1.0/m)*np.dot(dZ1,X.T)
    db1=(1.0/m)*np.sum(dZ1,axis=1,keepdims=True)

    grads={"dZ3": dZ3, "dW3": dW3, "db3": db3,
                 "dA2": dA2, "dZ2": dZ2, "dW2": dW2, "db2": db2,
                 "dA1": dA1, "dZ1": dZ1, "dW1": dW1, "db1": db1}
    return grads

def implement_gradient_check_n(X,Y,grads,parameter,epsilon=1e-6):
    parameters_value,_=dictionary_to_vector(parameter)
    grads=gradients_to_vector(grads)
    num_parameters=parameters_value.shape[0]
    J_plus=np.zeros((num_parameters,1))
    J_minus=np.zeros((num_parameters,1))
    gradapprox=np.zeros((num_parameters,1))
    for i in range(num_parameters):
        thetaplus=np.copy(parameters_value)
        thetaplus[i][0]=thetaplus[i][0]+epsilon
        J_plus[i],_=forward_propagation_n(X,Y,vector_to_dictionary(thetaplus))
        thetaminus=np.copy(parameters_value)
        thetaminus[i][0]=thetaminus[i][0]-epsilon
        J_minus[i],_=forward_propagation_n(X,Y,vector_to_dictionary(thetaminus))

        gradapprox[i]=(J_plus[i]-J_minus[i])/(2*epsilon)

    difference=np.linalg.norm(grads-gradapprox)/(np.linalg.norm(grads)+np.linalg.norm(gradapprox))
    if difference<1e-6:
        print ("\033[92m" + "Your backward propagation works perfectly fine! difference = " + str(difference) + "\033[0m")
    else:
        print ("\033[93m" + "There is a mistake in the backward propagation! difference = " + str(difference) + "\033[0m")

    return difference

X, Y, parameters = gradient_check_n_test_case()
print parameters
cost, cache = forward_propagation_n(X, Y, parameters)
gradients = backward_propagation_n(X, Y, cache)
print gradients
difference = implement_gradient_check_n(X, Y,gradients,parameters)

gc_utils.py:

import numpy as np
def sigmoid(x):
    """
    Compute the sigmoid of x
    Arguments:
    x -- A scalar or numpy array of any size.
    Return:
    s -- sigmoid(x)
    """
    s = 1/(1+np.exp(-x))
    return s

def relu(x):
    """
    Compute the relu of x
    Arguments:
    x -- A scalar or numpy array of any size.
    Return:
    s -- relu(x)
    """
    s = np.maximum(0,x)    
    return s

def dictionary_to_vector(parameters):
    """
    Roll all our parameters dictionary into a single vector satisfying our specific required shape.
    """
    keys = []
    count = 0
    for key in ["W1", "b1", "W2", "b2", "W3", "b3"]:

        # flatten parameter
        new_vector = np.reshape(parameters[key], (-1,1))
        keys = keys + [key]*new_vector.shape[0]        
        if count == 0:
            theta = new_vector
        else:
            theta = np.concatenate((theta, new_vector), axis=0)
        count = count + 1
    return theta, keys

def vector_to_dictionary(theta):
    """
    Unroll all our parameters dictionary from a single vector satisfying our specific required shape.
    """
    parameters = {}
    parameters["W1"] = theta[:20].reshape((5,4))
    parameters["b1"] = theta[20:25].reshape((5,1))
    parameters["W2"] = theta[25:40].reshape((3,5))
    parameters["b2"] = theta[40:43].reshape((3,1))
    parameters["W3"] = theta[43:46].reshape((1,3))
    parameters["b3"] = theta[46:47].reshape((1,1))
    return parameters

def gradients_to_vector(gradients):
    """
    Roll all our gradients dictionary into a single vector satisfying our specific required shape.
    """    
    count = 0
    for key in ["dW1", "db1", "dW2", "db2", "dW3", "db3"]:
        # flatten parameter
        new_vector = np.reshape(gradients[key], (-1,1))        
        if count == 0:
            theta = new_vector
        else:
            theta = np.concatenate((theta, new_vector), axis=0)
        count = count + 1
    return theta

这里写图片描述
这里写图片描述
这里写图片描述

def gradient_check(x,theta,epsilon= 1e-7):  #定义一元梯度检测函数
    J=x*theta    #函数主体
    dtheta=x     #对theta求导
    gradapprox=(x*(theta+epsilon)-x*(theta-epsilon))/(2*epsilon)   #梯度估计,就是上述公式(1)
    grad=dtheta
    numerator=np.linalg.norm(grad-gradapprox)  #其中np.linalg.norm()相当于二范数
    denomitor=np.linalg.norm(grad)+np.linalg.norm(gradapprox)   
    difference=numerator/denomitor  #上述公式(2)
    if difference < epsilon:   #梯度误差估计,小于epsilon说明正确
        print "the gradient is correct"
    elif difference >= epsilon:
        print "the gradient is not so ideal"
    return J,difference

x,theta=2,4
J,difference = gradient_check(x,theta)
print("difference = "+str(difference))

Expected output:

the gradient is correct
difference = 2.91933588329e-10

这里写图片描述

def forward_propagation_n(X, Y, parameters): 
    """
    Implements the forward propagation (and computes the cost) presented in Figure 3.

    Arguments:
    X -- training set for m examples
    Y -- labels for m examples 
    parameters -- python dictionary containing your parameters "W1", "b1", "W2", "b2", "W3", "b3":
                    W1 -- weight matrix of shape (5, 4)
                    b1 -- bias vector of shape (5, 1)
                    W2 -- weight matrix of shape (3, 5)
                    b2 -- bias vector of shape (3, 1)
                    W3 -- weight matrix of shape (1, 3)
                    b3 -- bias vector of shape (1, 1)

    Returns:
    cost -- the cost function (logistic cost for one example)
    """

    # retrieve parameters
    m = X.shape[1]
    W1 = parameters["W1"]
    b1 = parameters["b1"]
    W2 = parameters["W2"]
    b2 = parameters["b2"]
    W3 = parameters["W3"]
    b3 = parameters["b3"]

    # LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SIGMOID
    Z1 = np.dot(W1, X) + b1
    A1 = relu(Z1)
    Z2 = np.dot(W2, A1) + b2
    A2 = relu(Z2)
    Z3 = np.dot(W3, A2) + b3
    A3 = sigmoid(Z3)

    # Cost
    logprobs = np.multiply(-np.log(A3),Y) + np.multiply(-np.log(1 - A3), 1 - Y)
    cost = 1./m * np.sum(logprobs)

    cache = (Z1, A1, W1, b1, Z2, A2, W2, b2, Z3, A3, W3, b3)

    return cost, cache
def backward_propagation_n(X, Y, cache):
    """
    Implement the backward propagation presented in figure 2.

    Arguments:
    X -- input datapoint, of shape (input size, 1)
    Y -- true "label"
    cache -- cache output from forward_propagation_n()

    Returns:
    gradients -- A dictionary with the gradients of the cost with respect to each parameter, activation and pre-activation variables.
    """

    m = X.shape[1]
    (Z1, A1, W1, b1, Z2, A2, W2, b2, Z3, A3, W3, b3) = cache

    dZ3 = A3 - Y
    dW3 = 1./m * np.dot(dZ3, A2.T)
    db3 = 1./m * np.sum(dZ3, axis=1, keepdims = True)

    dA2 = np.dot(W3.T, dZ3)
    dZ2 = np.multiply(dA2, np.int64(A2 > 0))
    dW2 = 1./m * np.dot(dZ2, A1.T) 
    db2 = 1./m * np.sum(dZ2, axis=1, keepdims = True)

    dA1 = np.dot(W2.T, dZ2)
    dZ1 = np.multiply(dA1, np.int64(A1 > 0))
    dW1 = 1./m * np.dot(dZ1, X.T)
    db1 = 1./m * np.sum(dZ1, axis=1, keepdims = True)

    gradients = {"dZ3": dZ3, "dW3": dW3, "db3": db3,
                 "dA2": dA2, "dZ2": dZ2, "dW2": dW2, "db2": db2,
                 "dA1": dA1, "dZ1": dZ1, "dW1": dW1, "db1": db1}

    return gradients

这里写图片描述
这里写图片描述

def implement_gradient_check_n(X,Y,grads,parameter,epsilon=1e-6):  #定义n维梯度检测函数
    parameters_value,_=dictionary_to_vector(parameter)  #将parameter字典reshape成向量
    grads=gradients_to_vector(grads)
    num_parameters=parameters_value.shape[0]
    J_plus=np.zeros((num_parameters,1))
    J_minus=np.zeros((num_parameters,1))
    gradapprox=np.zeros((num_parameters,1))
    for i in range(num_parameters):
        thetaplus=np.copy(parameters_value)  #将parameter_value复制给thetaplus
        thetaplus[i][0]=thetaplus[i][0]+epsilon #thetaplus+epsilon
        J_plus[i],_=forward_propagation_n(X,Y,vector_to_dictionary(thetaplus)) #求出代价函数值
        thetaminus=np.copy(parameters_value)
        thetaminus[i][0]=thetaminus[i][0]-epsilon
        J_minus[i],_=forward_propagation_n(X,Y,vector_to_dictionary(thetaminus))#同样的,求出第i个元素减去epsilon之后得到的代价函数值

        gradapprox[i]=(J_plus[i]-J_minus[i])/(2*epsilon)  #梯度估计

    difference=np.linalg.norm(grads-gradapprox)/(np.linalg.norm(grads)+np.linalg.norm(gradapprox))
    if difference<1e-6:
        print ("\033[92m" + "Your backward propagation works perfectly fine! difference = " + str(difference) + "\033[0m")
    else:
        print ("\033[93m" + "There is a mistake in the backward propagation! difference = " + str(difference) + "\033[0m")

    return difference

X, Y, parameters = gradient_check_n_test_case()
print parameters
cost, cache = forward_propagation_n(X, Y, parameters)
gradients = backward_propagation_n(X, Y, cache)
print gradients
difference = implement_gradient_check_n(X, Y,gradients,parameters)

Expected output

Your backward propagation works perfectly fine! difference = 8.26588224678e-09
作者:Hansry 发表于2017/11/9 19:36:01 原文链接
阅读:35 评论:0 查看评论

Android O 拨打电话流程之呼出

$
0
0

DialtactsActivity.java模块是负责电话拨打界面
DialpadFragment.java:负责拨号盘的类文件
这里写图片描述

拨打电话界面,在onClick方法中点击dialpad_floating_action_button按钮进行拨打电话,进入handleDialButtonPressed()中处理拨打电话 DialerUtils.startActivityWithErrorToast
在CallIntentBuilder类的build方法中,
Intent intent = new Intent(Intent.ACTION_CALL, uri);
Intent.ACTION_CALL =”android.intent.action.CALL”

这里写图片描述

AndroidManifest.xml文件中有定义,对应文件为UserCallActivity
这里写图片描述
这里写图片描述

startActivityWithErrorToast中调用 placeCallOrMakeToast,最终调用
这里写图片描述

TelecomManager.java中的placeCall
这里写图片描述
最终进入TelecomManager.placeCall中,该方法继续调用ITelecomService,具体实现在类TelecomServiceImpl中
这里写图片描述
/packages/services/Telecomm/src/com/android/server/telecom/components/UserCallIntentProcessor.java
UserCallIntentProcessor.java中处理processIntent去发送到Activity
这里写图片描述

在processOutgoingCallIntent中处理,拨号权限,看是否直接拒绝,都是通过 sendBroadcastToReceiver(intent)发送出去
这里写图片描述

这里写图片描述

/packages/services/Telecomm/src/com/android/server/telecom/components/PrimaryCallReceiver.java

进入广播接收器PrimaryCallReceiver中通过调用getTelecomSystem中的getCallIntentProcessor来获取CallIntentProcessor对象,该对象的创建在TelecomSystem的构造方法中
这里写图片描述

这里写图片描述

/packages/services/Telecomm/src/com/android/server/telecom/CallIntentProcessor.java

该processIntent中处理,该类中的CallsManager是从哪儿来的呢?
这是CallIntentProcessor构造方法中传入进来的,这就要追寻到CallIntentProcessor创建来的,最终发现CallManager也是在TelecomSystem中创造的!
这里写图片描述

processOutgoingCallIntent中的处理部分,通过CallManager创建Call
这里写图片描述
这里写图片描述

/packages/services/Telecomm/src/com/android/server/telecom/CallsManager.java

这里写图片描述

主要完成的功能是:
创建Call对象
获取当前激活的卡的列表
获取当前使用哪一张卡呼出
设置当前通话账户: call.setTargetPhoneAccount(phoneAccountHandle);
设置当前call正在连接,将call加入列表 addCall(call);
这里写图片描述

这里写图片描述

当调用CallsManager的startOutgoingCall结束,返回call对象,之后调用
/packages/services/Telecomm/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java

processIntent()方法中最终调用 mCallsManager.placeOutgoingCall,该mCallsManager对象还是在之前创建的,placeOutgoingCall继续调用 call.startCreateConnection(mPhoneAccountRegistrar);建立连接

packages/services/Telecomm/src/com/android/server/telecom/Call.java
这里写图片描述

/packages/services/Telecomm/src/com/android/server/telecom/CreateConnectionProcessor.java
处理连接请求
这里写图片描述

/packages/services/Telecomm/src/com/android/server/telecom/ConnectionServiceWrapper.java

在方法createConnection 内部有 mBinder.bind(callback, call);这是一个回调函数,调用完成之后,将会调用callback中的onSuccess方法

这里写图片描述

这里写图片描述

会继续调用createConnection,mServiceInterface是IConnectionService的具体实现在ConnectionService.java的匿名内部类中

frameworks/base/telecomm/java/android/telecom/ConnectionService.java

这里写图片描述

发送创建连接消息MSG_CREATE_CONNECTION,之后调用ConnectionService.java中的方法createConnection
这里写图片描述
这里写图片描述

调用onCreateOutgoingConnection去创建连接,该方法为空,所以实现应该再起子类之中

/packages/services/Telephony/src/com/android/services/telephony/TelephonyConnectionService.java
public Connection onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, final ConnectionRequest request)方法来呼出连接

这里写图片描述

创建TelephonyConnection对象,获取该对象后,调用placeOutgoingConnection
这里写图片描述
这里写图片描述

frameworks/opt/telephony/src/java/com/android/internal/telephony/GsmCdmaPhone.java

发现是调用phone.dial拨打电话,该phone其实是GsCdmaPhone.java对象
public Connection dial(String dialString, UUSInfo uusInfo, int videoState, Bundle intentExtras)

这里写图片描述

dial方法之中还有一个ImsPhone对象,该对象主要处理Volte通话的

protected Connection dialInternal(String dialString, UUSInfo uusInfo, int videoState, Bundle intentExtras, ResultReceiver wrappedCallback)
这里写图片描述
在这里将会调用mCT.dial方法,也就是GsmCdmaCallTracker对象

/frameworks/opt/telephony/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
这里写图片描述

调用mCi.dial拨打,mCi是RIL对象,在GsmCdmaCallTracker的父类CallTracker中定义,updatePhoneState状态,发起状态改变通知.
那么这些对象是什么时候创建的呢?GsmCdmaCallTracker,RIL对象

/packages/services/Telephony/src/com/android/phone/PhoneApp.java
/packages/services/Telephony/src/com/android/phone/PhoneGlobals.java
/frameworks/opt/telephony/src/java/com/android/internal/telephony/PhoneFactory.java

/packages/services/Telephony/src/com/android/phone/PhoneApp.java
application标签中的android:persistent=”true”表示该应用始终常驻内存,一开始系统服务启动完毕之后,自动加载到内存中
这里写图片描述

这里写图片描述

/packages/services/Telephony/src/com/android/phone/PhoneGlobals.java

这里写图片描述

/frameworks/opt/telephony/src/java/com/android/internal/telephony/PhoneFactory.java


public static void makeDefaultPhone(Context context){
TelephonyComponentFactory telephonyComponentFactory = TelephonyComponentFactory.getInstance();
sCommandsInterfaces[i] = telephonyComponentFactory.makeRIL(context,networkModes[i], cdmaSubscription, i);
这里写图片描述

在makePhone里面创建GsmCdmaPhone,在GsmCdmaPhone的构造方法中创建来GsmCdmaCallTracker
这里写图片描述

综上所述,也就是说在系统开机之后,这些对象已经随着PhoneApp被创建出来了

frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java
这里写图片描述

发送请求:RIL_REQUEST_DIAL

/frameworks/opt/telephony/src/java/com/android/internal/telephony/RadioResponse.java主要处理上层发出请求,底层回馈信息并调用RIL对象的processResponseDone方法,完成一次主动请求,相当之前的processsolicated
/frameworks/opt/telephony/src/java/com/android/internal/telephony/RadioIndication.java主要处理底层上报消息,相当之前的processUnsolicated
/hardware/ril/libril/ril_service.cpp
RIL.java中新采用的radioProxy代理类来调用相关操作,该类调用的相关操作最终都是调用ril_service.cpp 中的方法

在RIL里面由对应的请求,dial,那么在RadioResponse.java中由 *+Response,dialResponse
这里写图片描述

这里写图片描述

这里写图片描述

在responseVoid中调用sendMessageResponse向上层发送消息
先去调用mRil.processResponse处理上报,然后发送消息,最终在调用processResponseDone来完成一次对应的请求
发送的消息为EVENT_OPERATION_COMPLETE,该消息是GsmCdmaCallTracker调用dial时利用obtainCompleteMessage去处理的.

这里写图片描述

这里写图片描述

这里写图片描述

在operationComplete中继续调用mCi.getCurrentCalls来获取call状态,消息为EVENT_POLL_CALLS_RESULT

   protected synchronized void handlePollCalls(AsyncResult ar) {
   >>>>>>>>>>>>>>>>>>
             updatePhoneState();  //更新手机状态
             mPhone.notifyPreciseCallStateChanged();//发起状态改变通知
  >>>>>>>>>>>>>>>>>>>
 }

之后底层会自动上报消息:RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED
先调用RIL对象中的processIndication
这里写图片描述

最后mCallStateRegistrants发起通知,GsmCdmaCallTracker注册来该事件, mCi.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null);
这里写图片描述

GsmCdmaCallTracker处理消息 EVENT_CALL_STATE_CHANGE,最后调用其父类中pollCallsWhenSafe()方法来处理,调用 handlePollCalls来查询call的状态等操作。

拨打电话呼出流程图:

这里写图片描述

作者:u013766436 发表于2017/11/9 19:46:42 原文链接
阅读:30 评论:0 查看评论

洛谷P3195 [HNOI2008]玩具装箱TOY(BZOJ1010)

$
0
0

斜率优化DP

洛谷题目传送门
BZOJ题目传送门

状态及转移很好想:
f[i]表示以i为结尾的玩具所需费用的最小值。

转移:

f[i]=min(f[j]+(ik=jc[k]+ij1L)2)

然而n50000,于是可以考虑进行斜率优化

假设对i而言xy好,则
f[x]+(ik=xc[k]+ix1L)2<f[y]+(ik=y+iy1L)2

记一个c的前缀和C[i],则ik=jc[k]=C[i]C[j]

X[i]=C[i]i1L,Y[i]=C[i]+i,则
f[x]+(X[i]Y[x])2<f[y]+(X[i]Y[y])2

左边=f[x]+X[i]22X[i]Y[x]+Y[x]2
右边=f[y]+X[i]22X[i]Y[y]+Y[y]2

联立后化简,得
f[x]+X[x]2f[y]X[y]22(Y[x]Y[y])>X[i]

然后用单调队列维护一下左边这个东西就行啦

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 50000 
using namespace std;
typedef long long LL;
LL f[MAXN+5],c[MAXN+5],X[MAXN+5],Y[MAXN+5];
int n,l,que[MAXN+5];
#define sqr(x) ((x)*(x))
LL K(int x,int y){
    return (f[x]+sqr(Y[x])-f[y]-sqr(Y[y]))/(2*(Y[x]-Y[y]));//左边的式子
}
int main(){
    scanf("%d%d",&n,&l);
    for (int i=1;i<=n;i++){
        scanf("%lld",&c[i]),c[i]+=c[i-1];
        X[i]=c[i]+i-1-l,Y[i]=c[i]+i;
        f[i]=1e18;
    }
    int r=0,w=0; 
    for (int i=1;i<=n;i++){//单调队列维护
        while (r<w&&K(que[r],que[r+1])<X[i]) r++;
        f[i]=f[que[r]]+sqr(X[i]-Y[que[r]]);
        while (r<w&&K(que[w-1],que[w])>=K(que[w],i)) w--;
        que[++w]=i;
    }
    return printf("%lld\n",f[n]),0;
}
作者:a1799342217 发表于2017/11/9 20:05:24 原文链接
阅读:33 评论:0 查看评论

自定义View之仿Emui开关控件

$
0
0

1. 简介

使用华为手机,发现设置应用中的开关空间颜色听挺好看,所以自己想写一个类似的,当然不可能完全相同,但是大致的效果差不多。
操作步骤:
1. 截图。PS取色;
2. 判断左右两边的半径;
3. 继承View复写onDraw方法,手动绘制。
4. 控制开关的状态:on/off

2. 效果

这里写图片描述

3. 实现

3.1 自定义属性

没有定义,颜色和半径都是按照自己的效果写成固定的,当然也可以写成自定义属性。

3.2 自定义View代码

涉及到一些动画的使用和View的事件处理

public class CustomSwitchView extends View {
    /**
     * 开关圆点颜色
     */
    private final int SWITCH_DOT_COLOR = 0xffffffff;

    /**
     * 关闭状态下的背景颜色
     */
    private final int OFF_BACKGROUND_COLOR = 0xffe2e2e2;

    /**
     * 打开状态下的背景颜色
     */
    private final int ON_BACKGROUND_COLOR = 0xff007dff;

    /**
     * 边界和开关圆点的间距
     */
    private final int BOUND_DOT_GAP = 8;

    /**
     * 是否打开
     */
    private boolean isOn = false;

    /**
     * 圆点的半径和坐标位置
     */
    private int mRadius;

    private int startX;
    private int endX;
    private float centerX;
    private float centerY;
    private RectF mRectF;

    /**
     * 开关的画笔
     */
    private Paint mSwitchPaint;

    public CustomSwitchView(Context context) {
        this(context, null);
    }

    public CustomSwitchView(Context context,
            @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomSwitchView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    /**
     * 打开或者关闭
     *
     * @param on true 打开, false 关闭
     */
    public void setOn(boolean on) {
        isOn = on;
        ValueAnimator animator;
        if (on) {
            animator = ValueAnimator.ofFloat(centerX, endX);
        } else {
            animator = ValueAnimator.ofFloat(centerX, startX);
        }
        animator.setDuration(200);
        animator.setRepeatCount(0);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                centerX = (Float) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.start();
    }

    private void init() {
        mSwitchPaint = new Paint();
        mSwitchPaint.setColor(OFF_BACKGROUND_COLOR);
        mSwitchPaint.setStrokeWidth(10f);
        mSwitchPaint.setStrokeCap(Paint.Cap.ROUND);
        mSwitchPaint.setAntiAlias(true);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mRectF = new RectF(getLeft(), getTop(), getRight(), getBottom());
        mRadius = h / 2 - BOUND_DOT_GAP;
        startX = getLeft() + mRadius + BOUND_DOT_GAP / 2;
        endX = getRight() - mRadius - BOUND_DOT_GAP / 2;
        centerX = startX;
        centerY = (getTop() + getBottom()) / 2.0f;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (isOn) {
            mSwitchPaint.setColor(ON_BACKGROUND_COLOR);
        } else {
            mSwitchPaint.setColor(OFF_BACKGROUND_COLOR);
        }
        canvas.drawRoundRect(mRectF, mRadius, mRadius, mSwitchPaint);
        mSwitchPaint.setColor(SWITCH_DOT_COLOR);
        canvas.drawCircle(centerX, centerY, mRadius, mSwitchPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_UP:
                setOn(!isOn);
                break;
            case MotionEvent.ACTION_MOVE: {
                float x = event.getX();
                if (x < endX && x > startX) {
                    centerX = x;
                    invalidate();
                }
            }
            break;
            default:
                break;
        }
        return true;
    }
}

4. 源码

源码依然是上传到Github
CustomViewDemo

作者:poorkick 发表于2017/11/9 20:29:23 原文链接
阅读:25 评论:0 查看评论

leetcode题解-13. Roman to Integer && 14. Longest Common Prefix && 20. Valid Parentheses

$
0
0

本次的三道题目都没有什么难度,我们先看第一道,题目如下:

Given a roman numeral, convert it to an integer.

Input is guaranteed to be within the range from 1 to 3999.

就是将古罗马数字转化为int型,上网查一下古罗马数字的表示方法就可以知道,总共有“IVXLCDM”7种字符分别表示1、5、10、50、100、500、1000。以下摘自于维基百科:

重复数次:一个罗马数字重复几次,就表示这个数的几倍。

右加左减:
    在较大的罗马数字的右边记上较小的罗马数字,表示大数字加小数字。
    在较大的罗马数字的左边记上较小的罗马数字,表示大数字减小数字。
    左减的数字有限制,仅限于I、X、C。比如45不可以写成VL,只能是XLV
    但是,左减时不可跨越一个位值。比如,99不可以用IC( {\displaystyle 100-1} 100-1)表示,而是用XCIX( {\displaystyle [100-10]+[10-1]} [100-10]+[10-1])表示。(等同于阿拉伯数字每位数字分别表示。)
    左减数字必须为一位,比如8写成VIII,而非IIX。
    右加数字不可连续超过三位,比如14写成XIV,而非XIIII。(见下方“数码限制”一项。)

所以就很容易得到下面的程序:

    public static int charToInt(char c) {
        int data = 0;

        switch (c) {
            case 'I':
                data = 1;
                break;
            case 'V':
                data = 5;
                break;
            case 'X':
                data = 10;
                break;
            case 'L':
                data = 50;
                break;
            case 'C':
                data = 100;
                break;
            case 'D':
                data = 500;
                break;
            case 'M':
                data = 1000;
                break;
        }

        return data;
    }

    public static int romanToInt(String s) {
        int i, total, pre, cur;

        total = charToInt(s.charAt(0));

        for (i = 1; i < s.length(); i++) {
            pre = charToInt(s.charAt(i - 1));
            cur = charToInt(s.charAt(i));
            //左减右加
            if (cur <= pre) {
                total += cur;
            } else {
                total = total - pre * 2 + cur;
            }
        }

        return total;
    }

再来看第二道题目:

Write a function to find the longest common prefix string amongst an array of strings.

本题是寻找一个字符串数组中所有字符串开头部分的公共子串,也就是找大家都相同的最长子串。逐个遍历然后比较即可得到min子串,代码如下所示:

    public static String longestCommonPrefix(String[] strs) {
        if(strs == null || strs.length == 0)
            return "";
        String min = strs[0];
        for(int i=1; i<strs.length; i++){
            //获得两个字符串中最小的长度
            int len = Math.min(min.length(), strs[i].length());
            //如果被比较的字符串比较短,则将min直接截断至其长度
            min = min.substring(0, len);
            //判断两个字符串是否相等,不等就将min截断在该位置处
            for(int j=0; j<len; j++){
                if(min.charAt(j) != strs[i].charAt(j)) {
                    min = min.substring(0, j);
                    break;
                }
            }

        }
        return min;
    }

这种方法效率比较低,我们可以参考下面的代码来进行改进效率,主要利用了内置的startsWith函数:

    public String longestCommonPrefix1(String[] strs) {
        int n=strs.length;
        if(n==0) return "";
        StringBuilder st=new StringBuilder(strs[0]);
        for(int i=1;i<n;i++){
            //对于要比较多字符串,如果不满足则将st最后一个字符删除,直到找到公共子串。
            while(!strs[i].startsWith(st.toString())) st.deleteCharAt(st.length()-1);
        }
        return st.toString();
    }

此外还有一种方法是,将数组排序,然后直接比较头尾两个字符串即可,这里我们需要理解一下对字符串数组进行排序的依据就是两个字符串的大小判断,就是按照其字符串中字符的顺序进行的:

    public String longestCommonPrefix2(String[] strs) {
        StringBuilder result = new StringBuilder();

        if (strs != null && strs.length > 0) {
            //排序
            Arrays.sort(strs);
            //首位两个字符串
            char[] a = strs[0].toCharArray();
            char[] b = strs[strs.length - 1].toCharArray();
            //比较两个字符串
            for (int i = 0; i < a.length; i++) {
                if (b.length > i && b[i] == a[i]) {
                    result.append(b[i]);
                } else {
                    return result.toString();
                }
            }
            return result.toString();
        } else
            return "";
    }

接下来我们开最后一道题目:

Given a string containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid.

The brackets must close in the correct order, "()" and "()[]{}" are all valid but "(]" and "([)]" are not.

就是要判断“(){}[]”的开闭顺序是否正确。很容易我们就会想到使用栈stack来解决这个问题,如果有没有正常结束的开,就把相应的符号入栈,然后在出栈。就可以实现本功能,代码入下:

    //50%
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<Character>();
        for (char c : s.toCharArray()) {
            if (c == '(')
                stack.push(')');
            else if (c == '{')
                stack.push('}');
            else if (c == '[')
                stack.push(']');
            else if (stack.isEmpty() || stack.pop() != c)
                return false;
        }
        return stack.isEmpty();
    }

上面这种方法虽然简单,但是使用了stack数据结构,效率比较低,我们可以使用数组来代替他的功能,代码如下所示:

    //99.9%
    public boolean isValid1(String s) {
        char[] stack = new char[s.length()];
        int head = 0;
        for(char c : s.toCharArray()) {
            switch(c) {
            //下面三种情况,则将其存到数组中
                case '{':
                case '[':
                case '(':
                    stack[head++] = c;
                    break;
                //遇到后三种符号时,则判断数组最后一个元素是否为与之对应的,或者数组为空时,也表明关闭顺序不对
                case '}':
                    if(head == 0 || stack[--head] != '{') return false;
                    break;
                case ')':
                    if(head == 0 || stack[--head] != '(') return false;
                    break;
                case ']':
                    if(head == 0 || stack[--head] != '[') return false;
                    break;
            }
        }
        return head == 0;

    }
作者:liuchonge 发表于2017/11/9 20:56:33 原文链接
阅读:0 评论:0 查看评论

LeetCode-667:Beautiful Arrangement II (数组的完美安排) -- medium

$
0
0

Question

Given two integers n and k, you need to construct a list which contains n different positive integers ranging from 1 to n and obeys the following requirement:
Suppose this list is [a1, a2, a3, … , an], then the list [|a1 - a2|, |a2 - a3|, |a3 - a4|, … , |an-1 - an|] has exactly k distinct integers.

If there are multiple answers, print any of them.

Example 1:

Input: n = 3, k = 1
Output: [1, 2, 3]

Explanation: The [1, 2, 3] has three different positive integers ranging from 1 to 3, and the [1, 1] has exactly 1 distinct integer: 1.

Example 2:

Input: n = 3, k = 2
Output: [1, 3, 2]

Explanation: The [1, 3, 2] has three different positive integers ranging from 1 to 3, and the [2, 1] has exactly 2 distinct integers: 1 and 2.

Note:

  • The n and k are in the range 1 <= k < n <= 104.

问题解析:

给定整数n和k,构造一个包含1-n的n个整数的数组[a1, a2, a3, … , an],使得[|a1 - a2|, |a2 - a3|, |a3 - a4|, … , |an-1 - an|] 新数组含有k个不同的数。

Answer

Solution 1:

二分法。

  • 由题我们可知:1..n最多可以构造出n-1个不同的差,比如 1..9为:[1 9 2 8 3 7 4 6 5]
  • 其相邻元素差的绝对值为:diff: 8 7 6 5 4 3 2 1
  • 观察构造的数据,其是大小交替的。那么这样的话,我们只要先构造出前k个,后面按照顺序来产生1就可以了。
  • 在后面顺序添加的时候注意,需要添加的是增序还是逆序。
  • 如:以1..7为例:
  • k=6:1 7 2 6 3 5 | 4
  • k=5:1 7 2 6 3 | 4 5
  • k=4:1 7 2 6 | 5 4 3
  • k=3:1 7 2 | 3 4 5 6
  • k=2:1 7 | 6 5 4 3 2
  • k=1:1 | 2 3 4 5 6 7
class Solution {
    public int[] constructArray(int n, int k) {
        int[] ans = new int[n];
        int l = 1, r = n;
        int i = 0;
        for (; i < k; i++){
            if (i % 2 == 0) ans[i] = l++;
            else ans[i] = r--;
        }

        if(i % 2 == 1){
            for (int j = l; j <= r; j++) ans[i++] = j;
        }else{
            for (int j = r; j >= l; j--) ans[i++] = j;
        }

        return ans;
    }
}
  • 时间复杂度:O(n),空间复杂度:O(n)
作者:Koala_Tree 发表于2017/11/9 21:02:55 原文链接
阅读:0 评论:0 查看评论

Angular 4入门教程系列:8:Tour Of Heroes之前后端服务

$
0
0

这里写图片描述
这篇文章我们将会重点学习一下Angular的HttpModule和In-Memory Web API的使用方法。

学习时间

大概5-10分钟。

事前准备

需要事前安装模块angular-in-memory-web-api才能保证此部分学习能够正常进行。因为这个系列中我们使用的是node的官方镜像,这个过程中我们使用了yarn进行处理包的依赖,所以接下来的部分我们将继续使用yarn进行安装。

方式1:

yarn add angular-in-memory-web-api
这样将会自动进行安装并把信息保存到package.json中

/workspace/HelloAngular # yarn add angular-in-memory-web-api
yarn add v1.2.0
[1/4] Resolving packages...
[2/4] Fetching packages...
info fsevents@1.1.2: The platform "linux" is incompatible with this module.
info "fsevents@1.1.2" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved 1 new dependency.
└─ angular-in-memory-web-api@0.3.1
Done in 51.71s.
/workspace/HelloAngular #

方式2:

修改package.json,然后使用yarn install

方式3:

使用npm install方式的开发者可以使用npm install angular-in-memory-web-api,并根据情况决定是否-g安装

注意:使用官方教程的时候angular-in-memory-web-api的最新版本0.5.1似乎有问题,使用0.3.1没有任何问题。没有细究具体原因。不然有可能因为其无法正常动作导致数据取不到,最终页面提示没有slice属性,其原因是因为没有取到数据而已。

InMemoryDataService

到目前为止,我们使用的是一个Hero的全局数组来模拟数据,接下来我们使用InMemoryDbService来进行模拟,所做的内容也非常类似,我们在createDb中创建一个数组,而这些数组保存的普通Json数据的格式,而非直接的对象。

/workspace/HelloAngular/src/app # cat in-memory-data.service.ts
import { InMemoryDbService } from 'angular-in-memory-web-api';
export class InMemoryDataService implements InMemoryDbService {
  createDb() {
    const heroes = [
      { id: 0,  name: 'Zero' },
      { id: 11, name: 'Mr. Nice' },
      { id: 12, name: 'Narco' },
      { id: 13, name: 'Bombasto' },
      { id: 14, name: 'Celeritas' },
      { id: 15, name: 'Magneta' },
      { id: 16, name: 'RubberMan' },
      { id: 17, name: 'Dynama' },
      { id: 18, name: 'Dr IQ' },
      { id: 19, name: 'Magma' },
      { id: 20, name: 'Tornado' }
    ];
    return {heroes};
  }
}
/workspace/HelloAngular/src/app #

使用方法:
注意此处的使用方式,在getHeroes中使用http模块的功能,虽然是模拟,但是跟实际的前后端开发,通过接口取到后端提供的json数据的实际方式,同前面的例子相比已经发生了天渊之别。

/workspace/HelloAngular/src/app # cat hero.service.ts
import { Injectable } from '@angular/core';
import { Http       } from '@angular/http';

import 'rxjs/add/operator/toPromise';
import { Hero } from './hero';

@Injectable()
export class HeroService {
  private heroesUrl = 'api/heroes';

  constructor(private http: Http) {}

  getHeroes(): Promise<Hero[]> {
    return this.http.get(this.heroesUrl)
             .toPromise()
             .then(response => response.json().data as Hero[])
             .catch(this.handleError);
  }

  private handleError(error: any): Promise<any> {
    console.error('An error occurred', error); // for demo purposes only
    return Promise.reject(error.message || error);
  }

  getHero(id: number): Promise<Hero> {
    return this.getHeroes()
             .then(heroes => heroes.find(hero => hero.id === id));
  }
}
/workspace/HelloAngular/src/app # 

引入根模块

/workspace/HelloAngular/src/app # cat app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule }  from '@angular/http';
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService }  from './in-memory-data.service';

import { AppComponent } from './app.component';
import { HeroDetailComponent } from './hero-detail.component'
import { HeroService } from './hero.service';
import { HeroesComponent } from './heroes.component';
import { DashboardComponent } from './dashboard.component';
import { AppRoutingModule } from './app-routing.module';


@NgModule({
  declarations: [
    AppComponent,
    HeroDetailComponent,
    HeroesComponent,
    DashboardComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    InMemoryWebApiModule.forRoot(InMemoryDataService),
    AppRoutingModule
  ],
  providers: [HeroService],
  bootstrap: [AppComponent]
})

export class AppModule { }
/workspace/HelloAngular/src/app # 

Http get

实际上我们使用HTTP的get来取到并显示信息,具体页面信息如下:
这里写图片描述

Http put

现在的页面修改了信息之后,如果按back的按钮则不能像像之前那样能够得到保存,因为之前保存在全局数组里面,自然可以。而是用http的put方法则可以实现保存的功能,简单来说,需要做如下几件事情:

  • * 在hero-detail的模板中添加一个保存的按钮 *
  • * 在添加的按钮中调用 hero的service的update方法 *
  • * 在update方法中使用http模块的put进行信息的保存 *

hero-detail.component.ts

/workspace/HelloAngular/src/app # cat hero-detail.component.ts
import { Component, Input } from '@angular/core';
import { OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Location } from '@angular/common';

import 'rxjs/add/operator/switchMap';

import { Hero } from './hero';
import { HeroService } from './hero.service';
@Component({
  selector: 'hero-detail',
  template: `
    <div *ngIf="hero">
      <h2>{{hero.name}} details!</h2>
      <div><label>id: </label>{{hero.id}}</div>
      <div>
        <label>name: </label>
        <input [(ngModel)]="hero.name" placeholder="name"/>
      </div>
      <button (click)="goBack()">Back</button>
      <button (click)="save()">Save</button>
    </div>
  `
})
export class HeroDetailComponent implements OnInit {
  @Input() hero: Hero;

  constructor(
    private heroService: HeroService,
    private route: ActivatedRoute,
    private location: Location
  ) {
  }

  ngOnInit(): void {
    this.route.paramMap
      .switchMap((params: ParamMap) => this.heroService.getHero(+params.get('id')))
      .subscribe(hero => this.hero = hero);
  }

  goBack(): void {
    this.location.back();
  }

  save(): void {
    this.heroService.update(this.hero)
      .then(() => this.goBack());
  }
}
/workspace/HelloAngular/src/app #

hero.service.ts

/workspace/HelloAngular/src/app # cat hero.service.ts
import { Injectable } from '@angular/core';
import { Http       } from '@angular/http';
import { Headers    } from '@angular/http';

import 'rxjs/add/operator/toPromise';
import { Hero } from './hero';

@Injectable()
export class HeroService {
  private heroesUrl = 'api/heroes';
  private headers = new Headers({'Content-Type': 'application/json'});

  constructor(private http: Http) {}

  getHeroes(): Promise<Hero[]> {
    return this.http.get(this.heroesUrl)
             .toPromise()
             .then(response => response.json().data as Hero[])
             .catch(this.handleError);
  }

  private handleError(error: any): Promise<any> {
    console.error('An error occurred', error); // for demo purposes only
    return Promise.reject(error.message || error);
  }

  getHero(id: number): Promise<Hero> {
    return this.getHeroes()
             .then(heroes => heroes.find(hero => hero.id === id));
  }

  update(hero: Hero): Promise<Hero> {
    const url = `${this.heroesUrl}/${hero.id}`;
    return this.http
      .put(url, JSON.stringify(hero), {headers: this.headers})
      .toPromise()
      .then(() => hero)
      .catch(this.handleError);
  }
}
/workspace/HelloAngular/src/app #

结果确认

修改英雄信息:
这里写图片描述
点击save按钮后,同样是goBack,但是信息被保存了下来
这里写图片描述

Http post

使用http post以便进行添加Hero,需要做如下几件事情:

  • * 在hero模板中添加用于添加hero的按钮 *
  • * 在添加的按钮中调用 hero的service的create方法 *
  • * 在create方法中使用http模块的post进行信息的添加 *

heroes.component.html

/workspace/HelloAngular/src/app # cat heroes.component.html
  <h1>{{title}}</h1>
  <div>
    <label>Hero name:</label> <input #heroName />
    <button (click)="add(heroName.value); heroName.value=''">
      Add
    </button>
  </div>

  <h2>My Heroes</h2>
  <ul class="heroes">
    <li *ngFor="let hero of heroes"  [class.selected]="hero === selectedHero" (click)="onSelect(hero)">
       <span class="badge">{{hero.id}}</span> {{hero.name}}
    </li> 
  </ul>

  <div *ngIf="selectedHero">
    <h2>
      {{selectedHero.name | uppercase}} is my hero
    </h2>
    <button (click)="gotoDetail()">View Details</button>
  </div>


/workspace/HelloAngular/src/app #

heroes.component.ts

/workspace/HelloAngular/src/app # cat heroes.component.ts
import { Component } from '@angular/core';
import { OnInit    } from '@angular/core';
import { Router    } from '@angular/router';

import { Hero } from './hero';
import { HeroService } from './hero.service';

@Component({
  selector: 'my-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css'],
  providers: []
})
export class HeroesComponent implements OnInit {
  title = 'Tour of Heroes';
  selectedHero: Hero;
  heroes: Hero[];

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }

  ngOnInit(): void{
    this.heroService.getHeroes().then(heroes => this.heroes = heroes); 
  }

  constructor(
    private router: Router,
    private heroService: HeroService) {
  }

  gotoDetail(): void {
    this.router.navigate(['/detail', this.selectedHero.id]);
  }

  add(name: string): void {
    name = name.trim();
    if (!name) { return; }
    this.heroService.create(name)
      .then(hero => {
        this.heroes.push(hero);
        this.selectedHero = null;
      });
  }
}
/workspace/HelloAngular/src/app #

hero.service.ts

/workspace/HelloAngular/src/app # cat hero.service.ts
import { Injectable } from '@angular/core';
import { Http       } from '@angular/http';
import { Headers    } from '@angular/http';

import 'rxjs/add/operator/toPromise';
import { Hero } from './hero';

@Injectable()
export class HeroService {
  private heroesUrl = 'api/heroes';
  private headers = new Headers({'Content-Type': 'application/json'});

  constructor(private http: Http) {}

  getHeroes(): Promise<Hero[]> {
    return this.http.get(this.heroesUrl)
             .toPromise()
             .then(response => response.json().data as Hero[])
             .catch(this.handleError);
  }

  private handleError(error: any): Promise<any> {
    console.error('An error occurred', error); // for demo purposes only
    return Promise.reject(error.message || error);
  }

  getHero(id: number): Promise<Hero> {
    return this.getHeroes()
             .then(heroes => heroes.find(hero => hero.id === id));
  }

  update(hero: Hero): Promise<Hero> {
    const url = `${this.heroesUrl}/${hero.id}`;
    return this.http
      .put(url, JSON.stringify(hero), {headers: this.headers})
      .toPromise()
      .then(() => hero)
      .catch(this.handleError);
  }

  create(name: string): Promise<Hero> {
    return this.http
      .post(this.heroesUrl, JSON.stringify({name: name}), {headers: this.headers})
      .toPromise()
      .then(res => res.json().data as Hero)
      .catch(this.handleError);
  }
}
/workspace/HelloAngular/src/app # 

结果确认

用新加的add按钮和输入框添加两个英雄New Hero1和New Hero2
这里写图片描述
可以确认已经实时的添加到列表中了
这里写图片描述

Http delete

使用http delete以便进行删除Hero,需要做如下几件事情:

  • * 在heroes模板中添加用于删除hero的按钮 *
  • * 在添加的按钮中调用 hero的service的delete方法 *
  • * 在delete方法中使用http模块的delete进行信息的删除*

heroes.component.html

/workspace/HelloAngular/src/app # cat heroes.component.html
  <h1>{{title}}</h1>
  <div>
    <label>Hero name:</label> <input #heroName />
    <button (click)="add(heroName.value); heroName.value=''">
      Add
    </button>
  </div>

  <h2>My Heroes</h2>
  <ul class="heroes">
    <li *ngFor="let hero of heroes"  [class.selected]="hero === selectedHero" (click)="onSelect(hero)">
       <span class="badge">{{hero.id}}</span> {{hero.name}}
       <button class="delete"
               (click)="delete(hero); $event.stopPropagation()">x</button>
    </li> 
  </ul>

  <div *ngIf="selectedHero">
    <h2>
      {{selectedHero.name | uppercase}} is my hero
    </h2>
    <button (click)="gotoDetail()">View Details</button>
  </div>


/workspace/HelloAngular/src/app #

heroes.component.ts

/workspace/HelloAngular/src/app # cat heroes.component.ts
import { Component } from '@angular/core';
import { OnInit    } from '@angular/core';
import { Router    } from '@angular/router';

import { Hero } from './hero';
import { HeroService } from './hero.service';

@Component({
  selector: 'my-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css'],
  providers: []
})
export class HeroesComponent implements OnInit {
  title = 'Tour of Heroes';
  selectedHero: Hero;
  heroes: Hero[];

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }

  ngOnInit(): void{
    this.heroService.getHeroes().then(heroes => this.heroes = heroes); 
  }

  constructor(
    private router: Router,
    private heroService: HeroService) {
  }

  gotoDetail(): void {
    this.router.navigate(['/detail', this.selectedHero.id]);
  }

  add(name: string): void {
    name = name.trim();
    if (!name) { return; }
    this.heroService.create(name)
      .then(hero => {
        this.heroes.push(hero);
        this.selectedHero = null;
      });
  }

  delete(hero: Hero): void {
    this.heroService
        .delete(hero.id)
        .then(() => {
          this.heroes = this.heroes.filter(h => h !== hero);
          if (this.selectedHero === hero) { this.selectedHero = null; }
        });
  }
}
/workspace/HelloAngular/src/app #

hero.service.ts

/workspace/HelloAngular/src/app # cat hero.service.ts
import { Injectable } from '@angular/core';
import { Http       } from '@angular/http';
import { Headers    } from '@angular/http';

import 'rxjs/add/operator/toPromise';
import { Hero } from './hero';

@Injectable()
export class HeroService {
  private heroesUrl = 'api/heroes';
  private headers = new Headers({'Content-Type': 'application/json'});

  constructor(private http: Http) {}

  getHeroes(): Promise<Hero[]> {
    return this.http.get(this.heroesUrl)
             .toPromise()
             .then(response => response.json().data as Hero[])
             .catch(this.handleError);
  }

  private handleError(error: any): Promise<any> {
    console.error('An error occurred', error); // for demo purposes only
    return Promise.reject(error.message || error);
  }

  getHero(id: number): Promise<Hero> {
    return this.getHeroes()
             .then(heroes => heroes.find(hero => hero.id === id));
  }

  update(hero: Hero): Promise<Hero> {
    const url = `${this.heroesUrl}/${hero.id}`;
    return this.http
      .put(url, JSON.stringify(hero), {headers: this.headers})
      .toPromise()
      .then(() => hero)
      .catch(this.handleError);
  }

  create(name: string): Promise<Hero> {
    return this.http
      .post(this.heroesUrl, JSON.stringify({name: name}), {headers: this.headers})
      .toPromise()
      .then(res => res.json().data as Hero)
      .catch(this.handleError);
  }

  delete(id: number): Promise<void> {
    const url = `${this.heroesUrl}/${id}`;
    return this.http.delete(url, {headers: this.headers})
      .toPromise()
      .then(() => null)
      .catch(this.handleError);
  }
}
/workspace/HelloAngular/src/app #

结果确认

Hero列表的显示页面中,每个英雄都有一个可删除的按钮
这里写图片描述
删除直到只剩4个
这里写图片描述
现在按钮对的不齐,修改CSS让它们对准一点,添加如下代码到heroes.component.css中

button.delete {
  float:right;
  margin-top: 2px;
  margin-right: .8em;
  background-color: gray !important;
  color:white;
}

结果最终显示为:
这里写图片描述

总结

通过学习使用angular-in-memory-web-api,可以学习到如何做一个模拟的后端,在实际的项目中完全可以模拟后端无法进行联调测试的情况,具有很好的实际意义。

作者:liumiaocn 发表于2017/11/9 21:19:47 原文链接
阅读:0 评论:0 查看评论

从零开始前端学习[47]:如何使用javascript来操作行内样式或者属性浅析

$
0
0

如何使用javascript来操作行内样式或者属性

  • 使用javascript来操作行内样式,或者标签固有属性

提示
博主:章飞_906285288
博客地址:http://blog.csdn.net/qq_29924041


使用javascript来操作行内样式,或者标签固有属性

我们知道,html是结构层,css是样式层,而js则是行为层,一个页面的样式控制主要是css来进行控制的,而在css中样式的引入又包括了外部引用,内部引用,或者行内样式等,但是这些样式都是通过样式属性来进行的。
内部样式或者行内样式都是写在style标签内的,而一般对于js所操纵的样式,一般情况下暂且可以理解为行内样式,那什么是行内样式呢???行内样式是写在左标签内部的样式,通过style来引入,

如下所示:
  <p class="fl_l" id="innerHTML_2" style="font-size: 20px;color: red;background: blue;text-align: center;">innerHTML_2</p>

并且行内样式也是最高的。

那怎么进行操作的呢?如下所示语法:

    语法:对象.style.css属性 = ""; 如果有“-”就要用驼峰命名(把减号去掉,减号后面的第一个字母大写,如backgroundColor)
    如:
    var box = document.getElementById("box");
    box.onclick = function(){
        box.style.backgroundColor = '#999';
    }

注意在js中引用的时候是不存在有中间的”-“的,同样的,其属性值与原来基本上也是一致的,并没有太大区别。

注意其在引用的时候是需要先引入style的。

关于通过js浮动的一点简单介绍:
在我们的前端里面我们都知道兼容很恼火,这里的浮动就有一个兼容问题,首先,在js里面我们不能直接这样子写

oBox.style.flaot = "right" 错误的写法

因为flaot是关键字,虽然在谷歌里面这样写也可以这样子写,但是,我们也不能这样子写,比如c++里面是代表浮点型,所以我们用其他的代替,还有我们的class也是保留字要用className代替

所以在google里面的话:

oBox.style.cssFloat = 'right';

而在低版本的浏览器中的 话:

oBox.style.styleFloat = 'right';

最重要的还有父级元素坍塌问题的思考,因为在你设置了浮动之后,如果不去清除浮动,势必会造成父级元素的坍塌的现象,这个时候你必须要清除浮动,

parent.setAttribute("class",'clearfix');  /*设置类属性来消除掉父级坍塌事件**/

具体会通过代码的形式来展示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <meta charset="UTF-8"><!--申明当前网页的编码集UTF-8-->
  <meta name="Generator" content="EditPlus®">   <!--编辑器的名称-->
  <meta name="Author" content="作者是谁">       
  <meta name="Keywords" content="关键词">
  <meta name="Description" content="描述和简介">
  <style type="text/css">                                        
        body,dl,dd,dt,p,h1,h2,h3,h4,h5,h6{ margin: 0;}
        ul,ol{margin: 0; list-style: none; padding: 0;}
        a{ text-decoration: none; }
        *{ margin: 0; padding: 0; }
        .main{width: 800px;margin: 40px auto;box-shadow: 0 0 10px 0 deeppink}
        p{width: 200px;height: 200px;box-shadow: 0 0 10px 0 blue;margin: 10px}
        .clearfix:after{
          display: block;
          content:"";
          clear: both;
        }
  </style>
</head>
<body>
<div class="main">
  <div class="compare1" id="parent">
    <p  id="innerHTML_1">1</p>
    <p  id="innerHTML_2">2</p>
  </div>
</div>
<script>
  var parent = document.getElementById("parent");
  var p1 = document.getElementById("innerHTML_1");
  var p2 = document.getElementById("innerHTML_2");

  p1.onmouseover = function () {
      p1.style.backgroundColor = 'blue';
      p1.style.color = "red";
  }
  p1.onmouseout = function () {
      p1.style.backgroundColor = 'deeppink';
      p1.style.color = "#334499";
  }
  p2.onmouseover = function () {
      p2.style.fontSize = "26px";
      p2.style.textAlign = "center";
  }
  p2.onmouseout = function () {
      p2.style.textAlign = "right";
  }

  parent.onclick= function () {
      parent.style.color = "red";  /*修改字体颜色属性**/
      p1.style.cssFloat = "left";  /*google浏览器让其进行浮动*/
      p1.style.styleFloat = "left"; /*低版本的情况下,浮动*/
      p2.style.cssFloat = "left";
      p2.style.styleFloat = "left";
      parent.setAttribute("class",'clearfix');  /*设置类属性来消除掉父级坍塌事件**/
      console.log("======");
  }
</script>
</body>
</html>

显示如下所示:
这里写图片描述

注意修改行内样式的时候,需要用到.style.cssXXX,浮动相关的样式修改的话,这个时候是有区别的还,再就是浮动的时候需要清除浮动

作者:qq_29924041 发表于2017/11/9 21:28:21 原文链接
阅读:0 评论:0 查看评论

BZOJ3450 Tyvj1952 Easy

$
0
0

标签:期望,数学,递推

Description

某一天WJMZBMR在打osu~~~但是他太弱逼了,有些地方完全靠运气:(
我们来简化一下这个游戏的规则
有n次点击要做,成功了就是o,失败了就是x,分数是按comb计算的,连续a个comb就有a*a分,comb就是极大的连续o。
比如ooxxxxooooxxx,分数就是2*2+4*4=4+16=20。
Sevenkplus闲的慌就看他打了一盘,有些地方跟运气无关要么是o要么是x,有些地方o或者x各有50%的可能性,用?号来表示。
比如oo?xx就是一个可能的输入。
那么WJMZBMR这场osu的期望得分是多少呢?
比如oo?xx的话,?是o的话就是oooxx => 9,是x的话就是ooxxx => 4
期望自然就是(4+9)/2 =6.5了

Input


第一行一个整数n,表示点击的个数
接下来一个字符串,每个字符都是ox?中的一个

Output

一行一个浮点数表示答案
四舍五入到小数点后4位
如果害怕精度跪建议用long double或者extended

Sample Input

4
????

Sample Output

4.1250


n<=300000
osu很好玩的哦
WJMZBMR技术还行(雾),x基本上很少呢

 

分析:

F[i]表示前i项的期望值,g[i]表示当前一段区间内连续一段‘o’的期望长度

然后对于每个字符更新f[i]和g[i]

Code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define dep(i,a,b) for(int i=a;i>=b;i--)
#define ll long long
#define mem(x,num) memset(x,num,sizeof x)
#ifdef WIN32
#define LL "%I64d"
#else
#define LL "%lld"
#endif
using namespace std;
inline ll read()
{
    ll f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
const int maxn=3e5+6;
int n;
double g[maxn],f[maxn];
char ch;
int main()
{
	n=read();
	rep(i,1,n){
		ch=getchar();
		if(ch=='o'){g[i]=g[i-1]+1;f[i]=f[i-1]+2*g[i-1]+1;}
		if(ch=='x'){g[i]=0;f[i]=f[i-1];}
		if(ch=='?'){g[i]=g[i-1]/2+0.5;f[i]=f[i-1]+g[i-1]+0.5;}
	}
	printf("%.4lf\n",f[n]);
	return 0;
}
		


作者:qwerty1125 发表于2017/11/9 21:48:09 原文链接
阅读:0 评论:0 查看评论

洛谷P2023 [AHOI2009]维护序列 (BZOJ1798)

$
0
0

线段树

洛谷题目传送门
BZOJ题目传送门

裸的双Tag线段树。。。(不过我都没打过双Tag的线段树)
BZOJMLE说我TLE。。。害我郁闷了半天。。。
注意BZOJ只给64MB
还有就是一定要先乘后加
具体见注释

代码:

#include<cctype>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 150000
using namespace std;
typedef long long LL;//开LL保险一点
struct tree{
    LL l,r;
    LL sum;
}t[MAXN*4+5];
LL n,m,MOD;
LL a[MAXN+5],lazy1[MAXN*4+5],lazy2[MAXN*4+5];//1表示加,2表示乘
inline char readc(){//fread读优
    static char buf[100000],*l=buf,*r=buf;
    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
    if (l==r) return EOF; return *l++;
}
inline LL _read(){
    LL num=0; char ch=readc();
    while (!isdigit(ch)) ch=readc();
    while (isdigit(ch)) { num=num*10+ch-48; ch=readc(); }
    return num;
}
void build(LL l,LL r,LL num){//建树
    t[num].l=l,t[num].r=r;
    if (l==r){//给叶子结点赋值
        t[num].sum=a[l];
        return;
    }
    build(l,(l+r)/2,num*2);
    build((l+r)/2+1,r,num*2+1);
    t[num].sum=(t[num*2].sum+t[num*2+1].sum)%MOD;
}
void pushdown(LL num){//传Tag
    if (lazy2[num]!=1){//先算乘法
        (lazy2[num*2]*=lazy2[num])%=MOD;
        (lazy2[num*2+1]*=lazy2[num])%=MOD;
        t[num*2].sum=(t[num*2].sum*lazy2[num])%MOD;
        t[num*2+1].sum=(t[num*2+1].sum*lazy2[num])%MOD;
        (lazy1[num*2]*=lazy2[num])%=MOD;
        (lazy1[num*2+1]*=lazy2[num])%=MOD;
        lazy2[num]=1;//1
    }
    if (lazy1[num]){//再算加法
        (lazy1[num*2]+=lazy1[num])%=MOD;
        (lazy1[num*2+1]+=lazy1[num])%=MOD;
        (t[num*2].sum+=((t[num*2].r-t[num*2].l+1)*lazy1[num])%MOD)%=MOD;
        (t[num*2+1].sum+=((t[num*2+1].r-t[num*2+1].l+1)*lazy1[num])%MOD)%=MOD;
        lazy1[num]=0;//0
    }
}
void nsrt(LL l,LL r,LL w,bool flag,LL num){//插入(修改)
    if (t[num].l>=l&&t[num].r<=r){
        if (!flag){//如果是乘操作
            t[num].sum=(t[num].sum*w)%MOD;
            lazy2[num]=(lazy2[num]*w)%MOD;
            lazy1[num]=(lazy1[num]*w)%MOD;//别忘了给加Tag乘
        }
        else{
            t[num].sum=(t[num].sum+((t[num].r-t[num].l+1)*w)%MOD)%MOD;
            lazy1[num]=(lazy1[num]+w)%MOD;
        }
        return;
    }
    if (t[num].l>r||t[num].r<l) return;
    if (lazy1[num]||lazy2[num]!=1)
        pushdown(num);
    nsrt(l,r,w,flag,num*2);
    nsrt(l,r,w,flag,num*2+1);
    t[num].sum=(t[num*2].sum+t[num*2+1].sum)%MOD;
}
LL srch(LL l,LL r,LL num){
    if (t[num].l>=l&&t[num].r<=r) 
        return t[num].sum;
    if (t[num].r<l||t[num].l>r) 
        return 0;
    if (lazy1[num]||lazy2[num]!=1)
        pushdown(num);
    LL x=srch(l,r,num*2);
    LL y=srch(l,r,num*2+1);
    return (x+y)%MOD;
}
int main(){
    n=_read(),MOD=_read();
    for (LL i=1;i<=n;i++)
        a[i]=(_read())%MOD;
    m=_read();
    for (LL i=1;i<=MAXN*4;i++)
        lazy2[i]=1;
    build(1,n,1);
    while (m--){
        LL flag=_read();
        switch (flag){
            case 1:{
                LL l=_read(),r=_read(),w=_read();
                nsrt(l,r,w,0,1);
                break;
            }
            case 2:{
                LL l=_read(),r=_read(),w=_read();
                nsrt(l,r,w,1,1);
                break;
            }
            default:{
                LL l=_read(),r=_read();
                printf("%d\n",srch(l,r,1));
                break;
            }
        }
    }
    return 0;
}
作者:a1799342217 发表于2017/11/9 13:51:20 原文链接
阅读:191 评论:0 查看评论

Chromium分发输入事件给WebKit处理的过程分析

$
0
0

       Chromium的Render进程接收到Browser进程分发过来的输入事件之后,会在Compoistor线程中处理掉滑动和捏合手势这两种特殊的输入事件,其它类型的输入事件则交给Main线程处理。Main线程又会进一步将输入事件分发给WebKit处理。WebKit则根据输入事件发生的位置在网页中找到对应的HTML元素进行处理。本文接下来详细分析Chromium分发输入事件给WebKit处理的过程。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

       以Touch事件为例。WebKit接收到Chromium分发给它的Touch事件通知后,所做的第一件事情是做Hit Test,也就是检测Touch事件发生在哪一个HTML元素上,然后再将接收到的Touch事件分发给该它处理,如图1所示:


图1 WebKit处理Touch事件的过程

       HTML元素对应于网页DOM Tree中的Node。要从DOM Tree中找到输入事件的目标Node,必须要知道所有Node的Z轴位置大小,然后按照从上到下的顺序进行Hit Test。从前面Chromium网页Graphics Layer Tree创建过程分析一文可以知道,DOM Tree中的Node在Z轴上的实际位置,除了跟它们本身的Z-Index属性值有关之外,还与它们所处理的Stacking Context的Z-Index值有关。网页的Render Layer Tree记录了所有的Stacking Context,因此,WebKit要从网页的Render Layer Tree入手,才能在DOM Tree中找到正确的目标Node处理当前发生的输入事件。

       接下来,我们就从Compoistor线程将输入事件分发给Main线程开始,分析WebKit接收和处理输入事件的过程。从前面Chromium网页滑动和捏合手势处理过程分析一文可以知道,Compoistor线程是在InputEventFilter类的成员函数ForwardToHandler中将它不处理的输入事件发分给Main线程处理的,如下所示:

void InputEventFilter::ForwardToHandler(const IPC::Message& message) {  
  ......  
  
  int routing_id = message.routing_id();  
  InputMsg_HandleInputEvent::Param params;  
  if (!InputMsg_HandleInputEvent::Read(&message, &params))  
    return;  
  const WebInputEvent* event = params.a;  
  ui::LatencyInfo latency_info = params.b;  
  .......  
  
  InputEventAckState ack_state = handler_.Run(routing_id, event, &latency_info);  
  
  if (ack_state == INPUT_EVENT_ACK_STATE_NOT_CONSUMED) {  
    ......  
    IPC::Message new_msg = InputMsg_HandleInputEvent(  
        routing_id, event, latency_info, is_keyboard_shortcut);  
    main_loop_->PostTask(  
        FROM_HERE,  
        base::Bind(&InputEventFilter::ForwardToMainListener,  
                   this, new_msg));  
    return;  
  }  
  
  ......   
}
       这个函数定义在文件external/chromium_org/content/renderer/input/input_event_filter.cc中。

       InputEventFilter类的成员函数ForwardToHandler的详细分析可以参考前面Chromium网页滑动和捏合手势处理过程分析一文。对于Compositor线程不处理的输入事件,InputEventFilter类的成员函数ForwardToHandler会将它重新封装在一个InputMsg_HandleInputEvent消息中。这个InputMsg_HandleInputEvent消息又会进一步封装在一个Task中,并且发送给Main线程的消息队列。这个Task绑定了InputEventFilter类的成员函数ForwardToMainListener。

       这意味着接下来InputEventFilter类的成员函数ForwardToMainListener就会在Main线程中被调用,用来处理Compositor线程分发过来的输入事件,如下所示:

void InputEventFilter::ForwardToMainListener(const IPC::Message& message) {
  main_listener_->OnMessageReceived(message);
}
       这个函数定义在文件external/chromium_org/content/renderer/input/input_event_filter.cc中。

       从前面Chromium网页滑动和捏合手势处理过程分析一文可以知道,InputEventFilter类的成员变量main_listener_指向的是一个RenderThreadImpl对象。这个RenderThreadImpl对象描述的就是Render进程中的Render Thread,也就是Main Thread。InputEventFilter类的成员函数ForwardToMainListener调用这个RenderThreadImpl对象的成员函数OnMessageReceived处理参数message描述的输入事件。

       RenderThreadImpl类的成员函数OnMessageReceived是从父类ChildThread继承下来的,它的实现如下所示:

bool ChildThread::OnMessageReceived(const IPC::Message& msg) {
  .....

  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(ChildThread, msg)
    ......
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()

  if (handled)
    return true;

  ......

  return router_.OnMessageReceived(msg);
}
       这个函数定义在文件external/chromium_org/content/child/child_thread.cc 中。

       ChildThread类的成员函数OnMessageReceived首先检查参数msg描述的消息是否要由自己处理。如果不处理,那么就再分发给注册在它里面的Route进行处理。

       从前面Chromium网页Frame Tree创建过程分析一文可以知道,Render进程会为每一个网页创建一个RenderViewImpl对象,用来代表网页所加载在的控件。这个RenderViewImpl对象在初始化的过程中,会通过父类RenderWidget的成员函数DoInit将自己注册为Render Thread中的一个Route,如下所示:

bool RenderWidget::DoInit(int32 opener_id,
                          WebWidget* web_widget,
                          IPC::SyncMessage* create_widget_message) {
  ......

  bool result = RenderThread::Get()->Send(create_widget_message);
  if (result) {
    RenderThread::Get()->AddRoute(routing_id_, this);
    .......
    return true;
  } 

  ......
}
      这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。

      这意味着前面分析的RenderThreadImpl类的成员函数OnMessageReceived会将接收到的、自己又不处理的消息分发给RenderWidget类处理。RenderWidget类通过成员函数OnMessageReceived接收RenderThreadImpl类分发过来的消息,并且判断分发过来的消息是否与输入事件有关,也就是判断是否为一个类型为InputMsg_HandleInputEvent的消息。如果是的话,那么就会进行处理。如下所示:

bool RenderWidget::OnMessageReceived(const IPC::Message& message) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(RenderWidget, message)
    IPC_MESSAGE_HANDLER(InputMsg_HandleInputEvent, OnHandleInputEvent)
    ......
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}
       这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。

       RenderWidget类通过成员函数OnMessageReceived会将类型为InputMsg_HandleInputEvent的消息分发给另外一个成员函数OnHandleInputEvent处理,如下所示:

void RenderWidget::OnHandleInputEvent(const blink::WebInputEvent* input_event,
                                      const ui::LatencyInfo& latency_info,
                                      bool is_keyboard_shortcut) {
  ......

  base::TimeTicks start_time;
  if (base::TimeTicks::IsHighResNowFastAndReliable())
    start_time = base::TimeTicks::HighResNow();
  ......

  bool prevent_default = false;
  if (WebInputEvent::isMouseEventType(input_event->type)) {
    const WebMouseEvent& mouse_event =
        *static_cast<const WebMouseEvent*>(input_event);
    ......
    prevent_default = WillHandleMouseEvent(mouse_event);
  }

  if (WebInputEvent::isKeyboardEventType(input_event->type)) {
    ......
#if defined(OS_ANDROID)
    // The DPAD_CENTER key on Android has a dual semantic: (1) in the general
    // case it should behave like a select key (i.e. causing a click if a button
    // is focused). However, if a text field is focused (2), its intended
    // behavior is to just show the IME and don't propagate the key.
    // A typical use case is a web form: the DPAD_CENTER should bring up the IME
    // when clicked on an input text field and cause the form submit if clicked
    // when the submit button is focused, but not vice-versa.
    // The UI layer takes care of translating DPAD_CENTER into a RETURN key,
    // but at this point we have to swallow the event for the scenario (2).
    const WebKeyboardEvent& key_event =
        *static_cast<const WebKeyboardEvent*>(input_event);
    if (key_event.nativeKeyCode == AKEYCODE_DPAD_CENTER &&
        GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE) {
      OnShowImeIfNeeded();
      prevent_default = true;
    }
#endif
  }

  if (WebInputEvent::isGestureEventType(input_event->type)) {
    const WebGestureEvent& gesture_event =
        *static_cast<const WebGestureEvent*>(input_event);
    ......
    prevent_default = prevent_default || WillHandleGestureEvent(gesture_event);
  }

  bool processed = prevent_default;
  if (input_event->type != WebInputEvent::Char || !suppress_next_char_events_) {
    suppress_next_char_events_ = false;
    if (!processed && webwidget_)
      processed = webwidget_->handleInputEvent(*input_event);
  }

  // If this RawKeyDown event corresponds to a browser keyboard shortcut and
  // it's not processed by webkit, then we need to suppress the upcoming Char
  // events.
  if (!processed && is_keyboard_shortcut)
    suppress_next_char_events_ = true;

  InputEventAckState ack_result = processed ?
      INPUT_EVENT_ACK_STATE_CONSUMED : INPUT_EVENT_ACK_STATE_NOT_CONSUMED;  
  ......

  // dispatch compositor-handled scroll gestures.
  bool event_type_can_be_rate_limited =
      input_event->type == WebInputEvent::MouseMove ||
      input_event->type == WebInputEvent::MouseWheel ||
      (input_event->type == WebInputEvent::TouchMove &&
       ack_result == INPUT_EVENT_ACK_STATE_CONSUMED);

  bool frame_pending = compositor_ && compositor_->BeginMainFrameRequested();

  // If we don't have a fast and accurate HighResNow, we assume the input
  // handlers are heavy and rate limit them.
  bool rate_limiting_wanted = true;
  if (base::TimeTicks::IsHighResNowFastAndReliable()) {
      base::TimeTicks end_time = base::TimeTicks::HighResNow();
      total_input_handling_time_this_frame_ += (end_time - start_time);
      rate_limiting_wanted =
          total_input_handling_time_this_frame_.InMicroseconds() >
          kInputHandlingTimeThrottlingThresholdMicroseconds;
  }

  // Note that we can't use handling_event_type_ here since it will be overriden
  // by reentrant calls for events after the paused one.
  bool no_ack = ignore_ack_for_mouse_move_from_debugger_ &&
      input_event->type == WebInputEvent::MouseMove;
  if (!WebInputEventTraits::IgnoresAckDisposition(*input_event) && !no_ack) {
    InputHostMsg_HandleInputEvent_ACK_Params ack;
    ack.type = input_event->type;
    ack.state = ack_result;
    ack.latency = swap_latency_info;
    scoped_ptr<IPC::Message> response(
        new InputHostMsg_HandleInputEvent_ACK(routing_id_, ack));
    if (rate_limiting_wanted && event_type_can_be_rate_limited &&
        frame_pending && !is_hidden_) {
      // We want to rate limit the input events in this case, so we'll wait for
      // painting to finish before ACKing this message.
      ......
      if (pending_input_event_ack_) {
        // As two different kinds of events could cause us to postpone an ack
        // we send it now, if we have one pending. The Browser should never
        // send us the same kind of event we are delaying the ack for.
        Send(pending_input_event_ack_.release());
      }
      pending_input_event_ack_ = response.Pass();
      ......
    } else {
      Send(response.release());
    }
  }

  ......
}
       这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。

       RenderWidget类的成员函数OnHandleInputEvent将参数input_event描述的输入事件分发给WebKit之前,会做以下三件事情:

       1. 检查参数input_event描述的输入事件是否是一个鼠标事件。如果是的话,那么就会调用另外一个成员函数WillHandleMouseEvent询问RenderWidget类是否要对它进行处理。如果处理,那么本地变量prevent_default的值就会被设置为true。

       2. 检查参数input_event描述的输入事件是否是一个DPAD键盘事件。如果是的话,并且当前获得焦点的是一个Input Text控件,那么就调用另外一个成员函数OnShowImeIfNeeded弹出输入法,以便用户可以输入文本给Input Text控件去。注意,这个检查是针对Android平台的,因为Android平台才会存在DPAD键。

       3. 检查参数input_event描述的输入事件是否是一个手势操作。如果是的话,那么就会调用另外一个成员函数WillHandleGestureEvent询问RenderWidget类是否要对它进行处理。如果处理,那么本地变量prevent_default的值也会被设置为true。

       RenderWidget类的成员函数WillHandleMouseEvent和WillHandleGestureEvent的返回值均为false,这表明RenderWidget类不会处理鼠标和手势操作这两种输入事件。

       RenderWidget类的成员函数OnHandleInputEvent接下来继续判断参数input_event描述的输入事件是否为键盘字符输入事件。如果不是,并且RenderWidget类不对它进行处理,那么RenderWidget类的成员函数OnHandleInputEvent就会将它分发给WebKit处理。这表明在RenderWidget类不处理的情况下,非键盘字符输入事件一定会分发给WebKit处理。

       另一方面,对于键盘字符输入事件,它也会分发给WebKit处理。不过,分发给WebKit之后,如果WebKit决定不对它进行处理,并且它被设置为浏览器的快捷键,即参数is_keyboard_shortcut的值等于true,那么下一个键盘字符输入事件将不会再分发给WebKit处理。因为下一个键盘字符输入事件将会作为浏览器快捷键的另外一部分。在这种情况下,RenderWidget类的成员变量suppress_next_char_events_的值会被设置为true。

       将参数input_event描述的输入事件分发给WebKit之后,RenderWidget类的成员函数OnHandleInputEvent考虑是否需要给Browser进程发送一个ACK。从前面Chromium网页输入事件捕捉和手势检测过程分析一文可以知道,Browser进程收到Render进程对上一个输入事件的ACK之后,才会给它分发下一个输入事件。因此,Render进程是否给Browser进程发送ACK,可以用来控制Browser进程分发输入事件给Render进程的节奏。

       有三种类型的输入事件是需要控制分发节奏的:鼠标移动、鼠标中键滚动和触摸事件。这三类输入事件都有一个特点,它们会连续大量地输入。每一次输入Render进程都需要对它们作出响应,也就是对网页进行处理。如果它们发生得太过频繁,那么就会造成Render进程的负担很重。因此,对于这三类输入事件,RenderWidget类的成员函数OnHandleInputEvent并不会都马上对它们进行ACK。这里有一点需要注意,对于触摸事件,只有WebKit没有对它进行处理,那么RenderWidget类的成员函数OnHandleInputEvent会马上对它进行ACK。这是因为这个触摸事件和接下来发生的触摸事件,可能会触发手势操作,例如滑动手势和捏合手势。这些手势操作必须要迅速进行处理,否则的话,用户就会觉得浏览器没有反应了。

       什么情况下不会马上进行ACK呢?如果网页的当前帧请求执行了一次Commit操作,也就是请求了重新绘制网页的CC Layer Tree,但是网页的CC Layer Tree还没有绘制完成,并且也没有同步到CC Pending Layer Tree,那么就不会马上进行ACK。这个ACK会被缓存在RenderWidget类的成员变量pending_input_event_ack_中。等到网页的CC Layer Tree重新绘制完成并且同步到CC Pending Layer Tree之后,被缓存的ACK才会发送给Browser进程。之所以要这样做,是因为Main线程在重新绘制网页的CC Layer Tree的时候,任务是相当重的,这时候不宜再分发新的输入事件给它处理。

        此外,如果平台实现了高精度时钟,那么RenderWidget类的成员函数OnHandleInputEvent也不一定要等到网页的CC Layer Tree重新绘制完成并且同步到CC Pending Layer Tree之后,才将缓存的ACK才会发送给Browser进程。如果已经过去了一段时间,网页的CC Layer Tree还没有绘制完成,也没有同步到CC Pending Layer Tree,那么RenderWidget类的成员函数OnHandleInputEvent就会马上给Browser进程发送一个ACK。这个时间被设置为kInputHandlingTimeThrottlingThresholdMicroseconds(4166)微秒,大约等于1/4帧时间(假设一帧时间为16毫秒)。

        还有一种特殊情况,造成RenderWidget类的成员函数OnHandleInputEvent不会缓存输入事件的ACK,那就是网页当前不可见。对于不可见的网页,Main线程不需要对它们进行处理,因为处理了也是白处理(用户看不到)。因此,可以认为此时分发给网页的任何输入都在瞬间完成,于是就可以安全地将它们ACK给Browser进程,不用担心Main线程的负载问题。

       最后,我们还看到,如果浏览器连上了Debugger,并且Debugger希望不要对鼠标移动事件进行ACK,那么鼠标移动事件在任何情况下,都不会进行ACK。Browser进程是通过类型为InputHostMsg_HandleInputEvent的消息向Render进程分发输入事件的。相应地,Render进程通过向Browser进程发送类型为InputHostMsg_HandleInputEvent_ACK的消息对输入事件进行ACK。

       接下来,我们就重点分析Chromium分发输入事件给WebKit处理的过程。从前面Chromium网页Frame Tree创建过程分析一文可以知道, RenderWidget类的成员变量webwidget_指向的是一个WebViewImpl对象。RenderWidget类的成员函数OnHandleInputEvent就是通过调用这个WebViewImpl对象的成员函数handleInputEvent将参数input_event描述的输入事件分发给WebKit处理的。前面我们已经假设这是一个Touch事件。

       WebViewImpl类的成员函数handleInputEvent的实现如下所示:

bool WebViewImpl::handleInputEvent(const WebInputEvent& inputEvent)
{
    ......

    return PageWidgetDelegate::handleInputEvent(m_page.get(), *this, inputEvent);
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebViewImpl.cpp中。

       对于Touch事件,WebViewImpl类的成员函数handleInputEvent将会调用PageWidgetDelegate类的静态成员函数handleInputEvent对它进行处理,如下所示:

bool PageWidgetDelegate::handleInputEvent(Page* page, PageWidgetEventHandler& handler, const WebInputEvent& event)
{
    LocalFrame* frame = page && page->mainFrame()->isLocalFrame() ? page->deprecatedLocalMainFrame() : 0;
    switch (event.type) {
    ......

    case WebInputEvent::TouchStart:
    case WebInputEvent::TouchMove:
    case WebInputEvent::TouchEnd:
    case WebInputEvent::TouchCancel:
        if (!frame || !frame->view())
            return false;
        return handler.handleTouchEvent(*frame, *static_cast<const WebTouchEvent*>(&event));

    ......

    default:
        return false;
    }
}

       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/PageWidgetDelegate.cpp中。

       从前面的调用过程可以知道,参数page是从WebViewImpl类的成员变量m_page传递过来的,它指向的是一个Page对象。PageWidgetDelegate类的静态成员函数handleInputEvent通过这个Page对象可以获得一个LocalFrame对象。这个LocalFrame对象用来在WebKit层描述在当前进程中加载的网页,它的创建过程可以参考前面Chromium网页Frame Tree创建过程分析一文。

       另外一个参数handler描述的是一个WebViewImpl对象。当第三个参数event描述的是一个Touch相关的事件时,PageWidgetDelegate类的静态成员函数handleInputEvent就会调用参数handler描述的WebViewImpl对象的成员函数handleTouchEvent对它进行处理。

       WebViewImpl类的成员函数handleTouchEvent是从父类PageWidgetEventHandler继承下来的,它的实现如下所示:

bool PageWidgetEventHandler::handleTouchEvent(LocalFrame& mainFrame, const WebTouchEvent& event)
{
    return mainFrame.eventHandler().handleTouchEvent(PlatformTouchEventBuilder(mainFrame.view(), event));
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/PageWidgetDelegate.cpp中。

       PageWidgetEventHandler类的成员函数handleTouchEvent首先调用参数mainFrame描述的一个LocalFrame对象的成员函数eventHandler获得一个EventHandler对象,接着再调用这个EventHandler对象的成员函数handleTouchEvent将处参数event描述的Touch事件,如下所示:

bool EventHandler::handleTouchEvent(const PlatformTouchEvent& event)
{
    ......

    const Vector<PlatformTouchPoint>& points = event.touchPoints();  

    unsigned i;
    ......
    bool allTouchReleased = true;
    for (i = 0; i < points.size(); ++i) {
        const PlatformTouchPoint& point = points[i];
        ......
        if (point.state() != PlatformTouchPoint::TouchReleased && point.state() != PlatformTouchPoint::TouchCancelled)
            allTouchReleased = false;
    }

    ......

    // First do hit tests for any new touch points.
    for (i = 0; i < points.size(); ++i) {
        const PlatformTouchPoint& point = points[i];
        ......
        
        if (point.state() == PlatformTouchPoint::TouchPressed) {
            HitTestRequest::HitTestRequestType hitType = HitTestRequest::TouchEvent | HitTestRequest::ReadOnly | HitTestRequest::Active;
            LayoutPoint pagePoint = roundedLayoutPoint(m_frame->view()->windowToContents(point.pos()));
            HitTestResult result;
            if (!m_touchSequenceDocument) {
                result = hitTestResultAtPoint(pagePoint, hitType);
            } else if (m_touchSequenceDocument->frame()) {
                LayoutPoint framePoint = roundedLayoutPoint(m_touchSequenceDocument->frame()->view()->windowToContents(point.pos()));
                result = hitTestResultInFrame(m_touchSequenceDocument->frame(), framePoint, hitType);
            } else
                continue;

            Node* node = result.innerNode();
            ......

            if (!m_touchSequenceDocument) {
                // Keep track of which document should receive all touch events
                // in the active sequence. This must be a single document to
                // ensure we don't leak Nodes between documents.
                m_touchSequenceDocument = &(result.innerNode()->document());
                ......
            }

            ......

            m_targetForTouchID.set(point.id(), node);

            ......
        }
    }

    ......

    // Holds the complete set of touches on the screen.
    RefPtrWillBeRawPtr<TouchList> touches = TouchList::create();

    // A different view on the 'touches' list above, filtered and grouped by
    // event target. Used for the 'targetTouches' list in the JS event.
    typedef WillBeHeapHashMap<EventTarget*, RefPtrWillBeMember<TouchList> > TargetTouchesHeapMap;
    TargetTouchesHeapMap touchesByTarget;

    // Array of touches per state, used to assemble the 'changedTouches' list.
    typedef WillBeHeapHashSet<RefPtrWillBeMember<EventTarget> > EventTargetSet;
    struct {
        // The touches corresponding to the particular change state this struct
        // instance represents.
        RefPtrWillBeMember<TouchList> m_touches;
        // Set of targets involved in m_touches.
        EventTargetSet m_targets;
    } changedTouches[PlatformTouchPoint::TouchStateEnd];

    for (i = 0; i < points.size(); ++i) {
        const PlatformTouchPoint& point = points[i];
        PlatformTouchPoint::State pointState = point.state();
        RefPtrWillBeRawPtr<EventTarget> touchTarget = nullptr;

        if (pointState == PlatformTouchPoint::TouchReleased || pointState == PlatformTouchPoint::TouchCancelled) {
            // The target should be the original target for this touch, so get
            // it from the hashmap. As it's a release or cancel we also remove
            // it from the map.
            touchTarget = m_targetForTouchID.take(point.id());
        } else {
            // No hittest is performed on move or stationary, since the target
            // is not allowed to change anyway.
            touchTarget = m_targetForTouchID.get(point.id());
        }

        LocalFrame* targetFrame = 0;
        bool knownTarget = false;
        if (touchTarget) {
            Document& doc = touchTarget->toNode()->document();
            // If the target node has moved to a new document while it was being touched,
            // we can't send events to the new document because that could leak nodes
            // from one document to another. See http://crbug.com/394339.
            if (&doc == m_touchSequenceDocument.get()) {
                targetFrame = doc.frame();
                knownTarget = true;
            }
        }

        ......

        RefPtrWillBeRawPtr<Touch> touch = Touch::create(
            targetFrame, touchTarget.get(), point.id(), point.screenPos(), adjustedPagePoint, adjustedRadius, point.rotationAngle(), point.force());

        ......

        // Ensure this target's touch list exists, even if it ends up empty, so
        // it can always be passed to TouchEvent::Create below.
        TargetTouchesHeapMap::iterator targetTouchesIterator = touchesByTarget.find(touchTarget.get());
        if (targetTouchesIterator == touchesByTarget.end()) {
            touchesByTarget.set(touchTarget.get(), TouchList::create());
            targetTouchesIterator = touchesByTarget.find(touchTarget.get());
        }

        // touches and targetTouches should only contain information about
        // touches still on the screen, so if this point is released or
        // cancelled it will only appear in the changedTouches list.
        if (pointState != PlatformTouchPoint::TouchReleased && pointState != PlatformTouchPoint::TouchCancelled) {
            touches->append(touch);
            targetTouchesIterator->value->append(touch);
        }

        // Now build up the correct list for changedTouches.
        // Note that  any touches that are in the TouchStationary state (e.g. if
        // the user had several points touched but did not move them all) should
        // never be in the changedTouches list so we do not handle them
        // explicitly here. See https://bugs.webkit.org/show_bug.cgi?id=37609
        // for further discussion about the TouchStationary state.
        if (pointState != PlatformTouchPoint::TouchStationary && knownTarget) {
            ......
            if (!changedTouches[pointState].m_touches)
                changedTouches[pointState].m_touches = TouchList::create();
            changedTouches[pointState].m_touches->append(touch);
            changedTouches[pointState].m_targets.add(touchTarget);
        }
    }
    if (allTouchReleased) {
        m_touchSequenceDocument.clear();
        ......
    }

    ......

    // Now iterate the changedTouches list and m_targets within it, sending
    // events to the targets as required.
    bool swallowedEvent = false;
    for (unsigned state = 0; state != PlatformTouchPoint::TouchStateEnd; ++state) {
        ......

        const AtomicString& stateName(eventNameForTouchPointState(static_cast<PlatformTouchPoint::State>(state)));
        const EventTargetSet& targetsForState = changedTouches[state].m_targets;
        for (EventTargetSet::const_iterator it = targetsForState.begin(); it != targetsForState.end(); ++it) {
            EventTarget* touchEventTarget = it->get();
            RefPtrWillBeRawPtr<TouchEvent> touchEvent = TouchEvent::create(
                touches.get(), touchesByTarget.get(touchEventTarget), changedTouches[state].m_touches.get(),
                stateName, touchEventTarget->toNode()->document().domWindow(),
                event.ctrlKey(), event.altKey(), event.shiftKey(), event.metaKey(), event.cancelable());
            touchEventTarget->toNode()->dispatchTouchEvent(touchEvent.get());
            swallowedEvent = swallowedEvent || touchEvent->defaultPrevented() || touchEvent->defaultHandled();
        }
    }

    return swallowedEvent;
}

       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/page/EventHandler.cpp中。

       EventHandler类的成员函数handleTouchEvent主要是做三件事情:

       1. 对当前发生的Touch事件的每一个Touch Point进行Hit Test,分别找到它们的Target Node。

       2. 对Touch Point进行分类。还与屏幕接触是一类,不再与屏幕接触是另一类。与屏幕接触的一类又划分为两个子类。一个子类是静止不动的,另一个子类是正在移动的。另外,目标Node相同的Touch Point也会被组织在同一个Touch List中。

       3. 将Touch事件分发给Target Node处理。

       接下来,我们就将EventHandler类的成员函数handleTouchEvent划分为三段进行分析,每一段对应于上述的一个事件。

       第一段代码如下所示:

bool EventHandler::handleTouchEvent(const PlatformTouchEvent& event)
{
    ......

    const Vector<PlatformTouchPoint>& points = event.touchPoints();  

    unsigned i;
    ......
    bool allTouchReleased = true;
    for (i = 0; i < points.size(); ++i) {
        const PlatformTouchPoint& point = points[i];
        ......
        if (point.state() != PlatformTouchPoint::TouchReleased && point.state() != PlatformTouchPoint::TouchCancelled)
            allTouchReleased = false;
    }

    ......

    // First do hit tests for any new touch points.
    for (i = 0; i < points.size(); ++i) {
        const PlatformTouchPoint& point = points[i];
        ......
        
        if (point.state() == PlatformTouchPoint::TouchPressed) {
            HitTestRequest::HitTestRequestType hitType = HitTestRequest::TouchEvent | HitTestRequest::ReadOnly | HitTestRequest::Active;
            LayoutPoint pagePoint = roundedLayoutPoint(m_frame->view()->windowToContents(point.pos()));
            HitTestResult result;
            if (!m_touchSequenceDocument) {
                result = hitTestResultAtPoint(pagePoint, hitType);
            } else if (m_touchSequenceDocument->frame()) {
                LayoutPoint framePoint = roundedLayoutPoint(m_touchSequenceDocument->frame()->view()->windowToContents(point.pos()));
                result = hitTestResultInFrame(m_touchSequenceDocument->frame(), framePoint, hitType);
            } else
                continue;

            Node* node = result.innerNode();
            ......

            if (!m_touchSequenceDocument) {
                // Keep track of which document should receive all touch events
                // in the active sequence. This must be a single document to
                // ensure we don't leak Nodes between documents.
                m_touchSequenceDocument = &(result.innerNode()->document());
                ......
            }

            ......

            m_targetForTouchID.set(point.id(), node);

            ......
        }
    }

       这段代码首先获得参数event描述的Touch事件关联的所有Touch Point,保存在本地变量points描述的一个Vector中。

       每一个Touch Point都用一个PlatformTouchPoint对象描述,它有五种状态,如下所示:

class PlatformTouchPoint {
public:
    enum State {
        TouchReleased,
        TouchPressed,
        TouchMoved,
        TouchStationary,
        TouchCancelled,
        TouchStateEnd // Placeholder: must remain the last item.
    };

    ......
};
       这些状态定义在文件external/chromium_org/third_party/WebKit/Source/platform/PlatformTouchPoint.h中。

       一个Touch Point的一般状态变化过程为:TouchPressed->TouchMoved/TouchStationary->TouchReleased/TouchCancelled。

       回到上面第一段代码中,它主要做的事情就是对那些状态为TouchPressed的Touch Point进行Hit Test,目的是找到它们的Target Node,并且将这些Target Node保存在EventHandler类的成员变量m_targetForTouchID描述的一个Hash Map中,键值为Touch Point对应的ID。有了这个Hash Map,当一个Touch Point从TouchPressed状态变为其它状态时,就可以轻松地知道它的Target Node,避免做重复的Hit Test。

       一系列连续的Touch Event只能发生在一个Document上。如果两个Touch Event有两个或者两个以上的Touch Point具有相同的ID,那么它们就是连续的Touch Event。它们所发生在的Document由第一个连续的Touch Event的第一个处于TouchPressed状态的Touch Point确定,也就是这个Touch Point的Target Node所在的Document。这个Document一旦确定,就会维护在EventHandler类的成员变量m_touchSequenceDocument中。

       当一个Touch Event的所有Touch Point的状态都处于TouchReleased或者TouchCancelled时,它就结束一个连续的Touch Event系列。这时候EventHandler类的成员变量m_touchSequenceDocument就会设置为NULL,表示接下来发生的Touch Event属于另外一个连续的系列。

       当一个Touch Event所在的Document所未确定时,EventHandler类的成员函数handleTouchEvent调用成员函数hitTestResultAtPoint对第一个Touch Point做Hit Test;当一个Touch Event所在的Document确定时,则调用另外一个成员函数hitTestResultInFrame做Hit Test。后者会将Hit Test的范围限制在指定的Document中。后面我们将以EventHandler类的成员函数hitTestResultAtPoint为例,分析Hit Test的执行过程。

       第二段代码如下所示:

    // Holds the complete set of touches on the screen.
    RefPtrWillBeRawPtr<TouchList> touches = TouchList::create();

    // A different view on the 'touches' list above, filtered and grouped by
    // event target. Used for the 'targetTouches' list in the JS event.
    typedef WillBeHeapHashMap<EventTarget*, RefPtrWillBeMember<TouchList> > TargetTouchesHeapMap;
    TargetTouchesHeapMap touchesByTarget;

    // Array of touches per state, used to assemble the 'changedTouches' list.
    typedef WillBeHeapHashSet<RefPtrWillBeMember<EventTarget> > EventTargetSet;
    struct {
        // The touches corresponding to the particular change state this struct
        // instance represents.
        RefPtrWillBeMember<TouchList> m_touches;
        // Set of targets involved in m_touches.
        EventTargetSet m_targets;
    } changedTouches[PlatformTouchPoint::TouchStateEnd];

    for (i = 0; i < points.size(); ++i) {
        const PlatformTouchPoint& point = points[i];
        PlatformTouchPoint::State pointState = point.state();
        RefPtrWillBeRawPtr<EventTarget> touchTarget = nullptr;

        if (pointState == PlatformTouchPoint::TouchReleased || pointState == PlatformTouchPoint::TouchCancelled) {
            // The target should be the original target for this touch, so get
            // it from the hashmap. As it's a release or cancel we also remove
            // it from the map.
            touchTarget = m_targetForTouchID.take(point.id());
        } else {
            // No hittest is performed on move or stationary, since the target
            // is not allowed to change anyway.
            touchTarget = m_targetForTouchID.get(point.id());
        }

        LocalFrame* targetFrame = 0;
        bool knownTarget = false;
        if (touchTarget) {
            Document& doc = touchTarget->toNode()->document();
            // If the target node has moved to a new document while it was being touched,
            // we can't send events to the new document because that could leak nodes
            // from one document to another. See http://crbug.com/394339.
            if (&doc == m_touchSequenceDocument.get()) {
                targetFrame = doc.frame();
                knownTarget = true;
            }
        }

        ......

        RefPtrWillBeRawPtr<Touch> touch = Touch::create(
            targetFrame, touchTarget.get(), point.id(), point.screenPos(), adjustedPagePoint, adjustedRadius, point.rotationAngle(), point.force());

        ......

        // Ensure this target's touch list exists, even if it ends up empty, so
        // it can always be passed to TouchEvent::Create below.
        TargetTouchesHeapMap::iterator targetTouchesIterator = touchesByTarget.find(touchTarget.get());
        if (targetTouchesIterator == touchesByTarget.end()) {
            touchesByTarget.set(touchTarget.get(), TouchList::create());
            targetTouchesIterator = touchesByTarget.find(touchTarget.get());
        }

        // touches and targetTouches should only contain information about
        // touches still on the screen, so if this point is released or
        // cancelled it will only appear in the changedTouches list.
        if (pointState != PlatformTouchPoint::TouchReleased && pointState != PlatformTouchPoint::TouchCancelled) {
            touches->append(touch);
            targetTouchesIterator->value->append(touch);
        }

        // Now build up the correct list for changedTouches.
        // Note that  any touches that are in the TouchStationary state (e.g. if
        // the user had several points touched but did not move them all) should
        // never be in the changedTouches list so we do not handle them
        // explicitly here. See https://bugs.webkit.org/show_bug.cgi?id=37609
        // for further discussion about the TouchStationary state.
        if (pointState != PlatformTouchPoint::TouchStationary && knownTarget) {
            ......
            if (!changedTouches[pointState].m_touches)
                changedTouches[pointState].m_touches = TouchList::create();
            changedTouches[pointState].m_touches->append(touch);
            changedTouches[pointState].m_targets.add(touchTarget);
        }
    }
    if (allTouchReleased) {
        m_touchSequenceDocument.clear();
        ......
    }
       这段代码主要是对Touch Point进行分门别类。

       首先,那些还与屏幕有接触的Touch Point将会保存在本地变量touches描述的一个Touch List中。那些状态不等于TouchReleased和TouchCancelled的Touch Point即为还与屏幕有接触的Touch Point。

       其次,具有相同Target Node的Touch Point又会保存在相同的Touch List中。这些Touch List它们关联的Target Node为键值,保存在本地变量touchesByTarget描述的一个Hash Map中。

       第三,那些位置或者状态发生变化,并且有Target Node的Touch Point会按照状态保存在本地变量changedTouches描述的一个数组中。相同状态的Touch Point保存在同一个Touch List中,它们的Target Node也会保存在同一个Hash Set中。位置或者状态发生变化的Touch Point,即为那些状态不等于TouchStationary的Touch Point。另外,如果一个Touch Point的Target Node所在的Document与当前Touch Event所发生在的Document不一致,那么该Touch Point会被认为是没有Target Node。

       这段代码还会做另外两件事情:

       1. 如果一个Touch Point的状态变为TouchReleased或者TouchCancelled,那么它就会从EventHandler类的成员变量m_targetForTouchID描述的一个Hash Map中移除。结合前面对第一段代码的分析,我们就可以知道,一个连续的Touch Event系列,它关联的Touch Point是会动态增加和移除的。

       2. 如果当前发生的Touch Event的所有Touch Point的状态均为TouchReleased或者TouchCancelled,那么当前连续的Touch Event系列就会结束。这时候EventHandler类的成员变量m_touchSequenceDocument将被设置为NULL。

       第三段代码如下所示:

   // Now iterate the changedTouches list and m_targets within it, sending
    // events to the targets as required.
    bool swallowedEvent = false;
    for (unsigned state = 0; state != PlatformTouchPoint::TouchStateEnd; ++state) {
        ......

        const AtomicString& stateName(eventNameForTouchPointState(static_cast<PlatformTouchPoint::State>(state)));
        const EventTargetSet& targetsForState = changedTouches[state].m_targets;
        for (EventTargetSet::const_iterator it = targetsForState.begin(); it != targetsForState.end(); ++it) {
            EventTarget* touchEventTarget = it->get();
            RefPtrWillBeRawPtr<TouchEvent> touchEvent = TouchEvent::create(
                touches.get(), touchesByTarget.get(touchEventTarget), changedTouches[state].m_touches.get(),
                stateName, touchEventTarget->toNode()->document().domWindow(),
                event.ctrlKey(), event.altKey(), event.shiftKey(), event.metaKey(), event.cancelable());
            touchEventTarget->toNode()->dispatchTouchEvent(touchEvent.get());
            swallowedEvent = swallowedEvent || touchEvent->defaultPrevented() || touchEvent->defaultHandled();
        }
    }

    return swallowedEvent;
}
       这段代码将Touch Event分发给Target Node处理。注意,并不是所有的Target Node都会被分发Touch Event。只有那些Touch Point位置或者状态发生了变化的Target Node才会获得Touch Event。

       这段代码按照Touch Point的状态分发Touch Event给Target Node处理,顺序为TouchReleased->TouchPressed->TouchMoved->TouchCancelled。如果具有相同状态的Touch Point关联了不同的Target Node,那么每一个Target Node都会获得一个Touch Event。

       每一个Target Node获得的Touch Event是不同的TouchEvent对象,每一个TouchEvent对象包含了以下三种信息:

       1. 所有与屏幕接触的Touch Point。这些Touch Point有的位于Target Node的范围内,有的可能位于Target Node的范围外。

       2. 位于Target Node的范围内的Touch Point。

       3. 具有相同状态的Touch Point。

       注意,这些Touch Point限定在当前发生的Touch Event关联的Touch Point中,也就是限定在参数event描述的Touch Event关联的Touch Point中。

       EventHandler类的成员函数是通过Target Node的成员函数handleTouchEvent给它们分发Touch Event,也就是通过调用Node类的成员函数handleTouchEvent将Touch Event分发给Target Node处理。

       接下来,我们就继续分析EventHandler类的成员函数hitTestResultAtPoint和Node类的成员函数handleTouchEvent的实现,以便了解WebKit根据Touch Point找到Target Node和将Touch Event分发给Target Node处理的过程。

       EventHandler类的成员函数hitTestResultAtPoint的实现如下所示:

HitTestResult EventHandler::hitTestResultAtPoint(const LayoutPoint& point, HitTestRequest::HitTestRequestType hitType, const LayoutSize& padding)
{
    ......

    // We always send hitTestResultAtPoint to the main frame if we have one,
    // otherwise we might hit areas that are obscured by higher frames.
    if (Page* page = m_frame->page()) {
        LocalFrame* mainFrame = page->mainFrame()->isLocalFrame() ? page->deprecatedLocalMainFrame() : 0;
        if (mainFrame && m_frame != mainFrame) {
            FrameView* frameView = m_frame->view();
            FrameView* mainView = mainFrame->view();
            if (frameView && mainView) {
                IntPoint mainFramePoint = mainView->rootViewToContents(frameView->contentsToRootView(roundedIntPoint(point)));
                return mainFrame->eventHandler().hitTestResultAtPoint(mainFramePoint, hitType, padding);
            }
        }
    }

    HitTestResult result(point, padding.height(), padding.width(), padding.height(), padding.width());
    ......

    // hitTestResultAtPoint is specifically used to hitTest into all frames, thus it always allows child frame content.
    HitTestRequest request(hitType | HitTestRequest::AllowChildFrameContent);
    m_frame->contentRenderer()->hitTest(request, result);
    ......

    return result;
}

       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/page/EventHandler.cpp中。

       EventHandler类的成员函数hitTestResultAtPoint首先会检查当前正在处理的网页的Main Frame是否是一个Local Frame。如果是的话,那么就会在它上面做Hit Test。否则的话,就在当前正在处理的网页所加载在的Frame上做Hit Test,也就是EventHandler类的成员变量m_frame描述的Frame。这个Frame是一个Local Frame。

       前面Chromium网页加载过程简要介绍和学习计划一文提到,在WebKit中,一个Frame有可能是Local的,也可能是Remote的。Local Frame就在当前进程加载一个网页,而Remote Frame在另外一个进程加载一个网页。Frame与Frame之间会形成一个Frame Tree。Frame Tree的根节点就是一个Main Frame。结合前面分析的EventHandler类的成员函数handleTouchEvent,我们就可以知道,如果当前正在处理的网页不是加载在一个Main Frame上,并且这个网页的Main Frame是一个Local Frame,那么发生在这个网页上的Touch Event将会在它的Main Frame上做Hit Test。在其余情况下,发生在当前正在处理的网页上的Touch Event,将会在该网页所加载在的Frame上做Hit Test。

       一旦确定在哪个Frame上做Hit Test之后,EventHandler类的成员函数hitTestResultAtPoint就会调用这个Frame的成员函数contentRenderer获得一个RenderView对象。从前面Chromium网页Render Object Tree创建过程分析一文可以知道,这个RenderView对象描述的就是当前正在处理的网页的Render Object Tree的根节点。有了这个RenderView对象之后,EventHandler类的成员函数hitTestResultAtPoint就会调用它的成员函数hitTest对参数point描述的Touch Point进行Hit Test。

       RenderView类的成员函数hitTest的实现如下所示:

bool RenderView::hitTest(const HitTestRequest& request, HitTestResult& result)
{
    return hitTest(request, result.hitTestLocation(), result);
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderView.cpp中。

       RenderView类的成员函数hitTest调用另外一个重载版本的成员函数hitTest对参数result描述的Touch Point进行Hit Test,如下所示:

bool RenderView::hitTest(const HitTestRequest& request, const HitTestLocation& location, HitTestResult& result)
{
    ......

    return layer()->hitTest(request, location, result);
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderView.cpp中。

       RenderView类重载版本的成员函数hitTest首先会调用另外一个成员函数layer获得一个RenderLayer对象。从前面Chromium网页Render Layer Tree创建过程分析一文可以知道,这个RenderLayer对象描述的就是网页的Render Layer Tree的根节点。有了这个RenderLayer对象之后,RenderView类重载版本的成员函数hitTest就会调用它的成员函数hitTest对象参数result描述的Touch Point进行Hit Test,如下所示:

bool RenderLayer::hitTest(const HitTestRequest& request, const HitTestLocation& hitTestLocation, HitTestResult& result)
{
    ......

    LayoutRect hitTestArea = renderer()->view()->documentRect();
    ......

    RenderLayer* insideLayer = hitTestLayer(this, 0, request, result, hitTestArea, hitTestLocation, false);
    ......

    return insideLayer;
}

      这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。

      RenderLayer类的成员函数hitTest首先是获得网页的Document对象占据的区域hitTestArea,然后再调用另外一个成员函数hitTestLayer在该区域上对参数hitTestLocation描述的Touch Point进行Hit Test,如下所示:

RenderLayer* RenderLayer::hitTestLayer(RenderLayer* rootLayer, RenderLayer* containerLayer, const HitTestRequest& request, HitTestResult& result,
                                       const LayoutRect& hitTestRect, const HitTestLocation& hitTestLocation, bool appliedTransform,
                                       const HitTestingTransformState* transformState, double* zOffset)
{
    ......

    // Ensure our lists and 3d status are up-to-date.
    m_stackingNode->updateLayerListsIfNeeded();
    update3DTransformedDescendantStatus();
    ......

    // The following are used for keeping track of the z-depth of the hit point of 3d-transformed
    // descendants.
    double localZOffset = -numeric_limits<double>::infinity();
    double* zOffsetForDescendantsPtr = 0;
    double* zOffsetForContentsPtr = 0;

    bool depthSortDescendants = false;
    if (preserves3D()) {
        depthSortDescendants = true;
        // Our layers can depth-test with our container, so share the z depth pointer with the container, if it passed one down.
        zOffsetForDescendantsPtr = zOffset ? zOffset : &localZOffset;
        zOffsetForContentsPtr = zOffset ? zOffset : &localZOffset;
    } else if (m_has3DTransformedDescendant) {
        // Flattening layer with 3d children; use a local zOffset pointer to depth-test children and foreground.
        depthSortDescendants = true;
        zOffsetForDescendantsPtr = zOffset ? zOffset : &localZOffset;
        zOffsetForContentsPtr = zOffset ? zOffset : &localZOffset;
    } else if (zOffset) {
        zOffsetForDescendantsPtr = 0;
        // Container needs us to give back a z offset for the hit layer.
        zOffsetForContentsPtr = zOffset;
    }

    ......

    // This variable tracks which layer the mouse ends up being inside.
    RenderLayer* candidateLayer = 0;

    // Begin by walking our list of positive layers from highest z-index down to the lowest z-index.
    RenderLayer* hitLayer = hitTestChildren(PositiveZOrderChildren, rootLayer, request, result, hitTestRect, hitTestLocation,
                                        localTransformState.get(), zOffsetForDescendantsPtr, zOffset, unflattenedTransformState.get(), depthSortDescendants);
    if (hitLayer) {
        if (!depthSortDescendants)
            return hitLayer;
        candidateLayer = hitLayer;
    }

    // Now check our overflow objects.
    hitLayer = hitTestChildren(NormalFlowChildren, rootLayer, request, result, hitTestRect, hitTestLocation,
                           localTransformState.get(), zOffsetForDescendantsPtr, zOffset, unflattenedTransformState.get(), depthSortDescendants);
    if (hitLayer) {
        if (!depthSortDescendants)
            return hitLayer;
        candidateLayer = hitLayer;
    }

    ......

    // Next we want to see if the mouse pos is inside the child RenderObjects of the layer. Check
    // every fragment in reverse order.
    if (isSelfPaintingLayer()) {
        // Hit test with a temporary HitTestResult, because we only want to commit to 'result' if we know we're frontmost.
        HitTestResult tempResult(result.hitTestLocation());
        bool insideFragmentForegroundRect = false;
        if (hitTestContentsForFragments(layerFragments, request, tempResult, hitTestLocation, HitTestDescendants, insideFragmentForegroundRect)
            && isHitCandidate(this, false, zOffsetForContentsPtr, unflattenedTransformState.get())) {
            ......
            if (!depthSortDescendants)
                return this;
            // Foreground can depth-sort with descendant layers, so keep this as a candidate.
            candidateLayer = this;
        } 
    }

    // Now check our negative z-index children.
    hitLayer = hitTestChildren(NegativeZOrderChildren, rootLayer, request, result, hitTestRect, hitTestLocation,
        localTransformState.get(), zOffsetForDescendantsPtr, zOffset, unflattenedTransformState.get(), depthSortDescendants);
    if (hitLayer) {
        if (!depthSortDescendants)
            return hitLayer;
        candidateLayer = hitLayer;
    }

    ......

    // If we found a layer, return. Child layers, and foreground always render in front of background.
    if (candidateLayer)
        return candidateLayer;

    if (isSelfPaintingLayer()) {
        HitTestResult tempResult(result.hitTestLocation());
        bool insideFragmentBackgroundRect = false;
        if (hitTestContentsForFragments(layerFragments, request, tempResult, hitTestLocation, HitTestSelf, insideFragmentBackgroundRect)
            && isHitCandidate(this, false, zOffsetForContentsPtr, unflattenedTransformState.get())) {
            ......
            return this;
        }
        ......
    }

    return 0;
}

       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。

       RenderLayer类的成员变量m_stackingNode指向的是一个RenderLayerStackingNode对象。这个RenderLayerStackingNode对象描述了当前正在处理的Render Layer所在的Stacking Context。RenderLayer类的成员函数hitTestLayer首先调用这个RenderLayerStackingNode对象的成员函数updateLayerListsIfNeeded更新它所描述的Stacking Context所包含的Render Layer的层次关系,以便接下来可以按照它们的Z轴位置进行Hit Test。

       RenderLayer类的成员函数hitTestLayer同时还会调用另外一个成员函数update3DTransformedDescendantStatus检查当前正在处理的Render Layer的子Render Layer是否设置了3D。如果设置了,那么RenderLayer类的成员变量m_has3DTransformedDescendant就会被设置为true。3D变换使得Hit Test不能简单地按照原来Z-Index的大小进行Hit Test。

       RenderLayer类的成员函数hitTestLayer接下来根据两种不同的情况,采取两种不同的Hit Test方法:

       1. 当前正在处理的Render Layer将CSS属性tranform-type设置为“preserve-3d”,或者它的子Render Layer设置了3D变换。在这种情况下,本地变量depthSortDescendants的值会被设置为true,并且另外两个本地变量zOffsetForDescendantsPtr和zOffsetForContentsPtr指向了一个类型为double的地址。这个地址包含的double值描述的是上一个被Hit的Render Layer在Touch Point处的Z轴位置。其中,本地变量zOffsetForContentsPtr描述的Z轴位置是给当前正在处理的Render Layer使用的,而本地变量zOffsetForDescendantsPtr描述的Z轴位置是给当前正在处理的Render Layer的子Render Layer使用的。在一个设置了3D变换的环境中,Z-Index值大的Render Layer不一定位于Z-Index值小的Render Layer的上面,需要进一步结合它们的3D变换情况进行判断。因此,就需要将上一个被Hit的Render Layer在Touch Point处的Z轴位置保存下来,用来与下一个也被Hit的Render Layer进行比较,以便得出正确的被Hit的Render Layer。

       2. 当前正在处理的Render Layer没有将CSS属性tranform-type设置为“preserve-3d”,以及它的子Render Layer也没有设置3D变换。在这种情况下,本地变量depthSortDescendants的值会被设置为false。另外两个本地变量zOffsetForDescendantsPtr和zOffsetForContentsPtr,前者被设置为NULL,后者设置为参数zOffset的值。将本地变量zOffsetForDescendantsPtr设置为NULL,是因为当前正在处理的Render Layer的子Render Layer在做Hit Test时,不需要与其它的子Render Layer在Touch Point处进行Z轴位置。将zOffsetForContentsPtr的值指定为参数zOffset的值,是因为调用者可能会指定一个Z轴位置,要求当前正在处理的Render Layer在Touch Point处与其进行比较。

       对第二种情况的处理比较简单,流程如下所示: 

       1. 按照Z-Index从大到小的顺序对Z-Index值大于等于0的子Render Layer进行Hit Test。如果发生了Hit,那么停止Hit Test流程。

       2. 对当前正在处理的Render Layer的Foreground层进行Hit Test。如果发生了Hit,那么停止Hit Test流程。

       3. 按照Z-Index从大到小的顺序对Z-Index值小于0的子Render Layer进行Hit Test。如果发生了Hit,那么停止Hit Test流程。

       4. 对当前正在处理的Render Layer关联的Render Object的Background层进行Hit Test。

       对第一种情况的处理相对就会复杂一些,如下所示:

       1. 对当前正在处理的Render Layer的所有子Render Layer,以及当前正在处理的Render Layer的Foreground层,都会一一进行Hit Test。在这个Hit Test过程中,所有被Hit的Render Layer,都会根据它们3D变换情况,检查它们在Touch Point处的Z轴位置。Z轴位置最大的Render Layer或者Render Object,将会选择用来接收Touch Event。

       2. 如果所有子Render Layer和当前正在处理的Render Layer的Foreground都没有发生Hit,那么就会再对当前正在处理的Render Layer的Background层进行Hit Test。

       注意,对于每一个子Render Layer,RenderLayer类的成员函数hitTestLayer就会调用另外一个成员函数hitTestChildren对分别对它们进行Hit Test。RenderLayer类的成员函数hitTestChildren又会调用hitTestLayer对每一个子Render Layer执行具体的Hit Test。

       这意味着,RenderLayer类的成员函数hitTestLayer会被递归调用来对Render Layer Tree中的每一个Render Layer进行Hit Test。当前正在处理的Render Layer是否被Hit,RenderLayer类的成员函数hitTestLayer是通过调用两次成员函数hitTestContentsForFragments进行检查。第一次调用是确定当前正在处理的Render Layer的Foreground层是否发生了Hit Test。第二次调用是确定当前正在处理的Render Layer的Background层是否发生了Hit Test。

        一旦检查当前正在处理的Render Layer发生了Hit,那么RenderLayer类的成员函数hitTestLayer还需要调用另外一个成员函数isHitCandidate将它在Touch Point处的Z轴位置与本地变量zOffsetForContentsPtr描述的Z轴位置(上一个被Hit的Render Layer在Touch Point的Z轴位置)进行比较。比较后如果发现当前正在处理的Render Layer在Touch Point处的Z轴位置较大,那么才会认为它是被Hit的Render Layer。

       接下来我们就继续分析RenderLayer类的成员函数hitTestContentsForFragments的实现,以便可以了解一个Render Layer在什么情况会被认为是发生了Hit,如下所示:

bool RenderLayer::hitTestContentsForFragments(const LayerFragments& layerFragments, const HitTestRequest& request, HitTestResult& result,
    const HitTestLocation& hitTestLocation, HitTestFilter hitTestFilter, bool& insideClipRect) const
{
    ......

    for (int i = layerFragments.size() - 1; i >= 0; --i) {
        const LayerFragment& fragment = layerFragments.at(i);
        ......
        if (hitTestContents(request, result, fragment.layerBounds, hitTestLocation, hitTestFilter))
            return true;
    }

    return false;
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。

       前面Chromium网页Layer Tree绘制过程分析一文提到,Render Layer是按Fragment进行划分的。因此,RenderLayer类的成员函数hitTestContentsForFragments分别调用另外一个成员函数hitTestContents对每一个Fragment进行Hit Test。只要其中一个Fragment发生了Hit,那么就会认为它所在的Render Layer发生了Hit。

       RenderLayer类的成员函数hitTestContents的实现如下所示:

bool RenderLayer::hitTestContents(const HitTestRequest& request, HitTestResult& result, const LayoutRect& layerBounds, const HitTestLocation& hitTestLocation, HitTestFilter hitTestFilter) const
{
    ......

    if (!renderer()->hitTest(request, result, hitTestLocation, toLayoutPoint(layerBounds.location() - renderBoxLocation()), hitTestFilter)) {
        ......
        return false;
    }

    ......

    return true;
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。

       RenderLayer类的成员函数hitTestContents首先调用成员函数renderer获得一个Render Object。这个Render Object是当前正在处理的Render Layer的宿主Render Object。也就是说,我们在为网页创建Render Layer Tree时,为上述Render Object创建了一个Render Layer。该Render Object的子Render Object如果没有自己的Render Layer,那么就会与该Render Object共享同一个Render Layer。这一点可以参考前面Chromium网页Render Layer Tree创建过程分析一文。 

       获得了当前正在处理的Render Layer的宿主Render Object之后,RenderLayer类的成员函数hitTestContents就调用它的成员函数hitTest检查它在参数layerBounds描述的区域内是否发生了Hit。如果发生了Hit,那么RenderLayer类的成员函数hitTestContents就直接返回一个true值给调用者。否则的话,就会返回一个false值给调用者。

       这一步执行完成之后,就从Render Layer Tree转移到Render Object Tree进行Hit Test,也就是调用RenderObject类的成员函数hitTest进行Hit Test,如下所示:

bool RenderObject::hitTest(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestFilter hitTestFilter)
{
    bool inside = false;
    if (hitTestFilter != HitTestSelf) {
        // First test the foreground layer (lines and inlines).
        inside = nodeAtPoint(request, result, locationInContainer, accumulatedOffset, HitTestForeground);

        // Test floats next.
        if (!inside)
            inside = nodeAtPoint(request, result, locationInContainer, accumulatedOffset, HitTestFloat);

        // Finally test to see if the mouse is in the background (within a child block's background).
        if (!inside)
            inside = nodeAtPoint(request, result, locationInContainer, accumulatedOffset, HitTestChildBlockBackgrounds);
    }

    // See if the mouse is inside us but not any of our descendants
    if (hitTestFilter != HitTestDescendants && !inside)
        inside = nodeAtPoint(request, result, locationInContainer, accumulatedOffset, HitTestBlockBackground);

    return inside;
}

       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderObject.cpp中。

       从前面分析的RenderLayer类的成员函数hitTestLayer可以知道,一个Reder Layer关联的Render Object会进行两次Hit Test。第一次是针对该Render Object的Foreground进行Hit Test,这时候参数hitTestFilter的值等于HitTestDescendants。第二次是针对该Render Object的Background层进行Hit Test,这时候参数hitTestFilter的值等于HitTestSelf。

       无论是Foreground层,还是Background层,RenderObject类的成员函数hitTest都是通过调用另外一个成员函数nodeAtPoint进行Hit Test的。RenderObject类的成员函数nodeAtPoint是从父类RenderBox继承下来的,它的实现如下所示:

bool RenderBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction action)
{
    LayoutPoint adjustedLocation = accumulatedOffset + location();

    // Check kids first.
    for (RenderObject* child = slowLastChild(); child; child = child->previousSibling()) {
        if ((!child->hasLayer() || !toRenderLayerModelObject(child)->layer()->isSelfPaintingLayer()) && child->nodeAtPoint(request, result, locationInContainer, adjustedLocation, action)) {
            updateHitTestResult(result, locationInContainer.point() - toLayoutSize(adjustedLocation));
            return true;
        }
    }

    // Check our bounds next. For this purpose always assume that we can only be hit in the
    // foreground phase (which is true for replaced elements like images).
    LayoutRect boundsRect = borderBoxRect();
    boundsRect.moveBy(adjustedLocation);
    if (visibleToHitTestRequest(request) && action == HitTestForeground && locationInContainer.intersects(boundsRect)) {
        updateHitTestResult(result, locationInContainer.point() - toLayoutSize(adjustedLocation));
        if (!result.addNodeToRectBasedTestResult(node(), request, locationInContainer, boundsRect))
            return true;
    }

    return false;
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderBox.cpp中。

       RenderBox类的成员函数nodeAtPoint首先对当前正在处理的Render Object的子Render Object进行Hit Test。这是通过递归调用RenderBox类的成员函数nodeAtPoint实现的。如果子Render Object没有被Hit,那么RenderBox类的成员函数才会判断当前正在处理的Render Object是否发生Hit,也就是判断参数locationInContainer描述的Touch Point是否落在当前正在处理的Render Object的区域boundsRect内。

       这里有一点需要注意,当前正在处理的Render Object的每一个子Render Objec并不是都会被递归Hit Test。只有那些没有创建Render Layer的子Render Object才会进行递归Hit Test。这些没有创建自己的Render Layer的子Render Object将会与当前正在处理的Render Object共享同一个Render Layer。对于那些有自己的Render Layer的子Render Object,它们的Hit Test将由前面分析的RenderLayer类的成员函数hitTestLayer发起。

       当一个Render Object发生Hit时,RenderBox类的成员函数nodeAtPoint就会调用另外一个成员函数updateHitTestResult将Hit信息记录在参数result描述的一个HitTestResult对象中。RenderBox类的成员函数updateHitTestResult由子类RenderObject实现,如下所示:

void RenderObject::updateHitTestResult(HitTestResult& result, const LayoutPoint& point)
{
    ......

    Node* node = this->node();

    // If we hit the anonymous renderers inside generated content we should
    // actually hit the generated content so walk up to the PseudoElement.
    if (!node && parent() && parent()->isBeforeOrAfterContent()) {
        for (RenderObject* renderer = parent(); renderer && !node; renderer = renderer->parent())
            node = renderer->node();
    }

    if (node) {
        result.setInnerNode(node);
        ......
    }
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderObject.cpp中。

       RenderObject类的成员函数updateHitTestResult首先调用成员函数node获得与当前正在处理的Render Object关联的HTML元素,也就是位于网页的DOM Tree中的一个Node,作为当前发生的Touch Event的Target Node。

       如果当前正在处理的Render Object没有关联一个HTML元素,那么就说明当前正在处理的Render Object是一个匿名的Render Object。这时候需要在网页的Render Object Tree中找到一个负责生成它的、非匿名的父Render Object,然后再获得与这个父Render Object关联的HTML元素,作为当前发生的Touch Event的Target Node。

       一旦找到了Target Node,RenderObject类的成员函数updateHitTestResult就会将它保存在参数result描述的一个HitTestResult对象中。这是通过调用HitTestResult类的成员函数setInnerNode实现的,如下所示:

void HitTestResult::setInnerNode(Node* n)
{
    ......
    m_innerNode = n;
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/HitTestResult.cpp中。

       HitTestResult类的成员函数setInnerNode主要是将参数n描述的一个HTML元素保存在成员变量m_innerNode中。这个HTML元素可以通过调用HitTestResult类的成员函数innerNode获得,如下所示:

class HitTestResult {
public:
    ......

    Node* innerNode() const { return m_innerNode.get(); }

    ......
};
      这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/HitTestResult.h中。

      这一步执行完成后,WebKit就通过网页的Render Layer Tree和Render Object Tree,最终在DOM Tree中找到了Target Node。这个Target Node负责接收和处理当前发生的Touch Event。

      回到前面分析的EventHandler类的成员函数handleTouchEvent中,接下来它就会将当前发生的Touch Event分发给前面找到的Target Node处理,这是通过调用它的成员函数dispatchTouchEvent实现的,如下所示:

bool Node::dispatchTouchEvent(PassRefPtrWillBeRawPtr<TouchEvent> event)
{
    return EventDispatcher::dispatchEvent(this, TouchEventDispatchMediator::create(event));
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/Node.cpp中。

       Node类的成员函数dispatchTouchEvent首先调用TouchEventDispatchMediator类的静态成员函数create将参数描述的Touch Event封装在一个TouchEventDispatchMediator对象中,然的再调用EventDispatcher类的静态成员函数dispatchEvent对该Touch Event进行处理,如下所示:

bool EventDispatcher::dispatchEvent(Node* node, PassRefPtrWillBeRawPtr<EventDispatchMediator> mediator)
{
    ......
    EventDispatcher dispatcher(node, mediator->event());
    return mediator->dispatchEvent(&dispatcher);
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/events/EventDispatcher.cpp中。

       EventDispatcher类的静态成员函数dispatchEvent首先从参数mediator描述的TouchEventDispatchMediator对象中取出它所封装的Touch Event,并且将该Touch Event封装在一个EventDispatcher对象中,最后调用参数mediator描述的TouchEventDispatchMediator对象的成员函数dispatchEvent对上述Touch Event进行分发处理,如下所示:

bool TouchEventDispatchMediator::dispatchEvent(EventDispatcher* dispatcher) const
{
    event()->eventPath().adjustForTouchEvent(dispatcher->node(), *event());
    return dispatcher->dispatch();
}

       这个函数定义在文件/external/chromium_org/third_party/WebKit/Source/core/events/TouchEvent.cpp中。

       TouchEventDispatchMediator类的成员函数dispatchEvent首先调用成员函数event获得一个TouchEvent对象。这个TouchEvent描述的就是当前发生的Touch Event。调用这个TouchEvent对象的成员函数eventPath可以获得一个EventPath对象。这个EventPath对象描述的是当前发生的Touch Event的分发路径。关于WebKit中的Event分发路径,下面我们再描述。有了这个EventPath对象之后,TouchEventDispatchMediator类的成员函数dispatchEvent调用它的成员函数adjustForTouchEvent调整Touch Event分发路径中的Shadow DOM的Touch List。关于Shadow DOM的更详细描述,可以参考What the Heck is Shadow DOM这篇文章。

        TouchEventDispatchMediator类的成员函数dispatchEvent最后调用参数dispatcher描述的EventDispatcher对象的成员函数dispatch处理它所封装的Touch Event,如下所示:

bool EventDispatcher::dispatch()
{
    .......

    void* preDispatchEventHandlerResult;
    if (dispatchEventPreProcess(preDispatchEventHandlerResult) == ContinueDispatching)
        if (dispatchEventAtCapturing(windowEventContext) == ContinueDispatching)
            if (dispatchEventAtTarget() == ContinueDispatching)
                dispatchEventAtBubbling(windowEventContext);
    dispatchEventPostProcess(preDispatchEventHandlerResult);

    ......

    return !m_event->defaultPrevented();
}

       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/events/EventDispatcher.cpp中。

       在网页发生的一个Event,分为五个阶段进行分发处理:

       1. Pre Process

       2. Capturing

       3. Target

       4. Bubbling

       5. Post Process

       我们通过一个例子说明这五个阶段的处理过程,如下所示:

<html>
    <body>
        <div id="div1"> 
            <div id="div2">
                <div id="div3">
                </div>
            </div>
        </div>
    </body>
</html>
       假设在div3上发生了一个Touch Event。

       在Pre Process阶段,WebKit会将Touch Event分发给div3的Pre Dispatch Event Handler处理,让div3有机会在DOM Event Handler处理Touch Event之前做一些事情,用来实现自己的行为。

       在Capturing阶段,WebKit会将Touch Event依次分发给html -> body -> div1 -> div2的DOM Event Handler处理。

       在Target阶段,WebKit会将Touch Event依次分发给div3的DOM Event Handler处理。。

       在Bubbling阶段, WebKit会将Touch Event依次分发给div2 -> div1 -> body -> html的DOM Event Handler处理。

       在Post Process阶段,WebKit会将Touch Event分发给div3的Post Dispatch Event Handler处理,让div3有机会在DOM Event Handler处理Touch Event之后做一些事情,与它的Pre Dispatch Event Handler相呼应。

       此外,如果在前面4个阶段,Touch Event的preventDefault函数没有被调用,那么WebKit会将它依次分发给div3 -> div2 -> div1 -> body -> html的Default Event Handler处理。在这个过程中,如果某一个Node的Default Event Handler处理了该Touch Event,那么该Touch Event的分发过程就会中止。

       其中,Pre Process和Post Process这两个阶段是一定会执行的。在Capturing、Target和Bubbling这三个阶段,如果某一个Node的DOM Event Handler调用了Touch Event的stopPropagation函数,那么它就会提前中止,后面的阶段也不会被执行。

       Pre Dispatch Event Handler、Post Dispatch Event Handler和Default Event Handler是由WebKit实现的,DOM Event Handler可以通过JavaScript进行注册。在注册的时候,可以指定DOM Event Handler在Capturing阶段还是Bubbling阶段接收Event,但是不能同时在这两个阶段都接收。此外,注册Target Node上的DOM Event Handler没有Capturing阶段还是Bubbling阶段之分,如果Event在Capturing阶段没有被中止,那么它将在Target阶段接收。

       明白了WebKit中的Event处理流程之后,接下来我们主要分析Target Node在Target阶段处理Touch Event的过程,也就是EventDispatcher类的成员函数dispatchEventAtTarget的实现,如下所示:

inline EventDispatchContinuation EventDispatcher::dispatchEventAtTarget()
{
    m_event->setEventPhase(Event::AT_TARGET);
    m_event->eventPath()[0].handleLocalEvents(m_event.get());
    return m_event->propagationStopped() ? DoneDispatching : ContinueDispatching;
}

      这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/events/EventDispatcher.cpp中。

      EventDispatcher类的成员变量m_event描述的就是当前发生的Touch Event。这个Touch Event的Event Path是一个Node Event Context列表。列表中的第一个Node Event Context描述的就是当前发生的Touch Event的Target Node的上下文信息。有了这个Node Event Context之后,就可以调用它的成员函数handleLocalEvents将当前发生的Touch Event分发给Target Node的DOM Event Handler处理,如下所示:

void NodeEventContext::handleLocalEvents(Event* event) const
{
    ......

    event->setTarget(target());
    event->setCurrentTarget(m_currentTarget.get());
    m_node->handleLocalEvents(event);
}

       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/events/NodeEventContext.cpp中。

       这时候,NodeEventContext类的成员变量m_node描述的就是Touch Event的Target Node。NodeEventContext类的成员函数handleLocalEvents调用这个Target Node的成员函数handleLocalEvents,用来将当前发生的Touch Event分发给它的DOM Event Handler处理,如下所示:

void Node::handleLocalEvents(Event* event)
{
    ......

    fireEventListeners(event);
}

       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/Node.cpp中。

       Node类的成员函数handleLocalEvents主要是调用另外一个成员函数fireEventListeners将当前发生的Touch Event分发给Target Node的DOM Event Handler处理。

       Node类的成员函数fireEventListeners是从父类EventTarget继承下来的,它的实现如下所示:

bool EventTarget::fireEventListeners(Event* event)
{
    ......

    EventTargetData* d = eventTargetData();
    ......

    EventListenerVector* legacyListenersVector = 0;
    AtomicString legacyTypeName = legacyType(event);
    if (!legacyTypeName.isEmpty())
        legacyListenersVector = d->eventListenerMap.find(legacyTypeName);

    EventListenerVector* listenersVector = d->eventListenerMap.find(event->type());
    ......

    if (listenersVector) {
        fireEventListeners(event, d, *listenersVector);
    } else if (legacyListenersVector) {
        ......
        fireEventListeners(event, d, *legacyListenersVector);
        ......
    }

    ......
    return !event->defaultPrevented();
}

       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/events/EventTarget.cpp中。

       Node类的成员函数fireEventListeners主要根据Touch Event的类型在Target Node注册的DOM Event Handler中找到对应的DOM Event Handler。例如,如果当前发生的是类型为MOVE的Touch Event,那么Node类的成员函数fireEventListeners就会找到注册在Target Node上的类型为TouchMove的DOM Event Handler。

       找到了对应的DOM Event Handler之后,Node类的成员函数fireEventListeners再调用另外一个重载版本的成员函数fireEventListeners将当前发生的Touch Event分发给它们处理,如下所示:

void EventTarget::fireEventListeners(Event* event, EventTargetData* d, EventListenerVector& entry)
{
    ......

    size_t i = 0;
    size_t size = entry.size();
    ......

    for ( ; i < size; ++i) {
        RegisteredEventListener& registeredListener = entry[i];
        if (event->eventPhase() == Event::CAPTURING_PHASE && !registeredListener.useCapture)
            continue;
        if (event->eventPhase() == Event::BUBBLING_PHASE && registeredListener.useCapture)
            continue;

        // If stopImmediatePropagation has been called, we just break out immediately, without
        // handling any more events on this target.
        if (event->immediatePropagationStopped())
            break;

        ExecutionContext* context = executionContext();
        if (!context)
            break;

        ......
        registeredListener.listener->handleEvent(context, event);
        ......
    }
    ......
}

       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/events/EventTarget.cpp中。

       在Capturing和Bubbling阶段,WebKit也是通过Node类的成员函数fireEventListeners将当前发生的Event分发给Event Path上的Node处理。从这里我们就可以看到,在Capturing阶段,如果一个Node在注册了一个在Capturing阶段接收Event的DOM Event Handler,那么此时该DOM Event Handler就会获得当前发生的Event。同样,在Bubbling阶段,如果一个Node在注册了一个在Bubbling阶段接收Event的DOM Event Handler,那么此时该DOM Event Handler就会获得当前发生的Event。对于Target Node来说,它注册的DOM Event Handler则会在Target阶段获得当前发生的Event。

       这些DOM Event Handler在注册的时候,会被JavaScript引擎V8封装在一个V8AbstractEventListener对象中。Node类的成员函数fireEventListeners通过调用这些V8AbstractEventListener对象的成员函数handleEvent将当前发生的Event分发给它们所封装的DOM Event Handler处理。这就会进入到JavaScript引擎V8里面去执行了。以后我们分析JavaScript引擎V8时,再回过头来看它对Event的处理流程。

       至此,我们就分析完成Chromium分发输入事件给WebKit,以及WebKit进行处理的过程了。这个过程是在Render进程的Main线程中执行的。回忆前面Chromium网页滑动和捏合手势处理过程分析一文,滑动和捏合手势这两种特殊的输入事件,是在Render进程的Compositor线程中处理的。

       WebKit在处理输入事件的过程中,需要通过Render Layer Tree和Render Object Tree,在DOM Tree中找到输入事件的Target Node(Hit Test)。找到了输入事件的Target Node之后,分为Pre Process、Capturing、Target、Bubbling和Post Process五个阶段对输入事件进行处理。其中,Capturing、Target和Bubbling这三个阶段是将输入事件分发给Target Node的DOM Event Handler处理,也就是我们通过JavaScript注册的Event Handler。DOM Event Handler最终是在JavaScript引擎V8执行的。

       至此,Chromium的网页输入事件处理机制我们就全部分析完成了。重新学习可以参考前面Chromium网页输入事件处理机制简要介绍和学习计划这篇文章。更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo

作者:Luoshengyang 发表于2016/7/25 1:00:35 原文链接
阅读:38927 评论:7 查看评论

Chromium视频标签简要介绍和学习计划

$
0
0

       随着互联网的发展,在网页上观看视频变得越来越流行,尤其是泛娱乐(手机直播)大行其道的今天。在HTML5之前,在网页上播放视频需要插件支持,例如Flash插件。有了HTML5之后,标签<video>使得浏览器有了播放视频的功能。与插件相比,浏览器的视频播放功能不仅在产品上体验更好,在技术上也更加稳定。本文接下来就简要介绍Chromium是如何实现<video>标签的视频播放功能的,以及制定学习计划。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

       本文以及接下来的文章,我们主要关注Chromium在Android平台上是如何实现<video>标签的视频播放功能的。我们知道,Android平台提供播放视频的API接口,也就是MediaPlayer接口。这个接口不仅可以用来播放本地媒体文件,也就用来播放网络上的流媒体文件。这可以大大简化Chromium在Android平台上支持<video>标签的工作,因为前者可以直接利用后者提供的MediaPlayer接口实现视频播放功能,如图1所示:


图1 <video>标签的实现

       从前面Chromium多进程架构简要介绍和学习计划这个系列的文章可以知道,Chromium是一个多进程架构。其中,Render进程负责加载、解析和渲染网页,Browser进程负责将Render进程渲染出来的网页内容合成到屏幕上显示。Render进程又是通过WebKit来加载和解析网页内容的。

       WebKit在解析网页内容时,每当遇到<video>标签,就会在DOM Tree中创建一个类型为HTMLMediaElement的节点。这个HTMLMediaElement节点又会在内部创建一个WebMediaPlayerClientImpl对象。这个WebMediaPlayerClientImpl对象在WebKit内部就描述为一个播放器,用来为<video>标签提供视频播放功能。

       WebMediaPlayerClientImpl类是由WebKit提供的,它本身不实现视频播放功能,因为视频播放是一个平台相关的功能。我们知道,WebKit是平台无关的,所有平台相关的功能都需要由它的使用者实现。在Chromium中,WebKit的使用者即为运行在Render进程中的Content模块。Content模块提供了一个WebMediaPlayerAndroid类,用来向WebKit提供视频播放功能。

       WebKit层的每一个WebMediaPlayerClientImpl对象在Content层都有一个对应的WebMediaPlayerAndroid对象。这些WebMediaPlayerAndroid对象就相当于是在Render进程内部实现的播放器。每一个播放器都关联有一个ID,它们被另外一个称为RendererMediaPlayerManager的对象管理。通过这种方式,就可以在一个网页上同时支持多个<video>标签,也就是可以同时播放多个视频。

       我们知道,Render进程运行在一个沙箱中,也就是它是一个受限进程。播放网络上的视频需要访问网络,以及使用系统的解码器等资源。因此,Render进程也没有实现视频播放功能,而是通过Browser进程进行播放。因此,对于Render进程中的每一个WebMediaPlayerAndroid对象,在Browser进程中都会有一个对应的WebMediaPlayerBridge对象。这些WebMediaPlayerBridge对象就相当于在Browser进程内部实现的播放器。这些播放器被另外一个称为BrowserMediaPlayerManager的对象管理,使得Browser进程可以同时创建多个播放器,以支持在一个网页上同时播放多个视频。

       WebMediaPlayerBridge类本身也不实现播放器功能。在Android平台上,WebMediaPlayerBridge类将通过SDK提供的MediaPlayer接口来实现视频播放功能。SDK是在Java层提供MediaPlayer接口的,而WebMediaPlayerBridge类是实现在C++层的,因此后者在使用前者时,需要通过JNI使用。

       总结来说,在Android平台上,Chromium会通过SDK接口MediaPlayer为网页中的每一个<video>标签创建一个播放器。播放器负责从网络上下载视频内容,并且进行解码。解码后得到的视频画面需要作为网页的一部分进行显示。从前面Chromium网页渲染机制简要介绍和学习计划这个系列的文章可以知道,在Chromium中,网页是Render进程进行渲染的,并且当前需要渲染的内容来自于网页的CC Active Layer Tree。这意味着要将播放器解码出来的视频画面交给网页的CC Active Layer Tree处理。

       播放器解码出来的视频画面是通过SurfaceTexture接口交给网页的CC Active Layer Tree处理的,如图2所示:


图2 <video>标签的视频画面渲染方式

       在Android平台上,SurfaceTexture是一个完美的接口。一方面它支持跨进程传输数据,另一方面传输的数据可以作为纹理使用。在我们这个情景中,MediaPlayer运行在Browser进程中,CC Active Layer Tree运行在Render进程中,并且是通过OpenGL进行渲染的。因此,SurfaceTexture非常适合将前者的输出作为后者的输入,并且通过OpenGL以纹理的方式渲染出来。

       具体来说,就是CC Active Layer Tree会为每一个<video>标签创建一个类型为VideoLayerImpl的节点。这个节点的内容就来自于播放器解码出来的视频画面。这些视频画面最终又是通过纹理来描述的。从前面Chromium网页渲染机制简要介绍和学习计划这个系列的文章可以知道,CC Active Layer Tree其它节点的内容,最终也是描述为纹理进行渲染的。因此,Chromium可以轻松地将播放器解码出来的视频画面作为网页的一部分进行显示。

       在Android平台上,Chromium还为<video>标签提供了全屏播放功能。全屏播放与非全屏播放可以进行无缝切换,它是怎么实现的呢?我们通过图3来说明,如下所示:


图3 <video>标签的全屏播放功能实现

       在<video>标签全屏播放的情况下,我们是看不到网页的其它内容的。这使得Chromium可以使用一种简单的方式实现<video>标签的全屏播放功能。当<video>标签全屏播放的时候,Browser进程会在浏览器窗口上创建一个全屏的SurfaceView,然后将这个SurfaceView底层的Surface取出来,设置为MediaPlayer的解码输出。这样就可以将MediaPlayer的解码输出全屏显示在屏幕上了。这时候由于网页是不可见的,Render进程不需要对它进行渲染。

       接下来,我们结合源码,按照三个情景深入分析Chromium对<video>标签的支持:

       1. 为<video>标签创建播放器的过程

       2. 渲染<video>标签视频画面的过程

       3. 全屏播放<video>标签视频的过程

       学习了这三个情景之后 ,我们就会对HTML5中的<video>标签有更深刻的了解,敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo

作者:Luoshengyang 发表于2016/8/8 1:00:30 原文链接
阅读:38276 评论:6 查看评论

Chromium为视频标签创建播放器的过程分析

$
0
0

       Chromium是通过WebKit解析网页内容的。当WebKit遇到<video>标签时,就会创建一个播放器实例。WebKit是平台无关的,而播放器实现是平台相关的。因此,WebKit并没有自己实现播放器,而仅仅是创建一个播放器接口。通过这个播放器接口,可以使用平台提供的播放器来播放视频的内容。这就简化了Chromium对视频标签的支持。本文接下来就分析Chromium为视频标签创建播放器的过程。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

       以Android平台为例,它的SDK提供了一个MediaPlayer接口,用来播放视频。Chromium的目标就是为网页中的每一个<video>标签创建一个MediaPlayer实例,如图1所示:


图1 Chromium为<video>标签创建MediaPlayer的过程

       首先,WebKit会为网页中的每一个<video>标签创建一个类型为HTMLMediaElement的DOM节点。HTMLMediaElement类内部有一个WebMediaPlayerClientImpl接口。这个WebMediaPlayerClientImpl接口指向的是一个运行在Render进程的Content模块中的一个WebMediaPlayerAndroid对象。这些WebMediaPlayerAndroid对象归一个称为RendererMediaPayerManager的对象管理。

       Render进程中的每一个WebMediaPlayerAndroid对象,在Browser进程中都有一个对应的WebMediaPlayerBridge对象。这些WebMediaPlayerBridge对象归一个称为BrowserMediaPayerManager的对象管理。每一个WebMediaPlayerBridge对象在Java层中又都对应有一个MediaPlayer对象。这些MediaPlayer对象描述的就是Android平台提供的播放器。

       接下来,我们就从WebKit解析<video>标签的属性开始,分析Chromium为它们创建MediaPlayer的过程,如下所示:

void HTMLMediaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
{
    if (name == srcAttr) {
        // Trigger a reload, as long as the 'src' attribute is present.
        if (!value.isNull()) {
            ......
            scheduleDelayedAction(LoadMediaResource);
        }
    } 

    ......
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。

       WebKit为网页的每一个标签创建了相应的DOM节点之后,就会调用这个DOM节点的成员函数parseAtrribute对它的属性进行解析。从前面Chromium网页DOM Tree创建过程分析一文可以容易知道,WebKit为<video>标签创建的DOM节点的实际类型为HTMLVideoElement。HTMLVideoElement类是从HTMLMediaElement类继承下来的,WebKit是调用后者的成同员函数parseAttribute来解析<video>的属性。

       我们假设<video>标签通过src属性设置了要播放的视频文件的URL。这时候HTMLMediaElement类的成员函数parseAttribute就会调用另外一个成员函数scheduleDelayedAction为<video>标签创建播放器,如下所示:

void HTMLMediaElement::scheduleDelayedAction(DelayedActionType actionType)
{
    .....

    if ((actionType & LoadMediaResource) && !(m_pendingActionFlags & LoadMediaResource)) {
        prepareForLoad();
        m_pendingActionFlags |= LoadMediaResource;
    }

    ......

    if (!m_loadTimer.isActive())
        m_loadTimer.startOneShot(0, FROM_HERE);
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。

       从前面的调用过程可以知道,参数actionType的值等于LoadMediaResource。这时候如果HTMLMediaElement类的成员变量m_pendingActionFlags的LoadMediaResource位等于0,那么就说明WebKit还没有为当前正在解析的<video>标签创建过播放器接口。于是接下来就会做两件事情:

       1. 调用另外一个成员函数prepareForLoad开始为当前正在解析的<video>标签创建图1所示的WebMediaPlayerClientImpl接口;

       2. 将成员变量m_pendingActionFlags的LoadMediaResource位设置为1,表示WebKit正在为当前正在解析的<video>标签创建过播放器接口,避免接下来出现重复创建的情况。

       HTMLMediaElement类的另外一个成员变量m_loadTimer描述的是一个定时器。如果这个定时器还没有被启动,那么HTMLMediaElement类的成员函数scheduleDelayedAction就会调用它的成员函数startOneShot马上进行启动。指定的启动时间单位为0,这意味着这个定时器会马上超时。超时后它将会调用HTMLMediaElement类的成员函数loadTimerFired。HTMLMediaElement类的成员函数loadTimerFired将会继续创建图1所示的WebMediaPlayerAndroid、WebMediaPlayerBridge和MediaPlayer接口。

       接下来我们就先分析HTMLMediaElement类的成员函数prepareForLoad的实现,如下所示:

void HTMLMediaElement::prepareForLoad()
{
    ......

    createMediaPlayer();

    ......
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。

       HTMLMediaElement类的成员函数prepareForLoad主要是调用另外一个成员函数createMediaPlayer为当前正在解析的<video>标签创建一个WebMediaPlayerClientImpl接口,如下所示:

void HTMLMediaElement::createMediaPlayer()
{
    ......

    m_player = MediaPlayer::create(this);

    ......
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。

       HTMLMediaElement类的成员函数createMediaPlayer调用MediaPlayer类的静态成员函数create创建了一个WebMediaPlayerClientImpl接口,并且保存在HTMLMediaElement类的成员变量m_player中。

       MediaPlayer类的静态成员函数create的实现如下所示:

static CreateMediaEnginePlayer createMediaEngineFunction = 0;

void MediaPlayer::setMediaEngineCreateFunction(CreateMediaEnginePlayer createFunction)
{
    ASSERT(createFunction);
    ASSERT(!createMediaEngineFunction);
    createMediaEngineFunction = createFunction;
}

PassOwnPtr<MediaPlayer> MediaPlayer::create(MediaPlayerClient* client)
{
    ASSERT(createMediaEngineFunction);
    return createMediaEngineFunction(client);
}
       这两个函数定义在文件external/chromium_org/third_party/WebKit/Source/platform/graphics/media/MediaPlayer.cpp中。

       MediaPlayer类的静态成员函数create调用全局变量createMediaEngineFunction描述的一个函数创建一个播放器接口返回给调用者。全局变量createMediaEngineFunction描述的函数实际上是WebMediaPlayerClientImpl类的静态成员函数create,它是通过调用MediaPlayer类的静态成员函数setMediaEngineCreateFunction设置的。

       WebMediaPlayerClientImpl类的静态成员函数create的实现如下所示:

PassOwnPtr<MediaPlayer> WebMediaPlayerClientImpl::create(MediaPlayerClient* client)
{
    return adoptPtr(new WebMediaPlayerClientImpl(client));
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebMediaPlayerClientImpl.cpp中。

       从这里可以看到,WebMediaPlayerClientImpl类的静态成员函数create返回的是一个WebMediaPlayerClientImpl对象。这个WebMediaPlayerClientImpl对象描述的就是WebKit层的播放器接口。

       这一步执行完成之后,WebKit就为当前正在解析的<video>标签创建了一个类型为WebMediaPlayerClientImpl的播放器接口。回到前面分析的HTMLMediaElement类的成员函数scheduleDelayedAction中,接下来它启动的定时器就会马上执行,也就HTMLMediaElement类的成员函数loadTimerFired会马上被调用。

       HTMLMediaElement类的成员函数loadTimerFired的实现如下所示:

void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*)
{
    ......

    if (m_pendingActionFlags & LoadMediaResource) {
        if (m_loadState == LoadingFromSourceElement)
            loadNextSourceChild();
        else
            loadInternal();
    }

    m_pendingActionFlags = 0;
} 
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。

       前面分析的HTMLMediaElement类的成员函数scheduleDelayedAction已经将成员变量m_pendingActionFlags的LoadMediaResource位设置为1。这时候HTMLMediaElement类的成员函数loadTimerFired就会检查HTMLMediaElement类的另外一个成员变量m_loadState的值是否等于LoadingFromSourceElement。如果等于,那么就说明当前正在解析的<video>标签通过子元素<source>指定要播放的视频的URL。否则的话,就通过属性src指定要播放的视频的URL。

       前面我们假定了当前正在解析的<video>标签通过属性src指定要播放的视频的URL,因此HTMLMediaElement类的成员函数loadTimerFired接下来就会调用成员函数loadInternal继续为其创建其它的播放器接口,如下所示:

void HTMLMediaElement::loadInternal()
{
    ......

    selectMediaResource();
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。

       HTMLMediaElement类的成员函数loadInternal调用另外一个成员函数selectMediaResource为当前正在解析的<video>标签选择当前要播放的视频的URL。确定了当前要播放的视频的URL之后,就会加载视频元数据。有了这些元数据之后,就可以为其创建真正的播放器。

       HTMLMediaElement类的成员函数selectMediaResource的实现如下所示:

void HTMLMediaElement::selectMediaResource()
{
    ......

    enum Mode { attribute, children };

    .......
    Mode mode = attribute;
    if (!fastHasAttribute(srcAttr)) {
        // Otherwise, if the media element does not have a src attribute but has a source
        // element child, then let mode be children and let candidate be the first such
        // source element child in tree order.
        if (HTMLSourceElement* element = Traversal<HTMLSourceElement>::firstChild(*this)) {
            mode = children;
            ......
        } 
        .....
    }

    ......

    if (mode == attribute) {
        ......

        // If the src attribute's value is the empty string ... jump down to the failed step below
        KURL mediaURL = getNonEmptyURLAttribute(srcAttr);
        ......

        loadResource(mediaURL, contentType, String());
        .....
        return;
    }

    ......
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。

       HTMLMediaElement类的成员函数selectMediaResource所做的事情就要确定是从当前正在解析的<video>标签的src属性获得要加载的视频的URL,还是从它的子元素<source>获得要加载的视频的URL。

       如果当前正在解析的<video>标签设置了src属性,那么就会优先从这个属性获得要加载的视频的URL。有了这个URL之后,HTMLMediaElement类的成员函数selectMediaResource就会调用另外一个成员函数loadResource加载它所描述的视频的元数据。

       在我们这个情景中,当前正在解析的<video>标签设置了src属性。因此,接下来我们就继续分析HTMLMediaElement类的成员函数loadResource的实现,如下所示:

void HTMLMediaElement::loadResource(const KURL& url, ContentType& contentType, const String& keySystem)
{
    ......

    m_currentSrc = url;
    ......
    bool attemptLoad = true;

    if (url.protocolIs(mediaSourceBlobProtocol)) {
        if (isMediaStreamURL(url.string())) {
            m_userGestureRequiredForPlay = false;
        } else {
            m_mediaSource = HTMLMediaSource::lookup(url.string());

            if (m_mediaSource) {
                if (!m_mediaSource->attachToElement(this)) {
                    // Forget our reference to the MediaSource, so we leave it alone
                    // while processing remainder of load failure.
                    m_mediaSource = nullptr;
                    attemptLoad = false;
                }
            }
        }
    }

    if (attemptLoad && canLoadURL(url, contentType, keySystem)) {
        .......

        if (!m_havePreparedToPlay && !autoplay() && m_preload == MediaPlayer::None) {
            ......
            deferLoad();
        } else {
            startPlayerLoad();
        }
    } 

    ......
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。

       HTMLMediaElement类的成员函数loadResource首先将当前要播放的视频的URL保存在成员变量m_currentSrc中,接下来判断该URL的协议部分是否为“blob”。协议“blob”是“Binary Large OBject”的缩写,表示一组二进制数据。例如,我们手头上有一组表示一个Image的二进制数据,这时候可以调用URL.createObjectURL函数为这组二进制数据创建一个blob协议地址,然后再将该地址设置为一个<img>标签的src,这样就可以将图像显示出来。

       通过blob协议,还可以描述媒体数据。在WebKit中,媒体数据可以通过Media Source或者Media Stream API描述。Media Source API的设计初衷是让JavaScript能动态产生媒体流,然后交给<video>标签播放。Media Stream API是为WebRTC设计的,不仅可以使用<video>标签播放从本地摄像头采集的图像,还可以播放从网络发送过来的实时媒体流。关于Media Source 和Media Steam API的更详细信息,可以参考Media Source ExtensionsMedia Capture and Streams这两篇文档。

       如果当前要播放的视频的URL的协议部分为“blob”,HTMLMediaElement类的成员函数loadResource首先会检查它描述的是否是一个Media Stream。如果是的话,那么就会将HTMLMediaElement类的成员变量m_userGestureRequiredForPlay设置为false,表示后台Tab网页的视频可以自动播放。Render进程有一个“disable-gesture-requirement-for-media-playback”选项。当这个选项的值设置为false时,HTMLMediaElement类的成员变量m_userGestureRequiredForPlay就会被设置为true,表示后台Tab网页的视频不可以自动播放。不过,如果要播放的视频是一个Media Stream,那么不会受到此限制。关于Render进程的“disable-gesture-requirement-for-media-playback”启动选项的更多信息,可以参考Chrome 47 offers a flag to disable defer media playback in background tabs一文。

       如果当前要播放的视频的URL的协议部分为“blob”,但是它描述的不是一个Media Stream API,那么HTMLMediaElement类的成员函数loadResource再检查它是否是一个Media Source。如果是的话,就会将该Media Source作为当前正在解析的<video>标签的播放源。在这种情况下,WebKit不需要从网络上下载媒体数据回来,只需要从指定的Media Source读取回来就可以了。这时候本地变量attemptLoad的值会被设置为false。在其余情况下,本地变量attemptLoad的值保持为初始值true。

       在本地变量attemptLoad的值为true的情况下,HTMLMediaElement类的成员函数loadResource会调用另外一个成员函数canLoadURL继续检查当前要播放的视频的URL描述的内容是否为多媒体数据,以及该多媒体使用的编码方式是否被支持。如果检查通过,HTMLMediaElement类的成员函数loadResource再判断当前解析的<video>标签的是否需要preload和autoplay。如果不需要,那么就会调用成员函数deferLoad延迟加载要播放的视频的内容。否则的话,就会调用成员函数startPlayerLoad马上加载要播放的视频的内容回来。

       我们假设当前解析的<video>标签设置了autoplay,因此接下来我们就继续分析HTMLMediaElement类的成员函数startPlayerLoad的实现,如下所示:

void HTMLMediaElement::startPlayerLoad()
{
    .......

    KURL requestURL = m_currentSrc;
    ......

    m_player->load(loadType(), requestURL, corsMode());
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。

       从前面的分析可以知道,HTMLMediaElement类的成员变量m_player指向的是一个WebMediaPlayerClientImpl对象。HTMLMediaElement类的成员函数startPlayerLoad主要是调用这个WebMediaPlayerClientImpl对象的成员函数load加载当前要播放的视频的内容。

       WebMediaPlayerClientImpl类的成员函数load的实现如下所示:

void WebMediaPlayerClientImpl::load(WebMediaPlayer::LoadType loadType, const WTF::String& url, WebMediaPlayer::CORSMode corsMode)
{
    ......

    KURL kurl(ParsedURLString, url);
    m_webMediaPlayer = createWebMediaPlayer(this, kurl, frame);
    ...... 
   
    m_webMediaPlayer->load(loadType, kurl, corsMode);
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebMediaPlayerClientImpl.cpp中。

       WebMediaPlayerClientImpl类的成员函数load首先调用函数createWebMediaPlayer请求运行在当前进程(Render进程)中的Content模块创建一个播放器接口。这个Content模块的播放器接口会保存在WebMediaPlayerClientImpl类的成员变量m_webMediaPlayer中。有了Content模块的播放器接口之后,WebMediaPlayerClientImpl类的成员函数load再调用它的成员函数load请求加载当前要播放的视频的内容。

       接下来我们先分析Content模块的播放器接口的创建过程,也就是函数createWebMediaPlayer的实现,如下所示:

static PassOwnPtr<WebMediaPlayer> createWebMediaPlayer(WebMediaPlayerClient* client, const WebURL& url, LocalFrame* frame)
{
    WebLocalFrameImpl* webFrame = WebLocalFrameImpl::fromFrame(frame);
    ......
    return adoptPtr(webFrame->client()->createMediaPlayer(webFrame, url, client));
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebMediaPlayerClientImpl.cpp中。

       函数createWebMediaPlayer首先获得一个类型为blink::WebFrameClient的接口。这个接口是由WebKit的使用者设置给WebKit的,以便WebKit可以通过它执行一些平台相关的功能。在我们这个情景中,WebKit的使用者即为Render进程中的Content模块。Content模块中的RenderFrameImpl类实现了该接口,并且会设置给WebKit。因此,函数createWebMediaPlayer接下来就会调用它的成员函数createMediaPlayer创建一个播放器接口。

       RenderFrameImpl类的成员函数createMediaPlayer的实现如下所示:

blink::WebMediaPlayer* RenderFrameImpl::createMediaPlayer(
    blink::WebLocalFrame* frame,
    const blink::WebURL& url,
    blink::WebMediaPlayerClient* client) {
  blink::WebMediaStream web_stream(
      blink::WebMediaStreamRegistry::lookupMediaStreamDescriptor(url));
  if (!web_stream.isNull())
    return CreateWebMediaPlayerForMediaStream(url, client);

#if defined(OS_ANDROID)
  return CreateAndroidWebMediaPlayer(url, client);
#else
  ......
#endif  // defined(OS_ANDROID)
}
       这个函数定义在文件external/chromium_org/content/renderer/render_frame_impl.cc中。

       RenderFrameImpl类的成员函数createMediaPlayer首先判断当前要播放的视频的URL描述的是否是一个Media Stream。如果是的话,那么就会调用成员函数CreateWebMediaPlayerForMediaStream创建一个类型为WebMediaPlayerMS的播放器。这个类型为WebMediaPlayerMS的播放器是在WebRTC中实现的。我们不考虑这种情况。

       如果当前要播放的视频的URL描述的不是一个Media Stream,那么RenderFrameImpl类的成员函数createMediaPlayer就会调用另外一个成员函数CreateAndroidWebMediaPlayer创建另外一种类型的播放器,如下所示:

WebMediaPlayer* RenderFrameImpl::CreateAndroidWebMediaPlayer(
      const blink::WebURL& url,
      WebMediaPlayerClient* client) {
  GpuChannelHost* gpu_channel_host =
      RenderThreadImpl::current()->EstablishGpuChannelSync(
          CAUSE_FOR_GPU_LAUNCH_VIDEODECODEACCELERATOR_INITIALIZE);
  ......

  scoped_refptr<StreamTextureFactory> stream_texture_factory;
  if (SynchronousCompositorFactory* factory =
          SynchronousCompositorFactory::GetInstance()) {
    stream_texture_factory = factory->CreateStreamTextureFactory(routing_id_);
  } else {
    scoped_refptr<webkit::gpu::ContextProviderWebContext> context_provider =
        RenderThreadImpl::current()->SharedMainThreadContextProvider();
    ......

    stream_texture_factory = StreamTextureFactoryImpl::Create(
        context_provider, gpu_channel_host, routing_id_);
  }

  return new WebMediaPlayerAndroid(
      frame_,
      client,
      weak_factory_.GetWeakPtr(),
      GetMediaPlayerManager(),
      GetCdmManager(),
      stream_texture_factory,
      RenderThreadImpl::current()->GetMediaThreadMessageLoopProxy(),
      new RenderMediaLog());
}
       这个函数定义在文件external/chromium_org/content/renderer/render_frame_impl.cc中。

       RenderFrameImpl类的成员函数CreateAndroidWebMediaPlayer首先是调用RenderThreadImpl类的成员函数EstablishGpuChannelSync为接下来要创建的播放器创建一个到GPU进程的GPU通道。之所以要创建这个GPU通道,是因为Render要在GPU进程中创建一个纹理,然后将这个纹理封装为一个SurfaceTexture,作为Android平台的MediaPlayer的解码输出。也就是说,Render进程会通过这个SurfaceTexture接收Android平台的MediaPlayer的解码输出,然后再以纹理的形式渲染在网页上。GPU通道的创建过程,也就是RenderThreadImpl类的成员函数EstablishGpuChannelSync的实现,可以参考前面Chromium的GPU进程启动过程分析一文。

       RenderFrameImpl类的成员函数CreateAndroidWebMediaPlayer最终创建的是一个类型为WebMediaPlayerAndroid的播放器。创建这个类型为WebMediaPlayerAndroid的播放器需要用到两个重要的对象。一个是StreamTextureFactory对象,另一个是RendererMediaPlayerManager对象。前者用来创建前面所述的纹理。后者用来管理在Render进程中创建的播放器实例。

       在WebView的情况下,调用SynchronousCompositorFactory类的静态成员函数GetInstance会获得一个SynchronousCompositorFactory对象。在这种情况下,上述StreamTextureFactory对象通过调用这个SynchronousCompositorFactory对象的成员函数CreateStreamTextureFactory获得。我们不考虑这一种情况。

       在独立App的情况下,上述StreamTextureFactory对象实际上是一个StreamTextureFactoryImpl对象,它是通过调用StreamTextureFactoryImpl类的静态成员函数Create创建的,如下所示:

scoped_refptr<StreamTextureFactoryImpl> StreamTextureFactoryImpl::Create(
    const scoped_refptr<cc::ContextProvider>& context_provider,
    GpuChannelHost* channel,
    int frame_id) {
  return new StreamTextureFactoryImpl(context_provider, channel, frame_id);
}
       这个函数定义在文件external/chromium_org/content/renderer/media/android/stream_texture_factory_impl.cc中。

       从这里可以看到,StreamTextureFactoryImpl类的静态成员函数Create返回的是一个StreamTextureFactoryImpl对象。

       回到前面分析的RenderFrameImpl类的成员函数CreateAndroidWebMediaPlayer中,它创建了一个StreamTextureFactoryImpl对象之后,接下来又会调用另外一个成员函数GetMediaPlayerManager获得一个RendererMediaPlayerManager对象,如下所示:

RendererMediaPlayerManager* RenderFrameImpl::GetMediaPlayerManager() {
  if (!media_player_manager_) {
    media_player_manager_ = new RendererMediaPlayerManager(this);
    ......
  }
  return media_player_manager_;
}
       这个函数定义在文件external/chromium_org/content/renderer/render_frame_impl.cc中。

       从这里可以看到,RenderFrameImpl类的成员函数GetMediaPlayerManager返回的是成员变量media_player_manager_指向的是一个RendererMediaPlayerManager对象。如果这个RendererMediaPlayerManager对象还没有创建,那么就会先进行创建。

       再回到前面分析的RenderFrameImpl类的成员函数CreateAndroidWebMediaPlayer中,有了一个StreamTextureFactoryImpl对象和一个RendererMediaPlayerManager对象之后,它就会创建一个类型为WebMediaPlayerAndroid的播放器。

       类型为WebMediaPlayerAndroid的播放器的创建过程,也就是WebMediaPlayerAndroid类的构造函数的实现,如下所示:

WebMediaPlayerAndroid::WebMediaPlayerAndroid(
    blink::WebFrame* frame,
    blink::WebMediaPlayerClient* client,
    base::WeakPtr<WebMediaPlayerDelegate> delegate,
    RendererMediaPlayerManager* player_manager,
    RendererCdmManager* cdm_manager,
    scoped_refptr<StreamTextureFactory> factory,
    const scoped_refptr<base::MessageLoopProxy>& media_loop,
    media::MediaLog* media_log)
    : ......,
      player_manager_(player_manager),
      ......,
      stream_texture_factory_(factory),
      ...... {

  ......

  player_id_ = player_manager_->RegisterMediaPlayer(this);
  ......

  TryCreateStreamTextureProxyIfNeeded();
}
       这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。

       WebMediaPlayerAndroid类的构造函数首先是将参数player_manager和factory描述的StreamTextureFactoryImpl对象和RendererMediaPlayerManager对象分别保存在成员变量player_manager_和stream_texture_factory_中。

       WebMediaPlayerAndroid类的构造函数接下来又会做两件事情:

       1. 调用上述RendererMediaPlayerManager对象的成员函数RegisterMediaPlayer将当前正在创建的WebMediaPlayerAndroid对象保存在其内部,并且为其分配一个ID。以后通过这个ID就可以在该RendererMediaPlayerManager对象中找到当前正在创建的WebMediaPlayerAndroid对象。

       2. 调用另外一个成员函数TryCreateStreamTextureProxyIfNeeded创建一个SurfaceTexture,以便后面可以用来接收Android平台的MediaPlayer的解码输出。

       关于WebMediaPlayerAndroid类的成员函数TryCreateStreamTextureProxyIfNeeded创建SurfaceTexture的过程,我们在接下来一篇文章中分析<video>标签的视频渲染过程时再分析。

       这一步执行完成之后,运行在Render进程中的Content模块就创建了一个类型为WebMediaPlayerAndroid的播放器接口。这个播放器接口会返回给WebKit层的WebMediaPlayerClientImpl类的成员函数load。WebMediaPlayerClientImpl类的成员函数load获得了这个播放器接口之后,就会调用它的成员函数load加载当前要播放的视频的内容,如下所示:

void WebMediaPlayerAndroid::load(LoadType load_type,
                                 const blink::WebURL& url,
                                 CORSMode cors_mode) {
  ......

  switch (load_type) {
    case LoadTypeURL:
      player_type_ = MEDIA_PLAYER_TYPE_URL;
      break;

    case LoadTypeMediaSource:
      player_type_ = MEDIA_PLAYER_TYPE_MEDIA_SOURCE;
      break;

    case LoadTypeMediaStream:
      CHECK(false) << "WebMediaPlayerAndroid doesn't support MediaStream on "
                      "this platform";
      return;
  }

  url_ = url;
  int demuxer_client_id = 0;
  if (player_type_ != MEDIA_PLAYER_TYPE_URL) {
    RendererDemuxerAndroid* demuxer =
        RenderThreadImpl::current()->renderer_demuxer();
    demuxer_client_id = demuxer->GetNextDemuxerClientID();

    media_source_delegate_.reset(new MediaSourceDelegate(
        demuxer, demuxer_client_id, media_loop_, media_log_));

    if (player_type_ == MEDIA_PLAYER_TYPE_MEDIA_SOURCE) {
      media::SetDecryptorReadyCB set_decryptor_ready_cb =
          media::BindToCurrentLoop(
              base::Bind(&WebMediaPlayerAndroid::SetDecryptorReadyCB,
                         weak_factory_.GetWeakPtr()));

      media_source_delegate_->InitializeMediaSource(
          base::Bind(&WebMediaPlayerAndroid::OnMediaSourceOpened,
                     weak_factory_.GetWeakPtr()),
          base::Bind(&WebMediaPlayerAndroid::OnNeedKey,
                     weak_factory_.GetWeakPtr()),
          set_decryptor_ready_cb,
          base::Bind(&WebMediaPlayerAndroid::UpdateNetworkState,
                     weak_factory_.GetWeakPtr()),
          base::Bind(&WebMediaPlayerAndroid::OnDurationChanged,
                     weak_factory_.GetWeakPtr()));
      InitializePlayer(url_, frame_->document().firstPartyForCookies(),
                       true, demuxer_client_id);
    }
  } else {
    info_loader_.reset(
        new MediaInfoLoader(
            url,
            cors_mode,
            base::Bind(&WebMediaPlayerAndroid::DidLoadMediaInfo,
                       weak_factory_.GetWeakPtr())));
    info_loader_->Start(frame_);
  }

  ......
}
       这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。

       类型为WebMediaPlayerAndroid的播放器只能处理普通URL描述的媒体流,以及BLOB协议描述的类型为Media Source的媒体流,不能处理类型为Media Stream的媒体流(需要由WebRTC处理)。

       类型为Media Source的媒体流数据直接从指定的Media Source获得。WebMediaPlayerAndroid类的成员函数load将该Media Source封装在一个MediaSourceDelegate对象中,然后通过这个MediaSourceDelegate对象来获得要播放的视频的元数据。有了要播放的视频的元数据之后,就可以调用WebMediaPlayerAndroid类的成员函数InitializePlayer初始化当前正在处理的播放器。

       我们假设当前要播放的视频的URL是一个普通的URL,它描述的媒体流的元数据要通过一个MediaInfoLoader对象从网络上下载回来。下载完成后,WebMediaPlayerAndroid类的成员函数DidLoadMediaInfo会被调用,它的实现如下所示:

void WebMediaPlayerAndroid::DidLoadMediaInfo(
    MediaInfoLoader::Status status,
    const GURL& redirected_url,
    const GURL& first_party_for_cookies,
    bool allow_stored_credentials) {
  ......

  InitializePlayer(
      redirected_url, first_party_for_cookies, allow_stored_credentials, 0);

  ......
}
       这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。

       WebMediaPlayerAndroid类的成员函数DidLoadMediaInfo会用下载回来的视频元数据初始化当前正在处理的播放器。这同样是通过调用WebMediaPlayerAndroid类的成员函数InitializePlayer进行的,如下所示:

void WebMediaPlayerAndroid::InitializePlayer(
    const GURL& url,
    const GURL& first_party_for_cookies,
    bool allow_stored_credentials,
    int demuxer_client_id) {
  ......
  player_manager_->Initialize(
      player_type_, player_id_, url, first_party_for_cookies, demuxer_client_id,
      frame_->document().url(), allow_stored_credentials);
  ......
}
       这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。

       从前面的分析可以知道,WebMediaPlayerAndroid类的成员变量player_manager_指向的是一个RendererMediaPlayerManager对象。WebMediaPlayerAndroid类的成员函数InitializePlayer调用这个RendererMediaPlayerManager对象的成员函数Initialize对当前正在处理的播放器进行初始化,如下所示:

void RendererMediaPlayerManager::Initialize(
    MediaPlayerHostMsg_Initialize_Type type,
    int player_id,
    const GURL& url,
    const GURL& first_party_for_cookies,
    int demuxer_client_id,
    const GURL& frame_url,
    bool allow_credentials) {
  MediaPlayerHostMsg_Initialize_Params media_player_params;
  media_player_params.type = type;
  media_player_params.player_id = player_id;
  media_player_params.demuxer_client_id = demuxer_client_id;
  media_player_params.url = url;
  media_player_params.first_party_for_cookies = first_party_for_cookies;
  media_player_params.frame_url = frame_url;
  media_player_params.allow_credentials = allow_credentials;

  Send(new MediaPlayerHostMsg_Initialize(routing_id(), media_player_params));
}
       这个函数定义在文件external/chromium_org/content/renderer/media/android/renderer_media_player_manager.cc中。

       RendererMediaPlayerManager对象的成员函数Initialize向Browser进程发送一个类型为MediaPlayerHostMsg_Initialize的IPC消息,用来请求Browser进程为当前正在处理的类型为WebMediaPlayerAndroid的播放器创建一个由Android平台实现的播放器。

       Browser进程是通过MediaWebContentsObserver类的成员函数OnMediaPlayerMessageReceived接收类型为MediaPlayerHostMsg_Initialize的IPC消息的,如下所示:

bool MediaWebContentsObserver::OnMediaPlayerMessageReceived(
    const IPC::Message& msg,
    RenderFrameHost* render_frame_host) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(MediaWebContentsObserver, msg)
    ......
    IPC_MESSAGE_FORWARD(MediaPlayerHostMsg_Initialize,
                        GetMediaPlayerManager(render_frame_host),
    ......
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}
       这个函数定义在文件external/chromium_org/content/browser/media/media_web_contents_observer.cc中。

       MediaWebContentsObserver类的成员函数OnMediaPlayerMessageReceived会将类型为MediaPlayerHostMsg_Initialize的IPC消息分发给运行在Browser进程中的一个BrowserMediaPlayerManager对象的成员函数OnInitialize处理。这个BrowserMediaPlayerManager对象负责管理在Browser进程中创建的播放器实例,它与运行在Render进程中的RendererMediaPlayerManager对象是对应的,不过后者用来管理在Render进程中创建的播放器实例。

       BrowserMediaPlayerManager类的成员函数OnInitialize的实现如下所示:

void BrowserMediaPlayerManager::OnInitialize(
    const MediaPlayerHostMsg_Initialize_Params& media_player_params) {
  ......

  MediaPlayerAndroid* player = CreateMediaPlayer(
      media_player_params,

      host->GetBrowserContext()->IsOffTheRecord(), this,
      host->browser_demuxer_android());

  ......

  AddPlayer(player);
}

       这个函数定义在文件external/chromium_org/content/browser/media/android/browser_media_player_manager.cc中。

       BrowserMediaPlayerManager类的成员函数OnInitialize主要是调用成员函数CreateMediaPlayer创建一个类型为MediaPlayerBridge的播放器实例,并且调用另外一个成员函数AddPlayer将这个播放器实例保存在内部。

       BrowserMediaPlayerManager类的成员函数CreateMediaPlayer的实现如下所示:

MediaPlayerAndroid* BrowserMediaPlayerManager::CreateMediaPlayer(
    const MediaPlayerHostMsg_Initialize_Params& media_player_params,
    bool hide_url_log,
    MediaPlayerManager* manager,
    BrowserDemuxerAndroid* demuxer) {
  switch (media_player_params.type) {
    case MEDIA_PLAYER_TYPE_URL: {
      const std::string user_agent = GetContentClient()->GetUserAgent();
      MediaPlayerBridge* media_player_bridge = new MediaPlayerBridge(
          media_player_params.player_id,
          media_player_params.url,
          media_player_params.first_party_for_cookies,
          user_agent,
          hide_url_log,
          manager,
          base::Bind(&BrowserMediaPlayerManager::OnMediaResourcesRequested,
                     weak_ptr_factory_.GetWeakPtr()),
          base::Bind(&BrowserMediaPlayerManager::OnMediaResourcesReleased,
                     weak_ptr_factory_.GetWeakPtr()),
          media_player_params.frame_url,
          media_player_params.allow_credentials);
      BrowserMediaPlayerManager* browser_media_player_manager =
          static_cast<BrowserMediaPlayerManager*>(manager);
      ContentViewCoreImpl* content_view_core_impl =
          static_cast<ContentViewCoreImpl*>(ContentViewCore::FromWebContents(
              browser_media_player_manager->web_contents_));
      if (!content_view_core_impl) {
        // May reach here due to prerendering. Don't extract the metadata
        // since it is expensive.
        // TODO(qinmin): extract the metadata once the user decided to load
        // the page.
        browser_media_player_manager->OnMediaMetadataChanged(
            media_player_params.player_id, base::TimeDelta(), 0, 0, false);
      } else if (!content_view_core_impl->ShouldBlockMediaRequest(
            media_player_params.url)) {
        media_player_bridge->Initialize();
      }
      return media_player_bridge;
    }

    case MEDIA_PLAYER_TYPE_MEDIA_SOURCE: {
      return new MediaSourcePlayer(
          media_player_params.player_id,
          manager,
          base::Bind(&BrowserMediaPlayerManager::OnMediaResourcesRequested,
                     weak_ptr_factory_.GetWeakPtr()),
          base::Bind(&BrowserMediaPlayerManager::OnMediaResourcesReleased,
                     weak_ptr_factory_.GetWeakPtr()),
          demuxer->CreateDemuxer(media_player_params.demuxer_client_id),
          media_player_params.frame_url);
    }
  }

  NOTREACHED();
  return NULL;
}

       这个函数定义在文件external/chromium_org/content/browser/media/android/browser_media_player_manager.cc中。

       BrowserMediaPlayerManager类的成员函数CreateMediaPlayer同样是只会为普通URL描述的视频以及类型为Media Source的视频创建播放器。这里我们只考虑普通URL描述的视频的情况。这时候BrowserMediaPlayerManager类的成员函数CreateMediaPlayer会创建一个类型为MediaPlayerBridge的播放器,并且在视频所加载在的网页可见的情况下,调用它的成员函数Initialize对它进行初始化,也就是获取视频元数据。如果视频所加载在的网页当前不可见,那么获取视频元数据的操作可以延后执行,也就是等到下次可见时再执行。

       我们假设视频所加载在的网页当前是可见的。接下来我们就继续分析类型为MediaPlayerBridge的播放器的初始化过程,也就是MediaPlayerBridge类的成员函数Initialize的实现,如下所示:

void MediaPlayerBridge::Initialize() {
  ......

  media::MediaResourceGetter* resource_getter =
      manager()->GetMediaResourceGetter();
  ......

  // Start extracting the metadata immediately if the request is anonymous.
  // Otherwise, wait for user credentials to be retrieved first.
  if (!allow_credentials_) {
    ExtractMediaMetadata(url_.spec());
    return;
  }

  resource_getter->GetCookies(url_,
                              first_party_for_cookies_,
                              base::Bind(&MediaPlayerBridge::OnCookiesRetrieved,
                                         weak_factory_.GetWeakPtr()));
}
       这个函数定义在文件external/chromium_org/media/base/android/media_player_bridge.cc中。

       MediaPlayerBridge类的成员变量allow_credentials_是一个布尔变量。当它的值等于false的时候,表示视频元数据可以通过匿名方式获取。在这种情况下,MediaPlayerBridge类的成员函数Initialize就会直接调用另外一个成员函数ExtractMediaMetadata获取视频元数据。

       当MediaPlayerBridge类的成员变量allow_credentials_的值等于true的时候,表示视频元数据要有凭证才可以获取。在这种情况下,MediaPlayerBridge类的成员函数Initialize先通过调用一个MediaResourceGetter对象的成员函数GetCookies获取该凭证。获得了这个凭证之后,MediaPlayerBridge类的成员函数OnCookiesRetrieved会被回调。

       MediaPlayerBridge类的成员函数OnCookiesRetrieved被回调的时候,它同样是会调用成员函数ExtractMediaMetadata去获取视频元数据。为简单起见,我们假设视频元数据可以通过匿名方式获取。因此,接下来我们就继续分析MediaPlayerBridge类的成员函数ExtractMediaMetadata的实现,以及了解视频元数据获取的过程,如下所示:

void MediaPlayerBridge::ExtractMediaMetadata(const std::string& url) {
  int fd;
  int64 offset;
  int64 size;
  if (InterceptMediaUrl(url, &fd, &offset, &size)) {
    manager()->GetMediaResourceGetter()->ExtractMediaMetadata(
        fd, offset, size,
        base::Bind(&MediaPlayerBridge::OnMediaMetadataExtracted,
                   weak_factory_.GetWeakPtr()));
  } else {
    manager()->GetMediaResourceGetter()->ExtractMediaMetadata(
        url, cookies_, user_agent_,
        base::Bind(&MediaPlayerBridge::OnMediaMetadataExtracted,
                   weak_factory_.GetWeakPtr()));
  }
}
       这个函数定义在文件external/chromium_org/media/base/android/media_player_bridge.cc中。

       MediaPlayerBridge类的成员函数ExtractMediaMetadata首先是调用成员函数InterceptMediaUrl检查当前要播放的视频是否已经存在本地,也就是之前下载过。如果是的话,就会直接从该文件读取视频元数据回来。否则的话,就会通过网络请求视频元数据。无论是哪一种方式,一旦得到视频元数据之后,就会回调用MediaPlayerBridge类的成员函数OnMediaMetadataExtracted进行后续处理。

       MediaPlayerBridge类的成员函数OnMediaMetadataExtracted的实现如下所示:

void MediaPlayerBridge::OnMediaMetadataExtracted(
    base::TimeDelta duration, int width, int height, bool success) {
  if (success) {
    duration_ = duration;
    width_ = width;
    height_ = height;
  }
  manager()->OnMediaMetadataChanged(
      player_id(), duration_, width_, height_, success);
}
       这个函数定义在文件external/chromium_org/media/base/android/media_player_bridge.cc中。

       获取的视频元数据包括视频持续时间、宽度以及高度。这些数据会分别保存在MediaPlayerBridge类的成员变量duration_、width_和height_中。

       MediaPlayerBridge类的成员函数OnMediaMetadataExtracted最后还会通知Browser进程中的BrowserMediaPlayerManager对象,当前正在处理的播放器已经获得了视频元数据。这个BrowserMediaPlayerManager对象是通过调用MediaPlayerBridge类的成员函数manager获得的,并且是通过调用它的成员函数OnMediaMetadataChanged对它进行通知的。

       BrowserMediaPlayerManager类的成员函数OnMediaMetadataChanged的实现如下所示:

void BrowserMediaPlayerManager::OnMediaMetadataChanged(
    int player_id, base::TimeDelta duration, int width, int height,
    bool success) {
  Send(new MediaPlayerMsg_MediaMetadataChanged(
      RoutingID(), player_id, duration, width, height, success));
  ......
}
       这个函数定义在文件external/chromium_org/content/browser/media/android/browser_media_player_manager.cc中。

       BrowserMediaPlayerManager类的成员函数OnMediaMetadataChanged主要是向Render进程发送一个类型为MediaPlayerMsg_MediaMetadataChanged的IPC消息,通知ID为player_id的播放器已经获得了要播放的视频的元数据。

       Render进程是通过RendererMediaPlayerManager类的成员函数OnMessageReceived接收类型为MediaPlayerMsg_MediaMetadataChanged的IPC消息的,如下所示:

bool RendererMediaPlayerManager::OnMessageReceived(const IPC::Message& msg) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(RendererMediaPlayerManager, msg)
    IPC_MESSAGE_HANDLER(MediaPlayerMsg_MediaMetadataChanged,
                        OnMediaMetadataChanged)
    ......
  IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}
       这个函数定义在文件external/chromium_org/content/renderer/media/android/renderer_media_player_manager.cc中。

       RendererMediaPlayerManager类的成员函数OnMessageReceived将类型为MediaPlayerMsg_MediaMetadataChanged的IPC消息分发给另外一个成员函数OnMediaMetadataChanged处理,如下所示:

void RendererMediaPlayerManager::OnMediaMetadataChanged(
    int player_id,
    base::TimeDelta duration,
    int width,
    int height,
    bool success) {
  WebMediaPlayerAndroid* player = GetMediaPlayer(player_id);
  if (player)
    player->OnMediaMetadataChanged(duration, width, height, success);
}
       这个函数定义在文件external/chromium_org/content/renderer/media/android/renderer_media_player_manager.cc中。

       RendererMediaPlayerManager类的成员函数OnMediaMetadataChanged首先根据参数player_id获得之前创建的一个类型为WebMediaPlayerAndroid的播放器,然后调用这个播放器的成员函数OnMediaMetadataChanged通知它,当前要播放的视频元数据已经获取回来了。

       WebMediaPlayerAndroid类的成员函数OnMediaMetadataChanged的实现如下所示:

void WebMediaPlayerAndroid::OnMediaMetadataChanged(
    const base::TimeDelta& duration, int width, int height, bool success) {
  ......

  if (ready_state_ != WebMediaPlayer::ReadyStateHaveEnoughData) {
    UpdateReadyState(WebMediaPlayer::ReadyStateHaveMetadata);
    UpdateReadyState(WebMediaPlayer::ReadyStateHaveEnoughData);
  }

  ......
}
       这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。

       WebMediaPlayerAndroid类的成员变量read_state_描述的播放器的状态。如果它的状态不等于WebMediaPlayer::ReadyStateHaveEnoughData,即还没有足够的数据开始播放视频,那么WebMediaPlayerAndroid类的成员函数OnMediaMetadataChanged此时就会先将状态更改为WebMediaPlayer::ReadyStateHaveMetadata,然后再更改为WebMediaPlayer::ReadyStateHaveEnoughData。这都是通过调用WebMediaPlayerAndroid类的成员函数UpdateReadyState实现的。

       WebMediaPlayerAndroid类的成员函数UpdateReadyState的实现如下所示:

void WebMediaPlayerAndroid::UpdateReadyState(
    WebMediaPlayer::ReadyState state) {
  ready_state_ = state;
  client_->readyStateChanged();
}
       这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。

       WebMediaPlayerAndroid类的成员函数UpdateReadyState除了将Content模块中的播放器的状态设置为参数state描述的值之外,还会通知WebKit层中的播放器,也就是一个WebMediaPlayerClientImpl对象,它的状态发生了变化。

       上述WebMediaPlayerClientImpl对象保存在WebMediaPlayerAndroid类的成员变量client_中,通过调用它的成员函数readyStateChanged可以通知它,播放器状态发生了变化,如下所示:

void WebMediaPlayerClientImpl::readyStateChanged()
{
    m_client->mediaPlayerReadyStateChanged();
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebMediaPlayerClientImpl.cpp中。

       从前面的分析可以知道,WebMediaPlayerClientImpl类的成员变量m_client指向的是一个HTMLMediaElement对象。这个HTMLMediaElement对象描述的就是要播放视频的<video>标签。WebMediaPlayerClientImpl类的成员函数readyStateChanged调用这个HTMLMediaElement对象的成员函数mediaPlayerReadyStateChanged通知它,播放器状态发生了变化,如下所示:

void HTMLMediaElement::mediaPlayerReadyStateChanged()
{
    setReadyState(static_cast<ReadyState>(webMediaPlayer()->readyState()));
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。

       HTMLMediaElement类的成员函数mediaPlayerReadyStateChanged首先获得Content模块中的播放器的当前状态,然后再调用另外一个成员函数setReadyState将这个状态保存在内部。从前面的分析可以知道,Content模块中的播放器的当前状态为WebMediaPlayer::ReadyStateHaveEnoughData,对应于在WebKit中定义的状态HAVE_ENOUGH_DATA。

       HTMLMediaElement类的成员函数setReadyState的实现如下所示:

void HTMLMediaElement::setReadyState(ReadyState state)
{
    ......

    bool tracksAreReady = textTracksAreReady();
    ......

    if (tracksAreReady)
        m_readyState = newState;
    else {
        // If a media file has text tracks the readyState may not progress beyond HAVE_FUTURE_DATA until
        // the text tracks are ready, regardless of the state of the media file.
        if (newState <= HAVE_METADATA)
            m_readyState = newState;
        else
            m_readyState = HAVE_CURRENT_DATA;
    }

    ......

    updatePlayState();
    
    ......
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。

       HTMLMediaElement类的成员变量m_readyState用来描述WebKit层中的播放器的状态。HTMLMediaElement类的成员函数setReadyState首先调用成员函数textTracksAreReady检查当前要播放的视频是否存在类型为Text的Track,也就是字幕。如果存在,并且这些Track都已经Ready,那么它的返回值就会等于true。另一方面,如果不存在类型为Text的Track,那么调用HTMLMediaElement类的成员函数textTracksAreReady得到的返回值也会等于true。

       在HTMLMediaElement类的成员函数textTracksAreReady的返回值等于true的情况下,HTMLMediaElement类的成员函数setReadyState才会将WebKit层中的播放器的状态设置为参数state的值,也就是将它的状态保持与Content模块中的播放器一致。否则的话,至多会将WebKit层中的播放器的状态设置为HAVE_CURRENT_DATA。这个HAVE_CURRENT_DATA状态不能让播放器开始播放视频。

       我们假设当前要播放的视频不存在类型为Text的Track。因此,这时候WebKit层中的播放器的状态将会被设置为HAVE_ENOUGH_DATA,也就是HTMLMediaElement类的成员变量m_readyState会被设置为HAVE_ENOUGH_DATA。

       HTMLMediaElement类的成员函数setReadyState最后调用成员函数updatePlayState开始播放视频,如下所示:

void HTMLMediaElement::updatePlayState()
{
    ......

    bool shouldBePlaying = potentiallyPlaying();
    bool playerPaused = m_player->paused();

    .....

    if (shouldBePlaying) {
        ......

        if (playerPaused) {
            ......

            m_player->play();
        }

        ......
    }

    ......
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。

       HTMLMediaElement类的成员函数updatePlayState首先调用另外一个成员函数potentiallyPlaying检查WebKit层中的播放器的状态。如果WebKit层中的播放器的状态表明它已经获得了足够的视频数据,并且视频还没有播放结束,以及没有被用户中止,也没有出现错误等,那么HTMLMediaElement类的成员函数potentiallyPlaying的返回值就会等于true。在这种情况下,如果Content模块中的播放器目前处于暂停状态,那么就可以通知它开始播放视频了。这是通过调用HTMLMediaElement类的成员变量m_player指向的一个WebMediaPlayerClientImpl对象的成员函数play实现的。

       从前面的分析可以知道,在我们这个情景中,通知Content模块中的播放器开始播放视频的条件是满足的,因此接下来我们就继续分析WebMediaPlayerClientImpl类的成员函数play的实现,如下所示:

void WebMediaPlayerClientImpl::play()
{
    if (m_webMediaPlayer)
        m_webMediaPlayer->play();
}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebMediaPlayerClientImpl.cpp中。

       从前面的分析可以知道,WebMediaPlayerClientImpl类的成员变量m_webMediaPlayer指向的是一个WebMediaPlayerAndroid对象。这个WebMediaPlayerAndroid对象描述的就是Content模块中的播放器实例。WebMediaPlayerClientImpl类的成员函数play调用这个WebMediaPlayerAndroid对象的成员函数play通知它开始播放视频。

       WebMediaPlayerAndroid类的成员函数play的实现如下所示:

void WebMediaPlayerAndroid::play() {
  ......

  TryCreateStreamTextureProxyIfNeeded();
  // There is no need to establish the surface texture peer for fullscreen
  // video.
  if (hasVideo() && needs_establish_peer_ &&
      !player_manager_->IsInFullscreen(frame_)) {
    EstablishSurfaceTexturePeer();
  }

  if (paused())
    player_manager_->Start(player_id_);
  ......
}
       这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。

       WebMediaPlayerAndroid类的成员函数play首先调用成员函数TryCreateStreamTextureProxyIfNeeded检查当前正在处理的WebMediaPlayerAndroid对象是否已经创建过一个纹理对象。如果还没有创建,那么就会请求GPU进程进行创建。

       WebMediaPlayerAndroid类的成员函数play接下来又检查是否需要将上述纹理对象封装为一个SurfaceTexture,然后再将该SurfaceTexture设置为Android平台的MediaPlayer的解码输出。在满足以下两个条件时,就需要进行封装和设置,也就是调用另外一个成员函数EstablishSurfaceTexturePeer:

       1. 要播放的媒体包含有视频,即调用成员函数hasVideo得到的返回值等于true。

       2. 要播放的视频不是全屏模式,这时候成员变量needs_establish_peer_的值等于true,以及调用调用成员变量player_manager_指向的RendererMediaPlayerManager对象的成员函数IsInFullscreen得到的返回值等于false。

       在我们这个情景中,上述两个条件都是满足的。不过,WebMediaPlayerAndroid类的TryCreateStreamTextureProxyIfNeeded和EstablishSurfaceTexturePeer的实现我们在接下来的一篇文章中再详细分析。

       WebMediaPlayerAndroid类的成员函数play最后检查当前正在处理的WebMediaPlayerAndroid对象描述的播放器是否处于暂停状态。如果是的话,那么就会调用成员变量player_manager_指向的RendererMediaPlayerManager对象的成员函数Start启动该播放器。

       接下来我们就继续分析RendererMediaPlayerManager类的成员函数Start的实现,以便了解类型为WebMediaPlayerAndroid的播放器的启动过程,如下所示:

void RendererMediaPlayerManager::Start(int player_id) {
  Send(new MediaPlayerHostMsg_Start(routing_id(), player_id));
}
       这个函数定义在文件external/chromium_org/content/renderer/media/android/renderer_media_player_manager.cc中。

       RendererMediaPlayerManager类的成员函数Start向Browser进程发送一个类型为MediaPlayerHostMsg_Start的IPC消息,用来请求启动ID值为player_id的播放器。

       Browser进程通过MediaWebContentsObserver类的成员函数OnMediaPlayerMessageReceived接收类型为MediaPlayerHostMsg_Start的IPC消息,如下所示:

bool MediaWebContentsObserver::OnMediaPlayerMessageReceived(
    const IPC::Message& msg,
    RenderFrameHost* render_frame_host) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(MediaWebContentsObserver, msg)
    ......
    IPC_MESSAGE_FORWARD(MediaPlayerHostMsg_Start,
                        GetMediaPlayerManager(render_frame_host),
                        BrowserMediaPlayerManager::OnStart)
    ......
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}
       这个函数定义在文件external/chromium_org/content/browser/media/media_web_contents_observer.cc中。

       MediaWebContentsObserver类的成员函数OnMediaPlayerMessageReceived会将类型为MediaPlayerHostMsg_Start的IPC消息分发给运行在Browser进程中的一个BrowserMediaPlayerManager对象的成员函数OnStart处理,如下所示:

void BrowserMediaPlayerManager::OnStart(int player_id) {
  MediaPlayerAndroid* player = GetPlayer(player_id);
  ......
  player->Start();
  ......
}
       这个函数定义在文件external/chromium_org/content/browser/media/android/browser_media_player_manager.cc中。

       BrowserMediaPlayerManager类的成员函数OnStart首先调用成员函数GetPlayer获得与参数player_id对应的一个MediaPlayerBridge对象,然后调用这个MediaPlayerBridge对象的成员函数Start启动它所描述的播放器。注意,这里获得的MediaPlayerBridge对象使用一个类型为MediaPlayerAndroid的指针引用,这是因为MediaPlayerBridge类是从类MediaPlayerAndroid继承下来的。

       接下来我们就继续分析MediaPlayerBridge类的成员函数Start的实现,如下所示:

void MediaPlayerBridge::Start() {
  if (j_media_player_bridge_.is_null()) {
    pending_play_ = true;
    Prepare();
  } else {
    if (prepared_)
      StartInternal();
    else
      pending_play_ = true;
  }
}
       这个函数定义在文件external/chromium_org/media/base/android/media_player_bridge.cc中。

       MediaPlayerBridge类的成员变量j_media_player_bridge_是一个类型为jobject的引用,它是用来指向在Java层创建的一个MediaPlayerBridge对象的。如果这个MediaPlayerBridge对象还没有创建,那么MediaPlayerBridge类的成员变量j_media_player_bridge_的值就会等于NULL。这种情况说明我们还没有为<video>标签创建一个真正的播放器。%

作者:Luoshengyang 发表于2016/8/15 0:59:33 原文链接
阅读:42495 评论:18 查看评论
Viewing all 35570 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>