[转载]android ListView - 左眼跳跳 - 博客园

mikel阅读(935)

[转载]android ListView – 左眼跳跳 – 博客园.

由于google doc 很多人都打不开,故更新了源码下载地址 【源码下载】—-2011-01-18

Android开发中ListView是比较常用的组件,它以列表的形式展示具体内容,并且能够根据数据的长度自适应显示。抽空把对ListView的使用做了整理,并写了个小例子,如下图。

列表的显示需要三个元素:

1.ListVeiw 用来展示列表的View。

2.适配器 用来把数据映射到ListView上的中介。

3.数据    具体的将被映射的字符串,图片,或者基本组件。

根据列表的适配器类型,列表分为三种,ArrayAdapter,SimpleAdapter和SimpleCursorAdapter

其中以ArrayAdapter最为简单,只能展示一行字。SimpleAdapter有最好的扩充性,可以自定义出各种效果。 SimpleCursorAdapter可以认为是SimpleAdapter对数据库的简单结合,可以方面的把数据库的内容以列表的形式展示出来。

我们从最简单的ListView开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
 * @author allin
 *
 */
public class MyListView extends Activity {
    private ListView listView;
    //private List<String> data = new ArrayList<String>();
    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        
        listView = new ListView(this);
        listView.setAdapter(new ArrayAdapter<String>(this, Android.R.layout.simple_expandable_list_item_1,getData()));
        setContentView(listView);
    }
    
    
    
    private List<String> getData(){
        
        List<String> data = new ArrayList<String>();
        data.add("测试数据1");
        data.add("测试数据2");
        data.add("测试数据3");
        data.add("测试数据4");
        
        return data;
    }
}

上面代码使用了ArrayAdapter(Context context, int textViewResourceId, List<T> objects)来装配数据,要装配这些数据就需要一个连接ListView视图对象和数组数据的适配器来两者的适配工作,ArrayAdapter的 构造需要三个参数,依次为this,布局文件(注意这里的布局文件描述的是列表的每一行的布 局,android.R.layout.simple_list_item_1是系统定义好的布局文件只显示一行文字,数据源(一个List集合)。同时 用setAdapter()完成适配的最后工作。运行后的现实结构如下图:

SimpleCursorAdapter

sdk的解释是这样的:An easy adapter to map columns from a cursor to TextViews or ImageViews defined in an XML file. You can specify which columns you want, which views you want to display the columns, and the XML file that defines the appearance of these views。简单的说就是方便把从游标得到的数据进行列表显示,并可以把指定的列映射到对应的TextView中。

下面的程序是从电话簿中把联系人显示到类表中。先在通讯录中添加一个联系人作为数据库的数据。然后获得一个指向数据库的Cursor并且定义一个布局文件(当然也可以使用系统自带的)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
 * @author allin
 *
 */
public class MyListView2 extends Activity {
    private ListView listView;
    //private List<String> data = new ArrayList<String>();
    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        
        listView = new ListView(this);
        
        Cursor cursor = getContentResolver().query(People.CONTENT_URI, null, null, null, null);
        startManagingCursor(cursor);
        
        ListAdapter listAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_expandable_list_item_1,
                cursor,
                new String[]{People.NAME},
                new int[]{android.R.id.text1});
        
        listView.setAdapter(listAdapter);
        setContentView(listView);
    }
    
    
}

Cursor cursor = getContentResolver().query(People.CONTENT_URI, null, null, null, null);先获得一个指向系统通讯录数据库的Cursor对象获得数据来源。

startManagingCursor(cursor);我们将获得的Cursor对象交由Activity管理,这样Cursor的生命周期和Activity便能够自动同步,省去自己手动管理Cursor。

SimpleCursorAdapter 构造函数前面3个参数和ArrayAdapter是一样的,最后两个参数:一个包含数据库的列的String型数组,一个包含布局文件中对应组件id的 int型数组。其作用是自动的将String型数组所表示的每一列数据映射到布局文件对应id的组件上。上面的代码,将NAME列的数据一次映射到布局文 件的id为text1的组件上。

注意:需要在AndroidManifest.xml中如权限:<uses-permission android:name=”android.permission.READ_CONTACTS”></uses-permission>

运行后效果如下图:

SimpleAdapter

simpleAdapter的扩展性最好,可以定义各种各样的布局出来,可以放上ImageView(图片),还可以放上Button(按 钮),CheckBox(复选框)等等。下面的代码都直接继承了ListActivity,ListActivity和普通的Activity没有太大的 差别,不同就是对显示ListView做了许多优化,方面显示而已。

下面的程序是实现一个带有图片的类表。

首先需要定义好一个用来显示每一个列内容的xml

vlist.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <ImageView android:id="@+id/img"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5px"/>
    <LinearLayout android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        <TextView android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#FFFFFFFF"
            android:textSize="22px" />
        <TextView android:id="@+id/info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#FFFFFFFF"
            android:textSize="13px" />
    </LinearLayout>
</LinearLayout>

下面是实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
 * @author allin
 *
 */
public class MyListView3 extends ListActivity {
    // private List<String> data = new ArrayList<String>();
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SimpleAdapter adapter = new SimpleAdapter(this,getData(),R.layout.vlist,
                new String[]{"title","info","img"},
                new int[]{R.id.title,R.id.info,R.id.img});
        setListAdapter(adapter);
    }
    private List<Map<String, Object>> getData() {
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("title", "G1");
        map.put("info", "google 1");
        map.put("img", R.drawable.i1);
        list.add(map);
        map = new HashMap<String, Object>();
        map.put("title", "G2");
        map.put("info", "google 2");
        map.put("img", R.drawable.i2);
        list.add(map);
        map = new HashMap<String, Object>();
        map.put("title", "G3");
        map.put("info", "google 3");
        map.put("img", R.drawable.i3);
        list.add(map);
        
        return list;
    }
}

使用simpleAdapter的数据用一般都是HashMap构成的List,list的每一节对应ListView的每一行。HashMap的 每个键值数据映射到布局文件中对应id的组件上。因为系统没有对应的布局文件可用,我们可以自己定义一个布局vlist.xml。下面做适配,new一个 SimpleAdapter参数一次是:this,布局文件(vlist.xml),HashMap的 title 和 info,img。布局文件的组件id,title,info,img。布局文件的各组件分别映射到HashMap的各元素上,完成适配。

运行效果如下图:

有按钮的ListView

但是有时候,列表不光会用来做显示用,我们同样可以在在上面添加按钮。添加按钮首先要写一个有按钮的xml文件,然后自然会想到用上面的方法定义一 个适配器,然后将数据映射到布局文件上。但是事实并非这样,因为按钮是无法映射的,即使你成功的用布局文件显示出了按钮也无法添加按钮的响应,这时就要研 究一下ListView是如何现实的了,而且必须要重写一个类继承BaseAdapter。下面的示例将显示一个按钮和一个图片,两行字如果单击按钮将删 除此按钮的所在行。并告诉你ListView究竟是如何工作的。效果如下:

vlist2.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <ImageView android:id="@+id/img"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5px"/>
    <LinearLayout android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        <TextView android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#FFFFFFFF"
            android:textSize="22px" />
        <TextView android:id="@+id/info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#FFFFFFFF"
            android:textSize="13px" />
    </LinearLayout>
    <Button android:id="@+id/view_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/s_view_btn"
        android:layout_gravity="bottom|right" />
</LinearLayout>

程序代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/**
 * @author allin
 *
 */
public class MyListView4 extends ListActivity {
    private List<Map<String, Object>> mData;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mData = getData();
        MyAdapter adapter = new MyAdapter(this);
        setListAdapter(adapter);
    }
    private List<Map<String, Object>> getData() {
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("title", "G1");
        map.put("info", "google 1");
        map.put("img", R.drawable.i1);
        list.add(map);
        map = new HashMap<String, Object>();
        map.put("title", "G2");
        map.put("info", "google 2");
        map.put("img", R.drawable.i2);
        list.add(map);
        map = new HashMap<String, Object>();
        map.put("title", "G3");
        map.put("info", "google 3");
        map.put("img", R.drawable.i3);
        list.add(map);
        
        return list;
    }
    
    // ListView 中某项被选中后的逻辑
    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        
        Log.v("MyListView4-click", (String)mData.get(position).get("title"));
    }
    
    /**
     * listview中点击按键弹出对话框
     */
    public void showInfo(){
        new AlertDialog.Builder(this)
        .setTitle("我的listview")
        .setMessage("介绍...")
        .setPositiveButton("确定", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
            }
        })
        .show();
        
    }
    
    
    
    public final class ViewHolder{
        public ImageView img;
        public TextView title;
        public TextView info;
        public Button viewBtn;
    }
    
    
    public class MyAdapter extends BaseAdapter{
        private LayoutInflater mInflater;
        
        
        public MyAdapter(Context context){
            this.mInflater = LayoutInflater.from(context);
        }
        @Override
        public int getCount() {
            // TODO Auto-generated method stub
            return mData.size();
        }
        @Override
        public Object getItem(int arg0) {
            // TODO Auto-generated method stub
            return null;
        }
        @Override
        public long getItemId(int arg0) {
            // TODO Auto-generated method stub
            return 0;
        }
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            
            ViewHolder holder = null;
            if (convertView == null) {
                
                holder=new ViewHolder(); 
                
                convertView = mInflater.inflate(R.layout.vlist2, null);
                holder.img = (ImageView)convertView.findViewById(R.id.img);
                holder.title = (TextView)convertView.findViewById(R.id.title);
                holder.info = (TextView)convertView.findViewById(R.id.info);
                holder.viewBtn = (Button)convertView.findViewById(R.id.view_btn);
                convertView.setTag(holder);
                
            }else {
                
                holder = (ViewHolder)convertView.getTag();
            }
            
            
            holder.img.setBackgroundResource((Integer)mData.get(position).get("img"));
            holder.title.setText((String)mData.get(position).get("title"));
            holder.info.setText((String)mData.get(position).get("info"));
            
            holder.viewBtn.setOnClickListener(new View.OnClickListener() {
                
                @Override
                public void onClick(View v) {
                    showInfo();                
                }
            });
            
            
            return convertView;
        }
        
    }
    
    
    
    
}

下面将对上述代码,做详细的解释,listView在开始绘制的时候,系统首先调用getCount()函数,根据他的返回值得到 listView的长度(这也是为什么在开始的第一张图特别的标出列表长度),然后根据这个长度,调用getView()逐一绘制每一行。如果你的 getCount()返回值是0的话,列表将不显示同样return 1,就只显示一行。

系统显示列表时,首先实例化一个适配器(这里将实例化自定义的适配器)。当手动完成适配时,必须手动映射数据,这需要重写getView()方 法。系统在绘制列表的每一行的时候将调用此方法。getView()有三个参数,position表示将显示的是第几行,covertView是从布局文 件中inflate来的布局。我们用LayoutInflater的方法将定义好的vlist2.xml文件提取成View实例用来显示。然后将xml文 件中的各个组件实例化(简单的findViewById()方法)。这样便可以将数据对应到各个组件上了。但是按钮为了响应点击事件,需要为它添加点击监 听器,这样就能捕获点击事件。至此一个自定义的listView就完成了,现在让我们回过头从新审视这个过程。系统要绘制ListView了,他首先获得 要绘制的这个列表的长度,然后开始绘制第一行,怎么绘制呢?调用getView()函数。在这个函数里面首先获得一个View(实际上是一个 ViewGroup),然后再实例并设置各个组件,显示之。好了,绘制完这一行了。那 再绘制下一行,直到绘完为止。在实际的运行过程中会发现 listView的每一行没有焦点了,这是因为Button抢夺了listView的焦点,只要布局文件中将Button设置为没有焦点就OK了。

运行效果如下图:

源码下载

[转载]Web页面性能测试工具浅析 - fo0ol - 博客园

mikel阅读(1053)

[转载]Web页面性能测试工具浅析 – fo0ol – 博客园.

做Web开发,难免要对自己开发的页面进行性能检测,自己写工具检测,工作量太大。网上有几款比较成熟的检测工具,以下就介绍一下,与大家分享。

互联网现有工具

基于网页分析工具:

1.       阿里测

2.   百度应用性能检测中心

2.       Web PageTest

3.       PingDom Tools

4.       GTmetrix

 

基于浏览器分析工具:

1.       Chrome自带工具F12

2.       Firefox插件:YSlow(Yahoo工具)

3.       Page Speed(google)

 

(以下以分析博客园网站为例www.cnblogs.com)

阿里测:

http://www.alibench.com

首页:

一、性能打分

a)         首字节时间

指标解释:浏览器开始收到服务器响应数据的时间(后台处理时间+重定向时间)
评估方法:达标时间=DNS解析时间+创建连接时间+SSL认证时间+100ms. 比达标时间每慢10ms减1分.

b)         使用长连接(keep alive)

指标解释: 服务器开启长连接后针对同一域名的多个页面元素将会复用同一下载连接(socket)
评估方法:服务器是否返回了”Connection: keep-alive”HTTP响应头,或者浏览器通过同一连接下载了多个对象

c)         开启GZIP压缩

指标解释:仅检查文本类型(“text/*”,”*JavaScript*”)
评估方法:服务器是否返回了”Transfer-encoding: gzip”响应头。假如全部压缩就是满分,否则:得分=满分x(100%-全部gzip后节省的比例%)

d)         图片压缩

评估方法:
对于GIF – 略过
对于PNG – 必须是8位或更低
对于JPEG – 对比使用photoshop质量选择50后的图片,尺寸超出10%以内及格,10%-50%警告,50%以上不达标
得分=满分x(100%-图片重新压缩后可以节省的比例%)

e)         设置静态内容缓存时间

指标解释:css,js,图片资源都应该明确的指定一个缓存时间
评估标准:如果有静态文件的过期时间设置小于30天,将会得到警告

f)          合并css和js文件

指标解释:合并js和css文件可以减少连接数
评估方法:每多一个css文件减5分,每多一个js文件减10分

g)         压缩JS

指标解释:除了开启gzip,使用js压缩工具可以进行代码级的压缩
评估方法:js文件会通过jsmin压缩.如果原始文件gzip过,jsmin处理过的文件也会gzip后再进行对比.如果能节省>5KB或者%10的尺寸,评估失败.如果能节省>1KB同样会收到警告.

h)         合理使用cookie

指标解释:cookie越小越好,而且对于静态文件需要避免设置cookie
评估方法:只要对静态文件域设置了cookie,评估失败. 对于其他请求,cookie尺寸过大会得到警告.

二、详情分析

i)           首次探测(首次探测会清空DNS缓存和浏览器缓存),重复探测(保留首次探测的缓存,进行再次探测)。

j)           页面加载时间:从页面开始加载到页面onload事件触发的时间。

k)         首字节时间:从开始加载到收到服务器返回数据的第一字节的时间。

l)           开始渲染时间:从开始加载到浏览器开始渲染第一个html元素的时间。

m)       Speed index:

n)         元素个数:页面中包含的所有DOM节点个数

o)         页面加载(包括加载时间,请求数,下载总计):从页面开始加载到onload事件触发这个时间段内的统计数据,一般来说onload触发代表着直接通过HTML引用的CSS,JS,图片资源已经完全加载完毕。

p)         完全加载:随着ajax应用的流行,很多资源都会通过JS脚步异步加载,所以onload事件并不意味着完全加载,onload之后js可能依然在异步加载资源。完全加载的定义是:页面onload后2秒内不再有网络请求时刻。

q)         元素瀑布图:通过元素瀑布图可以很直观得到以下信息。

i.              资源的加载顺序。

ii.              每个资源的排队延迟,加载过程。

iii.              加载过程中CPU和贷款的变化曲线。

iv.              统计出出错请求、大图片请求、onload之后的请求、开始渲染之前的请求、首字节较慢的请求及DNS解析较慢的请求个数。

r)          连接视图展现了页面加载过程中创建的(keep-alive)连接,以及通过每个连接所加载的资源。

 

三、元素分布

s)         资源类型统计:css,html,image,js,other(请求数,大小)

t)          资源域名统计:请求域名个数及次数

 

四、视图分析

将整个网页生成的过程以胶片视图、视频、截屏的形式展现出来,并提供详细的状态栏加载日志。

 

YSlow:

火狐插件(自行安装)

评分等级指标:

1.       确保少量的HTTP请求(合并JS,CSS图片等)

2.       使用内容分发CDN

3.       设置过期的HTTP Header.设置Expires Header可以将脚本, 样式表, 图片, Flash等缓存在浏览器的Cache中。

4.       使用gzip压缩

5.       将CSS放置html头部

6.       将JavaScript放置底部

7.       Avoid CSS expressions

8.       使用外部引用JavaScript与CSS

9.       减少DNS解析

10.   压缩JavaScript和CSS

11.   避免URL重定向。URL redirects are made using HTTP status codes 301 and 302. They tell the browser to go to another location.

12.   删除重复JavaScript和CSS

13.   设置ETags

 

以上只是粗略介绍,更多详细指标,小伙伴们还是自己去发现吧!

[转载].NET 微信开放平台接口(接收短信、发送短信) - 小杰仔 - 博客园

mikel阅读(949)

[转载].NET 微信开放平台接口(接收短信、发送短信) – 小杰仔 – 博客园.

前两天做个项目用到了微信api功能。项目完成后经过整理封装如下微信操作类。

以下功能的实现需要开发者已有微信的公众平台账号,并且开发模式已开启、接口配置信息已设置完毕。废话不多说直接上代码。

 

1、公众平台账号接口配置信息中URL接口代码如下:

复制代码
  1 <%@ WebHandler Language="C#" Class="WeixinInterface" %>
  2 
  3 using System;
  4 using System.Web;
  5 
  6 using WeiXin;
  7 using System.Xml;
  8 public class WeixinInterface : IHttpHandler
  9 {
 10 
 11     HttpContext context;
 12     public void ProcessRequest(HttpContext context)
 13     {
 14         this.context = context;        
 15 
 16         //WriteLog("[Begin:" + DateTime.Now.ToString() + "]");  //开始接收信息(输出日志,供调试使用)
 17 
 18         /*
 19          * 第四个参数"checkToken"为true时,此实例方法用于验证微信公众平台配置的URL、Token。
 20          * 第四个参数"checkToken"为false时,此实例方法用于接收用户信息、回复用户信息。
 21          * 第四个参数"checkToken"为true时,此实例代码最好不要包含于try catch语句中
 22          * 第三个参数从配置文件中读取,其值为微信公众平台接口配置信息中的“Token”
 23          */
 24         //第一步:实例化微信封装类 
 25         WeiXinMessage weixin = new WeiXinMessage(context.Request, context.Response, System.Configuration.ConfigurationManager.AppSettings["Token"].ToString(), false);
 26         //WriteLog("[ReceiveStr:" + reply.ReceiveStr + "]");    //输出接收字符串(输出日志,供调试使用)
 27 
 28         //第二步:获取用户发送消息类型 
 29         string msgType = weixin.GetMsgType();
 30         
 31         
 32         //第三步:根据接收到不同的消息类型,执行不同的业务逻辑
 33         if (msgType == "text")
 34         {
 35             
 36             string msg = weixin.GetMsgText();//获取用户发送文本信息
 37             //WriteLog("[UserMsg:" + msg + "]");  //输出用户发送的文本信息(输出日志,供调试使用)
 38             string answer = "";
 39             try
 40             {
 41                 //根据用户发送的信息,自动匹配自定义的“自动回复”
 42                 //answer = HEBCCC.SSSS.BLL.W_ZDHFBLL.GetAnswer(answer);
 43             }
 44             catch (Exception ex)
 45             {
 46                 WriteLog(DateTime.Now.ToString() + "[error:" + ex.Message + "]");
 47             }
 48             //WriteLog("[answer:" + answer + "]");
 49             if (!string.IsNullOrEmpty(answer))
 50             {
 51                 //匹配出自动回复内容,推送给用户文本信息
 52                 weixin.SendMsgText(answer);//此代码不能包含于try catch语句中,否则报错。
 53             }
 54             else//匹配不出自动回复内容时,从系统xml配置文件中读取默认文本内容推送给用户
 55             {
 56                 XmlNode autoReplyXmlNode = null;
 57                 try
 58                 {
 59                     string path = context.Server.MapPath("~/xml/sys.xml");
 60                     XmlDocument xmldom = new XmlDocument();
 61                     xmldom.Load(path);//加载xml文件
 62                     XmlNode xmlNode = xmldom.SelectSingleNode("root");//读取第一个节点
 63                     autoReplyXmlNode = xmlNode.SelectSingleNode("autoReply");//读取第一个节点
 64                 }
 65                 catch (Exception ex)
 66                 {
 67                     WriteLog(DateTime.Now.ToString() + "[error2:" + ex.Message + "]");
 68                 }
 69                 if (autoReplyXmlNode != null && !string.IsNullOrEmpty(autoReplyXmlNode.InnerText))
 70                     weixin.SendMsgText(autoReplyXmlNode.InnerText);//此代码不能包含于try catch语句中,否则报错。
 71 
 72             }
 73         }
 74         else if (msgType == "event")
 75         {
 76             //获取事件类型(Event)、事件Key值(EventKey)
 77             string[] array = weixin.GetEventType();
 78             if (array[0] == "click" && array[1].ToLower() == "c_khgh")
 79             {
 80                 weixin.SendMsgText("抱歉,此功能暂未开通【河北华网计算机技术有限公司】");
 81             }
 82         } 
 83     }
 84 
 85     /// <summary>
 86     /// 记录日志
 87     /// </summary>
 88     /// <param name="strMemo">内容</param>
 89     private void WriteLog(string strMemo)
 90     {
 91         string logPath=System.Configuration.ConfigurationManager.AppSettings["SysLog"].ToString();
 92         string filename = context.Server.MapPath(logPath + "\\WeiXin.txt");
 93         if (!System.IO.Directory.Exists(context.Server.MapPath(logPath)))
 94             System.IO.Directory.CreateDirectory(context.Server.MapPath(logPath));
 95         System.IO.StreamWriter sr =null;
 96         try
 97         {
 98             if (!System.IO.File.Exists(filename))
 99             {
100                 sr =  System.IO.File.CreateText(filename);
101             }
102             else
103             {
104                 sr = System.IO.File.AppendText(filename);
105             }
106             sr.WriteLine(strMemo);
107         }
108         catch
109         {
110         }
111         finally
112         {
113             if (sr != null)
114                 sr.Close();
115         }
116     }
117 
118     public bool IsReusable
119     {
120         get
121         {
122             return false;
123         }
124     }
125 
126 }
复制代码

 

注:以上接口代码实现了,接收用户短信、被动向用户推送短信(自动回复)功能。由于 我自己项目本身功能的要求,我只用到的接收用户文本短信、接收用户推送事件、向用户推送文本信息的功能。所以在我封装的类库里也只有这些功能。其他功能: 如接收图片消息、接收地理位置消息、接收链接消息、推送音乐消息、推送图文消息并未实现。

 

2、自定义菜单

接口完成后,下面就该配制自己的自定义菜单了。我这里做了一个测试页,用于配制自定义菜单。

现实现三个功能:获取凭证(access_token)、创建菜单(需access_token)、删除菜单(需access_token)

获取凭证所需的appid、appsecret在微信公众平台的接口配置信息里。

 

 

代码如下:

 

复制代码
 1 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
 2 
 3 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 4 <html xmlns="http://www.w3.org/1999/xhtml">
 5 <head runat="server">
 6     <title></title>
 7 </head>
 8 <body>
 9     <form id="form1" runat="server">
10     <div>
11         <table>
12             <tr>
13                 <td align="right">
14                     appid:
15                 </td>
16                 <td>
17                     <asp:TextBox ID="_txtAppid" runat="server"></asp:TextBox>
18                 </td>
19             </tr>
20             <tr>
21                 <td align="right">
22                     appsecret:
23                 </td>
24                 <td>
25                     <asp:TextBox ID="_txtAppsecret" runat="server"></asp:TextBox>
26                 </td>
27             </tr>
28             <tr>
29                 <td colspan="2" align="center">
30                     <asp:Button ID="_btnAccessToken" runat="server" Text="获取access_token" OnClick="_btnAccessToken_Click" />
31                 </td>
32             </tr>
33         </table>
34         <asp:Label ID="_lblMsg" runat="server" Text=""></asp:Label>
35         <br />
36         <br />
37         <br />
38         <br />
39         <br />
40         <table>
41             <tr>
42                 <td align="right">access_token:</td>
43                 <td>
44                     <asp:TextBox ID="_txtacetoken" TextMode="MultiLine" Rows="5" Columns="40" runat="server"></asp:TextBox>
45                 </td>
46             </tr>
47 
48             <tr>
49                 <td align="right">自定义菜单内容:</td>
50                 <td>
51                     <asp:TextBox ID="_txtMenu" TextMode="MultiLine" Rows="10" Columns="40" runat="server"></asp:TextBox>
52                 </td>
53             </tr>
54             <tr>
55                 <td colspan="2" align="center">
56                     <asp:Button ID="_btnSetMenu" runat="server" Text="设置菜单" 
57                         onclick="_btnSetMenu_Click"  />
58                     <asp:Button ID="_btnDelMenu" runat="server" Text="删除自定义菜单" 
59                         onclick="_btnDelMenu_Click"  />
60                 </td>
61             </tr>
62         </table>
63         <asp:Label ID="_lblMsg2" runat="server" Text=""></asp:Label>        
64     </div>
65     </form>
66 </body>
67 </html>
复制代码

 

复制代码
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Web;
 5 using System.Web.UI;
 6 using System.Web.UI.WebControls;
 7 
 8 using System.Net;
 9 using System.IO;
10 using System.Text;
11 public partial class _Default : System.Web.UI.Page
12 {
13     protected void Page_Load(object sender, EventArgs e)
14     {
15 
16     }
17 
18     //获取access_token
19     protected void _btnAccessToken_Click(object sender, EventArgs e)
20     {
21         if (string.IsNullOrEmpty(_txtAppid.Text.Trim()))
22         {
23             ScriptManager.RegisterStartupScript(this, GetType(), "", "alert('请输入appid');", true);
24             return;
25         }
26         if (string.IsNullOrEmpty(_txtAppsecret.Text.Trim()))
27         {
28             ScriptManager.RegisterStartupScript(this, GetType(), "", "alert('请输入appsecret');", true);
29             return;
30         }
31         string url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + _txtAppid.Text.Trim() + "&secret=" + _txtAppsecret.Text.Trim();
32         HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
33         webRequest.Method = "get";
34         HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();
35         StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.Default);
36         _lblMsg.Text = "返回结果:" + sr.ReadToEnd();
37 
38     }
39 
40 
41 
42 
43     //设置菜单
44     protected void _btnSetMenu_Click(object sender, EventArgs e)
45     {
46         if (string.IsNullOrEmpty(_txtacetoken.Text.Trim()))
47         {
48             ScriptManager.RegisterStartupScript(this, GetType(), "", "alert('请输入access_token');", true);
49             return;
50         }
51         if (string.IsNullOrEmpty(_txtMenu.Text.Trim()))
52         {
53             ScriptManager.RegisterStartupScript(this, GetType(), "", "alert('请输入自定义菜单内容');", true);
54             return;
55         }
56 
57         string padata = _txtMenu.Text.Trim();
58         string url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" + _txtacetoken.Text.Trim();//请求的URL
59         try
60         {
61             byte[] byteArray = Encoding.UTF8.GetBytes(padata); // 转化
62             HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
63             webRequest.Method = "POST";
64             webRequest.ContentLength = byteArray.Length;
65        
66             Stream newStream = webRequest.GetRequestStream();
67             newStream.Write(byteArray, 0, byteArray.Length); //写入参数
68             newStream.Close();
69             HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();
70             StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.Default);
71             _lblMsg2.Text = "返回结果:" + sr.ReadToEnd();
72         }
73         catch (Exception ex)
74         {
75             throw ex;
76         }
77     }
78 
79     //删除菜单
80     protected void _btnDelMenu_Click(object sender, EventArgs e)
81     {
82         if (string.IsNullOrEmpty(_txtacetoken.Text.Trim()))
83         {
84             ScriptManager.RegisterStartupScript(this, GetType(), "", "alert('请输入access_token');", true);
85             return;
86         }
87         string url = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=" + _txtacetoken.Text.Trim();
88 
89         HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
90         webRequest.Method = "get";
91         HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();
92         StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.Default);
93         _lblMsg2.Text = "返回结果:" + sr.ReadToEnd();
94     }
95 
96 }
复制代码

 

 

说明:这里附上单文件dll。整个项目的源码已放入到CSDN中,需要的朋友可以去下,在这就不单独上传了。 

    WeiXin.dll

 

  CSDN连接:http://download.csdn.net/detail/xujie825/6329293

 

  还有一点就是微信的主动推送,虽然微信对外没有公开主动推送的api接口,但是经过我们的努力完全是可以实现的。我的项目里已经过验证。由于微信不提倡,所以这里也就不附上主动推送的代码了。有学习精神的朋友们,多努力努力吧。

[转载]REST WCF 30分钟理论到实践 - 一文钱 - 博客园

mikel阅读(1004)

[转载]【REST WCF】30分钟理论到实践 – 一文钱 – 博客园.

先来点理论知识,来自 http://www.cnblogs.com/simonchen/articles/2220838.html

一.什么是Rest

REST软件架构是由Roy Thomas Fielding博士2000年在他的论文《Architectural Styles and the Design of Network- based Software Architectures》首次提出的。他提出的理论对后来的Web技术的发展产生了巨大的影响,他是许多重要Web架构标准的设计者,这些标准就是 HTTP、URI等。

  • Rest的英文全称是“Representational State Transfer”。中文翻译为“表述性状态转移”。REST本身只是为分布式超媒体系统设计的一种架构风格,而不是标准。
  • 那么如何理解“Representational State Transfer”这句话呢?下面我们来解释一下:
    1.     Representational :中文直译:代表的,表像的。如果把WEB 服务器端中所有的东西(数据)都看作是资源(Resource),那么呈现在用户面前(客户端)的就是资源的表像(Representation)。每一个资源都有自己的唯一标识(URI)。
    2.     State :中文直译:状态。首先这个状态是客户端的状态,而不是服务器端的状态(在REST 中,服务器端应该是无状态的)。那么,把State和Representation联系在一起(Representational State),可以理解成:每一个资源(Resource)在客户端的表像(Representation)就是客户端的一个状态(State)。
    3.     Transfer:中文直译:转移。当用户通过不同的URI访问不同的资源时,客户端的表像(Representation)也会随着变化,也就意味着客户端的状态变更(Transfer)了,连起来就是:Representational State Transfer。
  • REST=老的Web规范+3个新的规范:REST实际上也是基于已有的Web规范集合产生的。传统的Web应用大都是BS系统,这些系统共同遵循一些老的Web规范,这些规范主要包含 3条:
    1.   客户-服务器:这种规范的提出,改善了用户接口跨多个平台的可移植性,并且通过简化服务器组件,改善了系统的可伸缩性。最为关键的是通过分离用户接口和数据存储这两个关注点,使得不同用户终端享受相同数据成为了可能。
    2.   无状态性:无状态性是在客户-服务器约束的基础上添加的又一层规范。他要求通信必须在本质上是无状态的,即从客户到服务器的每个 request都 必须包含理解该request所必须的所有信息。这个规范改善了系统的可见性(无状态性使得客户端和服务器端不必保存对方的详细信息,服务器只需要处理当 前request,而不必了解所有的request历史),可靠性(无状态性减少了服务器从局部错误中恢复的任务量),可伸缩性(无状态性使得服务器端可 以很容易的释放资源,因为服务器端不必在多个request中保存状态)。同时,这种规范的缺点也是显而易见得,由于不能将状态数据保存在服务器上的共享 上下文中,因此增加了在一系列request中发送重复数据的开销,严重的降低了效率。
    3.       缓存:为了改善无状态性带来的网络的低效性,我们填加了缓存约束。缓存约束允许隐式或显式地标记一个response中的数据,这样就赋予了客户 端缓存response数据的功能,这样就可以为以后的request共用缓存的数据,部分或全部的消除一部分交互,增加了网络的效率。但是用于客户端缓 存了信息,也就同时增加了客户端与服务器数据不一致的可能,从而降低了可靠性。

 

  • REST在原有的架构上增加了3个新规范:统一接口、分层系统和按需代码:
    1.       统一接口:REST架构风格的核心特征就是强调组件之间有一个统一的接口,这表现在REST世界里,网络上所有的事物都被抽象为资源,而REST 就是通过通用的链接器接口对资源进行操作。这样设计的好处是保证系统提供的服务都是解耦的,极大的简化了系统,从而改善了系统的交互性和可重用性。并且 REST针对Web的常见情况做了优化,使得REST接口被设计为可以高效的转移大粒度的超媒体数据,这也就导致了REST接口对其它的架构并不是最优 的。
    2.       分层系统:分层系统规则的加入提高了各种层次之间的独立性,为整个系统的复杂性设置了边界,通过封装遗留的服务,使新的服务器免受遗留客户端的影响,这也就提高了系统的可伸缩性。
    3.       按需代码:REST允许对客户端功能进行扩展。比如,通过下载并执行 applet或脚本形式的代码,来扩展客户端功能。但这在改善系统可扩展性的同时,也降低了可见性。所以它只是REST的一个可选的约束。

二.Rest的特点

由于Rest遵守的这些规范,因此Rest架构的特点也非常的明显:

  •   REST是一种架构,而不是一个规范。
  •   REST是一种典型的Client-Server架构,但是强调瘦服务器端,服务器端只应该处理跟数据有关的操作,所有有关显示的工作都应该放在客户端。
  •   在REST架构中,服务器是无状态的,也就是说服务器不会保存任何与客户端的会话状态信息。所有的状态信息只能放在双方沟通的 Message(消息)中。
  •   REST架构是幂等的,对于相同的请求,服务器返回的结果也是相同的,因此服务器端返回的结果是可以缓存的,既可以存在客户端也可以存在代理服务器端。
  •   在REST架构中,所有的操作都是基于统一的方式进行的:
    1.     每个Resource都有一个唯一的ID。
    2.     通过Representation(客户端)来处理Resource(服务器端)。也就是说,客户端不能直接操作服务器端的Resource,只能通过对 相应的Representation的操作,并发送相应的请求,最后由服务器端来处理Resource并返回结果。
    3.     客户端和服务器端传送的任何一个Message(消息),都应该是自描述的。也就是说处理这个 Message所需要的上下文环境都应该包含在这个Message当中。
    4.     多媒体的交互系统,客户端和服务器端传送的内容可以是文档,图片,声音等等多媒体数据,这也是一个Resource能够对应不同的Representation(例如文档,图片等)的基础。

 

  •   分层结构,像TCP/IP的分层结构一样,第n层使用第n-1层提供的服务并为第n+1层提供服务。在REST中,Client- Server之间加入了Proxy层和Gateway层。在这些中间层可以加入一些业务处理以外的功能,譬如:负载均衡,安全控制等等。
  •   Code-On-Demand,客户端可以访问服务器端的Resource,但并不知道如何处理服务器端返回的结果,这个处理过程的代码应该 是从服务器端发送过来,然后在客户端执行,也就是说客户端的功能是根据需要动态从服务器端获得的。一个很简单的例子,Applet就是从服务器端下载然后 在客户端执行的。注意,这个特性是可选的(Optional),也就是说在你的REST实现当中,可以不考虑这个特性。

三.Rest的优点

既然Rest风格有这些特点,那么也就具备了许多优点:

  •   缓存使用 HTTP 向 RESTful 端点申请数据时,用到的 HTTP 动词是 GET。对于 GET 请求响应中返回的资源,可以用多种不同的方式进行缓存。Conditional GET 就是可供选择的一种实现细节,客户端可以向服务验证他的数据是否为最新版本;RESTful 端点可以通过它进一步提高速度和可伸缩性。
  •   扩展 REST 鼓励每项资源包含处理特殊请求所需的所有必要状态。满足这一约束时,RESTful 服务更易于扩展且可以没有状态。
  •   副作用如您使用 GET 请求资源,RESTful 服务应该没有副作用(遗憾的是,与其他一些 REST 约束相比,这一约束更容易被打破)。
  •   幂等统一接口另外两个常用到的主要 HTTP 动词是 PUT 和 DELETE。用户代理想要修改资源时最常使用 PUT,DELETE 可以自我描述。要点(也就是“幂等”一词所强调的)是您可以对特殊资源多次使用这两个动词,效果与首次使用一样——至少不会有任何其他影响。构建可靠的分 布式系统时(即错误、网络故障或延迟可能导致多次执行代码),这一优点可提供保障。
  •   互操作性许多人将 SOAP 捧为建立客户端-服务器程序最具互操作性的方法。但一些语言和环境至今仍没有 SOAP 工具包。有一些虽然有工具包,但采用的是旧标准,不能保证与使用更新标准的工具包可靠沟通。对于大多数操作,REST 仅要求有 HTTP 库(当然,XML 库通常也很有帮助),它的互操作性肯定强过任何 RCP 技术(包括 SOAP)。
  •   简易性与其他优点相比,这一优点更主观一些,不同的人可能有不同的感受。对我而言,使用 REST 的简易性涉及到代表资源的 URI 和统一接口。作为一名 Web 冲浪高手,我理解在浏览器中输入不同的 URI 可以得到不同的资源(有时也被称为 URI 或 URL 黑客,但绝无恶意)。由于有多年使用 URI 的经验,所以为资源设计 URI 对我来说得心应手。使用统一接口简化了开发过程,因为我不必为每个需要建立的服务构建接口、约定或 API。接口(客户端与我的服务交互的方式)由体系结构约束设置。

四.Rest的设计原则

REST架构是针对Web应用而设计的,其目的是为了降低开发的复杂性,提高系统的可伸缩性。REST提出了如下设计准则:

  1. 网络上的所有事物都被抽象为资源(resource),比如图片、音乐、视频、文字、以及服务等等;
  2. 每个资源有唯一的资源标识符(resource identifier),URI定位资源;
  3. 通过通用的连接器接口(generic connector interface)对资源进行操作,比如使用 HTTP 标准动词(GET、POST、PUT 和 DELETE)的统一接口完成操作;
  4. 对资源的各种操作不会改变资源标识符,URI不变;
  5. 所有的操作都是无状态的(stateless)。

五.wcf3.5到wcf4.0 Rest的新增特性

 

  1. 增加对路由的支持
  2. 对缓存的支持。
  3. 帮助(help)页面的改进。
  4. 消息错误处理
  5. 消息格式的多样性如(XML\JSON\ATOM\TEXT\BINARY)
  6. 简化操作。

甩过一遍理论,那么就趁热实践一番吧!

六.实践出真知

 

按正常步骤新建一个WCF应用,常见的CRUD操作

复制代码
 [ServiceContract]
    public interface IExampleService
    {

        [OperationContract]
        string GetData(string value);

        [OperationContract]
        string AddData(string value);

        [OperationContract ]
        string UpdateData(string value);

        [OperationContract ]
        string DeleteData(string value);

    }
复制代码

那么rest模式该是如何呢?

复制代码
 [ServiceContract]
    public interface IExampleService
    {

        [OperationContract]
        [WebGet(UriTemplate = "/Rest/Get/{value}", ResponseFormat = WebMessageFormat.Json)]
        string GetData(string value);

        [OperationContract]
        [WebInvoke(UriTemplate = "/Rest/Add/{value}", Method = "POST")]
        string AddData(string value);

        [OperationContract ]
        [WebInvoke(UriTemplate = "/Rest/Update/{value}", Method = "PUT")]
        string UpdateData(string value);

        [OperationContract ]
        [WebInvoke (UriTemplate="/Rest/Delete/{value}",Method="DELETE")]
        string DeleteData(string value); 

    }
复制代码

比较下就很容易看出多加了些标签,并且也从方法的使用上可以对应出GET、POST、PUT、DELETE的使用。

wcf可以看元数据,那么rest也有对应的方式,在web.config中添加如下配置就可以查看help页面

复制代码
 <services>
      <service name="RestWCFTest.ExampleService">
        <endpoint address="" behaviorConfiguration="HelpBehavior" binding="webHttpBinding"
          bindingConfiguration="" contract="RestWCFTest.IExampleService" />
      </service>
    </services>

  <behaviors>
      <endpointBehaviors>
        <behavior name="HelpBehavior">
          <webHttp helpEnabled="true" />
        </behavior>
      </endpointBehaviors>
</behaviors>
复制代码

help页面如下

点击方法进去可以看见调用方式

我们的接口实现

复制代码
   public string GetData(string value)
        {
            return string.Format("You entered: {0}", value);
        }

        public string AddData(string value)
        {
            return string.Format("You added: {0}", value);
        }

        public string UpdateData(string value)
        {
            return string.Format("You updated: {0}", value);
        }

        public string DeleteData(string value)
        {
            return string.Format("You deleted: {0}", value);
        }
复制代码

现在我们用fiddler来模拟请求测试下

在composer选项里有模拟请求的功能,very good!我们先来调用GetData操作,根据参数获取数据,根据设置的URI模板,“123456”为匹配

的参数,执行它!

看请求的信息

GET http://localhost/REST4/ExampleService.svc/Rest/Get/123456 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 0

看响应的数据

复制代码
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 21
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 05:16:52 GMT

"You entered: 123456"
复制代码

200 OK 调用正常,content-type是json,因为我们指定的,IIS是7.5,对,我的确是部署在7.5上。。。看结果也是和预期一模一样,so easy~

可能有同学会问,这是返回的json数据么?我也觉得不是,如果在方法标签上修改为如下

 [OperationContract]
        [WebGet(UriTemplate = "/Rest/Get/{value}",BodyStyle=WebMessageBodyStyle.Wrapped, ResponseFormat = WebMessageFormat.Json)]
        string GetData(string value);

多加了个修饰bodystyle,它的功能是对结果进行包装,包装后再看返回的结果

复制代码
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 39
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 06:34:24 GMT

{"GetDataResult":"You entered: 123456"}
复制代码

果然,被包装了,它是一个json格式的数据了。

POST

请求

POST http://localhost/REST4/ExampleService.svc/Rest/Add/1234 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 0

响应

复制代码
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 92
Content-Type: application/xml; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 05:06:41 GMT

<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">You added: 1234</string>
复制代码

这个时候我们没有指定返回的格式,默认为XML。

PUT

请求

PUT http://localhost/REST4/ExampleService.svc/Rest/Update/123 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 0

响应

复制代码
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 93
Content-Type: application/xml; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 05:23:04 GMT

<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">You updated: 123</string>
复制代码

DELETE

请求

DELETE http://localhost/REST4/ExampleService.svc/Rest/Delete/123 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 0

响应

复制代码
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 93
Content-Type: application/xml; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 05:14:56 GMT

<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">You deleted: 123</string>
复制代码

有同学可能DELETE请求发出去没反应(IIS 7.5),请在web.config里加上以下节点

复制代码
 <system.webServer>
<modules runAllManagedModulesForAllRequests="true">
      <remove name="WebDAVModule" />
    </modules>
<handlers>
      <remove name="WebDAV" />
    </handlers>
 </system.webServer>
复制代码

至此一般的传参情况就是如此了,下面列举一些其它传参情况

复制代码
   [OperationContract]
        [WebGet(UriTemplate = "/Rest/GetList2/", ResponseFormat = WebMessageFormat.Json)]
        List<ExampleData> GetDataLs2();

   [OperationContract]
        [WebInvoke(UriTemplate = "/Rest/AddLs3", BodyStyle = WebMessageBodyStyle.Wrapped,
            ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json, Method = "POST")]
        List<ExampleData> AddDataLs3(List<ExampleData> datas);

        [OperationContract]
        [WebInvoke(UriTemplate = "/Rest/AddLs4", BodyStyle = WebMessageBodyStyle.Wrapped,
            ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json, Method = "POST")]
        List<ExampleData> AddDataLs4(List<ExampleData> datas1, List<ExampleData> datas2);
复制代码

实体

复制代码
  public class ExampleData
    {
        public string Name
        {
            get;
            set;
        }

        public string Age
        {
            get;
            set;
        }
    }
复制代码

接口实现

复制代码
  public List<ExampleData> GetDataLs2()
        {
            List<ExampleData> result = new List<ExampleData> { 
                new ExampleData{ Name="张三", Age="20"}
                ,new ExampleData {Name="李四",Age="21"}
                 ,new ExampleData {Name="王五",Age="30"}
            };

            return result;
        }

  public List<ExampleData> AddDataLs3(List<ExampleData> datas)
        {
            return datas;
        }

  public List<ExampleData> AddDataLs4(List<ExampleData> datas1, List<ExampleData> datas2)
        {
            List<ExampleData> result = new List<ExampleData>();
            result.AddRange(datas1);
            result.AddRange(datas2);

            return result;
        }
复制代码

 

我们看到有获取实体集合了,还有传参的时候也是实体集合了

首先看看获取集合的情况

请求

GET http://localhost/REST4/ExampleService.svc/Rest/GetList2/ HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 0

响应

复制代码
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 88
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 05:21:52 GMT

[{"Age":"20","Name":"张三"},{"Age":"21","Name":"李四"},{"Age":"30","Name":"王五"}]
复制代码

嗯,返回的格式不错。

看看怎样做新增操作的

AddDataLs3

请求

复制代码
POST http://localhost/REST4/ExampleService.svc/Rest/AddLs3 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 41

{"datas":[{"Name":"xiaohua","Age":"13"}]}
复制代码

这时候我们会注意到,多了request body了,并且datas就是参数名

响应

复制代码
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 52
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 06:59:55 GMT

{"AddDataLs3Result":[{"Age":"13","Name":"xiaohua"}]}
复制代码

被包装了的数据。

AddDataLs4

请求

复制代码
POST http://localhost/REST4/ExampleService.svc/Rest/AddLs4 HTTP/1.1
User-Agent: Fiddler
Content-type: application/json
Host: localhost
Content-Length: 78

{"datas1":[{"Name":"xiaohua","Age":"13"}],"datas2":[{"Name":"li","Age":"13"}]}
复制代码

响应

复制代码
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 77
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2013 07:02:58 GMT

{"AddDataLs4Result":[{"Age":"13","Name":"xiaohua"},{"Age":"13","Name":"li"}]}
复制代码

 

面对茫茫多的CRUD的时候,我们也许会显得不耐烦,因为每个操作都去写方法,真是烦躁,不妨可以试下如下的方式

   [OperationContract]
        [WebInvoke(UriTemplate = "/Rest/*", Method = "*", ResponseFormat = WebMessageFormat.Json)]
        string ExecuteData();

用星号来匹配所有的请求,让程序区识别请求到底是GET、POST、PUT还是DELETE

实现

复制代码
   public string ExecuteData()
        {
            var request = WebOperationContext.Current.IncomingRequest;

            var method = request.Method;
            var args = request.UriTemplateMatch.WildcardPathSegments;

            switch (method)
            {
                case "POST":
                    return "POST...";
                case "DELETE":
                    return "DELETE...";
                case "PUT":
                    return "UPDATE...";
                default:
                    return "GET...";
            }
        }
复制代码

嗯,不知不觉就贴了这么多代码了,其实我字没写多少,今天就到这吧,算是入门了吧。

以上例子所有源码下载

[转载]关于CSS中清除浮动的方法 - twobin - 博客园

mikel阅读(1031)

[转载]关于CSS中清除浮动的方法 – twobin – 博客园.

在使用CSS样式时会经常使用到浮动(float),这时如果没有清除浮动就会造成很多怪异的现象,因此对父级元素清除浮动是必须要做的,这样也是书写CSS的一个良好习惯。

目前常用的方法大致有三种。

(1)使用空标签清除浮动

这是目前最常用的一种清除浮动的方法。空标签可以是div标签,也可以是P标签。其实理论上可以是任何标签。这种方法是在需要清除浮动的父级元素内部的最后添加一个清除浮动的空标签,其CSS代码:clear:both此方法简洁明了,方便实用,但其弊端也很明显,就是增加了无意义的结构元素。此外,有一种与此相似的方法,<br clear=”all”/>也可以实现同样的效果。

(2)使用after伪对象清除浮动

各浏览器均支持after伪对象,所以after应该是适用的,不存在兼容问题。其CSS代码:#layout:after{ display:block; clear:both; content:””; visibility:hidden; height:0;}。使用中需注意:1、该方法中必须为需要清除浮动元素的伪对象设置height:0否则该元素会比实际高出若干像素;2、content属性是必须的,但其值可以为空。此方法实用有效,且不用添加无意义的标签,但CSS代码较复杂,不够简洁明了。

(3)使用overflow属性

此方法有效地解决了通过空标签元素清除浮动而不得不增加无意代码的弊端。使用该方法是只需在需要清除浮动的元素中定义CSS属性:overflow:auto;其目的是让高度自适应,zoom:1;是为了兼容IE6(zoom不符合W3C标准),也可以用height:1%;的方式来解决

这三种清除浮动的方法在实际应用中均实用有效,至于使用哪一种那就是仁者见仁智者见智了,本人还是习惯使用第一种方法,简单快捷无压力,而且那几个清除浮动的标签其实也无伤大雅!

[转载]Android UI开发第三十一篇——Android的Holo Theme - 张兴业 - 博客园

mikel阅读(824)

[转载]Android UI开发第三十一篇——Android的Holo Theme – 张兴业 – 博客园.

        好长时间没写Android UI方面的文章了,今天就闲扯一下Android的Holo主题。一直做android开发的可能都知道,Android 系统的UI有过两次大的变化,一次是android 3.0发布,UI开发支持了Fragment,主要增加了大屏幕显示的支持,这个版本就开始支持Holo Theme,由于android 3.X的设备占有率也不高,这一次的改变没有引起大的关注;再一次的改变就是Android 4.0 ,也就是通常所说的 ICS ,这个于2011年底发布的Android系统,同时也发布了指导性的应用设计规范《Android Design》 有了设计规范的指导,就有了更多应用采用了Holo Theme,尤其国外的应用。Holo Theme的主要特点是轻快的颜色、适当的阴影、卡片化布局、方角矩形。

 

 

 

一、Holo Theme的三种表现形式

 

         Holo Theme是android4.0开始提出的一套UI风格,Holo Theme一 共有三种风格-Holo Light,Holo Dark,Holo Light with dark action bars。Android 4.0以后的手机系统内集成有Holo Theme的控件,简单的应用直接调用这些控件,就能设计出Holo 风格的应用,和原生系统风格很一致。下图是Holo Theme三种风格的表现形式。

 

        

 

                

 

                  Gmail in Holo Light.                                               Settings in Holo Dark.                     Talk in Holo Light with dark action bar.

 

 

 

二、Holo Theme是Android Design的一部分

 

         Holo Theme是Android Design的一部分,Holo Theme 是 Android Design 的最基础的呈现方式。如果要以 VennDiagram 来表示的话,大概是下图这样的。

 


 

       如果整个背景的浅灰代表了 Android Design,那么其中淡蓝的圆圈便是 Holo Theme。 Android Design包含了对色彩、主题、事件状态图、应用图标、控件等Android 应用设计相关的定义,主题成了 Android Design的一部分。

 

 

三、Holo Theme在android 4.0版本以下怎么实现

 

 

 

           Holo Theme在4.0之上很容易实现,系统支持,如果在4.0之前的版本中使用Holo风格,则通过HoloEverywhere 库即可实现。HoloEverywhere 依赖 ActionBarSherlock来在4.0之前版本中实现ActionBar。下图是HoloEverywhere的实现效果,后篇会详细介绍HoloEverywhere。

 

 

 

                            

 

 

 

 

 

 

 

/**
* @author 张兴业

 

*  iOS入门群:83702688
*  android开发进阶群:241395671
*  我的新浪微博:@张兴业TBOW
*/

 

 

 

 

http://developer.android.com/design/index.html

[转载]ASP.NET MVC的过滤器 - 猴健居士 - 博客园

mikel阅读(869)

[转载]ASP.NET MVC的过滤器 – 猴健居士 – 博客园.

  APS.NET MVC中(以下简称“MVC”)的每一个请求,都会分配给相应的控制器和对应的行为方法去处理,而在这些处理的前前后后如果想再加一些额外的逻辑处理。这时候就用到了过滤器。

MVC支持的过滤器类型有四种,分别是:Authorization(授权),Action(行为),Result(结果)和Exception(异常)。如下表,

过滤器类型 接口 描述
Authorization IAuthorizationFilter 此类型(或过滤器)用于限制进入控制器或控制器的某个行为方法
Exception IExceptionFilter 用于指定一个行为,这个被指定的行为处理某个行为方法或某个控制器里面抛出的异常
Action IActionFilter 用于进入行为之前或之后的处理
Result IResultFilter 用于返回结果的之前或之后的处理

但是默认实现它们的过滤器只有三种,分别是Authorize(授权),ActionFilter,HandleError(错误处理);各种信息如下表所示

过滤器 类名 实现接口 描述
ActionFilter AuthorizeAttribute IAuthorizationFilter 此类型(或过滤器)用于限制进入控制器或控制器的某个行为方法
HandleError HandleErrorAttribute IExceptionFilter 用于指定一个行为,这个被指定的行为处理某个行为方法或某个控制器里面抛出的异常
自定义 ActionFilterAttribute IActionFilter和IResultFilter 用于进入行为之前或之后的处理或返回结果的之前或之后的处理

下面介绍的过滤器中,除了上面这几种外,还多加一种过滤器OutputCache

 

1           授权过滤器Authorize

 

1.1          默认Authorize使用

现在在网上无论是要求身份验证的地方多得是,发邮件,买东西,有时候就算吐个槽都要提示登录的。这里的某些操作,就是要经过验证授权才被允许。在MVC中可以利用Authorize来实现。例如一个简单的修改密码操作

        [Authorize]
        public ActionResult ChangePassword()
        {
            return View();
        }

它需要用户通过了授权才能进入到这个行为方法里面,否则硬去请求那个页面的话,只会得到这个结果

如果要通过验证,通过调用FormsAuthentication.SetAuthCookie方法来获得授权,登陆的页面如下

复制代码
@model FilterTest.Models.LogInModel
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <title>Login</title>
</head>
<body>
    <div>
    @using( Html.BeginForm()){
       <div>
        ID:@Html.TextBoxFor(m=>m.UserName)
        <br />
        Password:@Html.PasswordFor(m => m.Password)
        <br />
        <input type="submit" value="login" />
       </div>
    }
    </div>
</body>
</html>
复制代码

行为方法如下

复制代码
        [HttpPost]//这里用了谓词过滤器,只处理POST的请求
        public ActionResult Login(LogInModel login)
        {
            if (login.UserName == "admin" && login.Password == "123456")
            {
                FormsAuthentication.SetAuthCookie(login.UserName, false);
                return Redirect("/Customer/ChangePassword");
            }

            return View();
        }
复制代码

当然有登录也要有注销,因为注销是在登陆之后发生的,没登陆成功也就没有注销,所以注销的行为方法也要加上Authorize过滤器,注销调用的是FormsAuthentication.SignOut方法,代码如下

        [Authorize]
        public ActionResult LogOut()
        {
            FormsAuthentication.SignOut();
            return Redirect("/Customer/Login");
        }

1.2          自定义授权

我们不一定要用MVC默认的Authorize授权验证规则,规则可以自己来定,自定义授权过滤器可以继承AuthorizeAttribute这个类,这个类里面有两个方法是要重写的

  •          bool AuthorizeCore(HttpContextBase httpContext):这里主要是授权验证的逻辑处理,返回true的则是通过授权,返回了false则不是。
  •          void HandleUnauthorizedRequest(AuthorizationContext filterContext):这个方法是处理授权失败的事情。

这里就定义了一个比较骑呢的授权处理器,当请求的时候刚好是偶数分钟的,就通过可以获得授权,反之则不通过。当授权失败的时候,就会跳转到登陆页面了。

复制代码
    public class MyAuthorizeAttribute:AuthorizeAttribute
    {

        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            //return base.AuthorizeCore(httpContext);
            return DateTime.Now.Minute % 2 == 0
        }

        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            filterContext.HttpContext.Response.Redirect("/Customer/Login");

            //base.HandleUnauthorizedRequest(filterContext);
        }
    }
复制代码

然后用到一个行为方法上,

        [MyAuthorize]
        public ActionResult ShowDetail()
        {
            return View();
        }

每当偶数分钟的时候就可以访问得到这个ShowDetail的视图,否则就会跳到了登陆页面了。

2           处理错误过滤器HandleError

 

2.1          默认HandleError使用

在往常的开发中,想到异常处理的马上就会想到try/catch/finally语句块。在MVC里面,万一在行为方法里面抛出了什么异常的,而那 个行为方法或者控制器有用上HandleError过滤器的,异常的信息都会在某一个视图显示出来,这个显示异常信息的视图默认是在 Views/Shared/Error

这个HandleError的属性如下

属性名称 类型 描述
ExceptionType Type 要处理的异常的类型,相当于Try/Catch语句块里Catch捕捉的类型,如果这里不填的话则表明处理所有异常
View String 指定需要展示异常信息的视图,只需要视图名称就可以了,这个视图文件要放在Views/Shared文件夹里面
Master String 指定要使用的母版视图的名称
Order Int 指定过滤器被应用的顺序,默认是-1,而且优先级最高的是-1

这个Order属性其实不只这个HandleError过滤器有,其优先级规则跟其他过滤器的都是一样。

下面则故意弄一个会抛异常的行为方法

        [HandleError(ExceptionType = typeof(Exception))]
        public ActionResult ThrowErrorLogin()
        {
            throw new Exception("this is ThrowErrorLogin Action Throw");
        }

 

光是这样还不够,还要到web.config文件中的<system.web>一节中添加以下代码

<customErrors mode="On" />

 

因为默认的开发模式中它是关闭的,要等到部署到服务器上面才会开启,让异常信息比较友好的用一个视图展现。

像这里访问ThrowErrorLogin视图时,由于抛出了一次,就转到了一个特定的视图

在这里看到的异常视图是这样的,除了用这个建项目时默认生成的异常视图之外,我们还可以自己定义异常视图,视图里面要用到的异常信息,可以通过@Model获取,它是一个ExceptionInfo类型的实例,例如这里建了一个异常视图如下

复制代码
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <title>MyErrorPage</title>
</head>
<body>
    <div>
    <p>
        There was a <b>@Model.Exception.GetType().Name</b> 
        while rendering <b>@Model.ControllerName</b>'s 
        <b>@Model.ActionName</b> action. 
    </p> 
    <p style="color:Red"> 
        <b>@Model.Exception.Message</b> 
    </p> 
    <p>Stack trace:</p> 
    <pre style=" background-color:Orange">@Model.Exception.StackTrace</pre>
        </div>
</body>
</html>
复制代码

 

它存放的路径是~/Views/Sheared里面,像上面的行为方法如果要用异常信息渲染到这个视图上面,在控制器的处改成这样就可以了

[HandleError(ExceptionType = typeof(Exception), View = "MyErrorPage")]

 

2.2          自定义错误异常处理

  这里的错误处理过滤器也可以自己来定义,做法是继承HandleErrorAttribute类,重写void OnException(ExceptionContext filterContext)方法,这个方法调用是为了处理未处理的异常,例如

复制代码
        public override void OnException(ExceptionContext filterContext)
        {
            //base.OnException(filterContext);
            if (!filterContext.ExceptionHandled && 
                filterContext.Exception.Message == "this is ThrowErrorLogin Action Throw")
            { 
                filterContext.ExceptionHandled=true;
                filterContext.HttpContext.Response.Write("5洗ten No Problem<br/>" +
                    filterContext.Exception.ToString());
            }
        }
复制代码

  这里用到的传入了一个ExceptionContext的对象,既可以从它那里获得请求的信息,又可以获取异常的信息,它部分属性如下

属性名称 类型 描述
ActionDescriptor ActionDescriptor 提供详细的操作方法
Result ActionResult 结果的操作方法,过滤器可以取消,要求将此属性设置为一个非空值
Exception Exception 未处理的异常
ExceptionHandled bool 另一个过滤器,则返回true,如果有明显的异常处理


  这里的 ExceptionHandler属性要提一下的是,如果这个异常处理完的话,就把它设为true,那么即使有其他的错误处理器捕获到这个异常,也可以通 过ExceptionHandler属性判断这个异常是否经过了处理,以免重复处理一个异常错误而引发新的问题。

3           OutputCache过滤器

OutputCache过滤器用作缓存,节省用户访问应用程序的时间和资源,以提高用户体验,可这个我试验试不出它的效果。留作笔记记录一下。OuteputCacheAttribute这个类有以下属性

 

属性名称 类型 描述
Duration int 缓存的时间,以秒为单位,理论上缓存时间可以很长,但实际上当系统资源紧张时,缓存空间还是会被系统收回。
VaryByParam string 以哪个字段为标识来缓存数据,比如当“ID”字段变化时,需要改变缓存(仍可保留原来的缓存),那么应该设VaryByParam为”ID”。这里你可以设置以下几个值:
* = 任何参数变化时,都改变缓存。
none = 不改变缓存。
以分号“;”为间隔的字段名列表 = 列表中的字段发生变化,则改变缓存。
Location OutputCacheLocation 缓存数据放在何处。默认是Any,其他值分别是Client,Downstream,Server,None,ServerAndClient
NoStore bool 用于决定是否阻止敏感信息的二级存储。

例如一个OutputCache过滤器可以这样使用

        [OutputCache(Location= System.Web.UI.OutputCacheLocation.Client,Duration=60)]
        public ActionResult Login()
        {
            return View();
        }

 

或者有另外一种使用方式——使用配置文件,在<system.web>节点下添加以下设置

复制代码
    <caching>
      <outputCacheSettings>
        <outputCacheProfiles>
          <add name="testCache" location="Client" duration="60"/>
        </outputCacheProfiles>
      </outputCacheSettings>
    </caching>
复制代码

使用控制的时候就这样

        [OutputCache(CacheProfile="testCache")]
        public ActionResult Login()
        {
            return View();
        }

4           自定义过滤器

万一前面介绍的过滤器也满足不了需求,要在行为方法执行返回的前前后后定义自己的处理逻辑的话,这个自定义过滤器就应该能派上用场了。若要自定 义一个过滤器,则要继承ActionFilterAttribute类,这个类是一个抽象类,实现了IActionFilter和 IResultFilter接口,主要通过重写四个虚方法来达到在行为方法执行和返回的前后注入逻辑

方法

参数

描述

OnActionExecuting

ActionExecutingContext

在行为方法执行前执行

OnActionExecuted

ActionExecutedContext

在行为方法执行后执行

OnResultExecuting

ResultExecutingContext

在行为方法返回前执行

OnResultExecuted

ResultExecutedContext

在行为方法返回后执行

四个方法执行顺序是OnActionExecuting——>OnActionExecuted—— >OnResultExecuting——>OnResultExecuted。上面四个方法的参数都是继承基 ContollorContext类。例如下面定义了一个自定义的过滤器

复制代码
   public class MyCustomerFilterAttribute : ActionFilterAttribute
    {
        public string Message { get; set; }

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            base.OnActionExecuted(filterContext);
            filterContext.HttpContext.Response.Write(string.Format( "<br/> {0} Action finish Execute.....",Message));
        }

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            CheckMessage(filterContext);
            filterContext.HttpContext.Response.Write(string.Format("<br/> {0} Action start Execute.....", Message));
            base.OnActionExecuting(filterContext);
        }

        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            filterContext.HttpContext.Response.Write(string.Format("<br/> {0} Action finish Result.....", Message));
            base.OnResultExecuted(filterContext);
        }

        public override void OnResultExecuting(ResultExecutingContext filterContext)
        {
            filterContext.HttpContext.Response.Write(string.Format("<br/> {0} Action start Execute.....", Message));
            base.OnResultExecuting(filterContext);
        }

        private void CheckMessage(ActionExecutingContext filterContext)
        { 
            if(string.IsNullOrEmpty( Message)||string.IsNullOrWhiteSpace(Message))
                Message = filterContext.Controller.GetType().Name + "'s " + filterContext.ActionDescriptor.ActionName;
        }
    }
复制代码

 

使用它的行为方法定义如下

        [MyCustomerFilter]
        public ActionResult CustomerFilterTest()
        {
            Response.Write("<br/>Invking CustomerFilterTest Action");
            return View();
        }

 

执行结果如下

这个就证明了上面说的顺序。

当控制器也使用上这过滤器时,而行为方法不使用时,结果如下

如果控制器和行为方法都使用了过滤器,理论上是显示上面两个结果的有机结合。但实际不然,因为在定义过滤器的时候还少了 一个特性:[AttributeUsage(AttributeTargets.All, AllowMultiple = true)],把这个加在MyCustomerFilterAttribute就行了。

    [AttributeUsage(AttributeTargets.All, AllowMultiple = true)]//多次调用
    public class MyCustomerFilterAttribute : ActionFilterAttribute
    {
        ……
    }

 

由这幅图可以看出,同一个过滤器分别用在了控制器和行为方法中,执行同一个方法时都会有先后顺序,如果按默认值(不设Order的情况下),一 般的顺序是由最外层到最里层,就是“全局”——>“控制器”——>“行为方法”;而特别的就是错误处理的过滤器,由于异常是由里往外抛的,所 以它的顺序刚好也反过来:“行为方法”——>“控制器”——>“全局”。

既然这里有提到全局的过滤器,那么全局的过滤器是在Global.asax文件里面的RegisterGlobalFilters(GlobalFilterCollection filters)中设置的

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new HandleErrorAttribute());
            filters.Add(new MyFilters.MyCustomerFilterAttribute() { Message="Global"});//全局过滤器
        }

 

这里它默认也添加了一个错误处理的过滤器来处理整个MVC应用程序所抛出的异常。

好了,过滤器介绍到此结束了。最后附上了源码,整理的不算很好,有什么不足的错的恳请大家多多指点,谢谢!

FilterTest.rar

[转载]那两年炼就的Android内功修养 - 老罗的Android之旅 - 博客频道 - CSDN.NET

mikel阅读(1368)

[转载]那两年炼就的Android内功修养 – 老罗的Android之旅 – 博客频道 – CSDN.NET.

经过两年的时间,终于完成对Android系统的研究了。Android是一个博大精深的系统,老罗不敢说自己精通了(事实上最讨厌的就是说自己精通神马 神马的了,或者说企业说要招聘精通神马神马的人才),但是至少可以说打通了整个Android系统,从最上面的应用层,一直到最下面的Linux内核,炼 就的是一种内功修养。这篇文章和大家一起分享这两年研究Android系统的历程,以此感谢大家一直以来的支持和鼓励。        以下是本文的提纲:

1. 理念

2. 里程碑

3. 看过的书

4. 研究过的内容

5. 将来要做的事情

它们涵盖了老罗这两年一直想要和大家分享的内容。好了,不说废话了,直入主题。

一. 理念

这里说的理念是说应该带什么样的心态去研究一个系统。古人说书中自的颜如玉,书中自有黄金屋,我想说代码里也有颜如玉和黄金屋,所以老罗希望大家都能 “Read The Fucking Source Code”。再者,对于优秀的开源项目来说,不去读一下它的源代码,简直就是暴殄天物啊。那么,读代码有什么好处呢?太多了,除了可以学到别人的优秀代 码、架构之外,最重要的是,我们能从中找到答案,从而可以解决自己项目上的燃眉之急。

我们在项目中碰到问题的时候,通常第一反应都是到网上去搜索答案。但是有时候有些问题,网络并不能给出满意的答案。这时候就千万不要忘了你所拥有的一个大 招——从代码中找答案!当然,从代码中找答案说起来是轻松,但是等到直正去找时,可能就会发现云里雾里,根本不知道那些代码在说什么东东,甚至连自己想要 看的源代码文件都不知道在哪里。这就要求平时就要养成读代码的习惯,不要临时抱佛脚。有时候临时抱佛脚是能解决问题,但是千万不能抱着这种侥幸心里,掌握 一门技术还是需要踏踏实实地一步一步走。

胡克其实在牛顿之前,就发现了万有引力定律,并且推导出了正确的公式。但由于数学不好,他只能勉强解释行星绕日的圆周运动,而没有认识到支配天体运行的力 量是“万有”的。后来数学狂人牛顿用微积分圆满地解决了胡克的问题,并且把他提出的力学三条基本定律推广到了星系空间,改变了自从亚里士多德以来公认的天 地不一的旧观点,被科学界奉为伟大的发现。胡克大怒,指责牛顿剽窃了他的成果。牛顿尖酸刻薄的回敬:是啊,我他妈还真是站在巨人的肩膀上呢!

我们有理由相信像牛顿、乔布斯之类的狂人,不用站在巨人的肩膀上也能取得瞩目的成就。但是,我们不是牛顿,也不是乔布斯,所以在看代码之前,还是找一些前 人总结的资料来看看吧。拿Android系统来说,你在至少得懂点Linux内核基础吧!所以在看Android源代码之前,先找些Linux内核的经典 书籍来看看吧,骚年!后面老罗会推荐一些书籍给大家。

另外,我们知道,现在的互联网产品,讲究的是快速迭代。Android系统自第一个版本发布以来,到现在已经经历了很多版本呢?那么我们应该如何去选择版 本来阅读呢?一般来说,就是选择最新的版本来阅读了。不过随着又有新版本的源代码的发布,我们所看的源代码就会变成旧版本。这时候心里就会比较纠结:是应 该继续看旧的代码,还是去追新版本的代码呢?就当是看连续剧,一下子跳到前面去,可能就不知道讲什么了。其实版本就算更新得再快,基础的东西也是不会轻易 变化的。我们看代码时,要抱着的一个目的就是弄懂它的骨架和脉络。毕竟对于一个系统来说,它是有很多细节的,我们无法在短时间把它们都完全吃透。但是主要 我们掌握了它的骨架和脉络,以后无论是要了解它的什么细节,都可以很轻轻地找到相关的源文件,并且可以很容易进入主题。

坦白说,对于Android系统,很多细节我也不了解。所以有时候你们可以看到,在博客文章后面的评论上,有些同学问的一些比较具体的问题,我是没有回复 的。一来是我不懂,二来是我也没有时间去帮这些同学去扒代码来看。这也是在文章一开头,我就说自己没有精通Android系统的原因。但是请相信,主要你 熟悉Android系统的代码,并且有出现问题的现场,顺藤摸瓜跟着代码走下去,并且多一点耐心和细心,是可以解决问题的!

关于Android版本的问题,相信大家都知道我现在的文章都是基于2.3来写的。很多同学都说我out了,现在都4.2了,甚至4.3或者5.0都要出 来了,还在看2.3。我想说的是,主要你掌握了它的骨架和脉络,无论版本上怎么变化,原理都是一样的,这就是以不变应万变之道。因此,我就一直坚持研究 2.3,这样可以使得前前后后研究的东西更连贯一致,避免分散了自己的精力。如果还有疑问的话,后面我讲到Android的UI架构时,就会简单对比一下 4.2和2.3的不同,其实就会发现,基本原理还是一样的!

说到Android系统的骨架和脉络,也有同学抱怨我的文章里面太多代码细节了,他们希望我可以抽象一下,用高度概括的语言或者图像来勾勒出每一个模块的 轮廓。我想说的是,如果你不看代码,不了解细节,即使我能够用概括的语言或者图像来勾勒出这样的轮廓出来,也许这个轮廓只有我才能看得懂。

我在真正开始看Android系统的源代码之前,也是有这样的想法,希望能有一张图来清楚地告诉我Android系统的轮廓,例如,HAL为什么要将驱动 划分成用户空间和内核空间两部分,为什么说Binder是所有IPC机制效率最高的。我确实是从网上得到抽象的资料来解释这两个问题,但是这些资料对我来 说,还是太抽象了,以至于我有似懂非懂的感觉,实际上就是不懂!就是因为这样,激发了我要从代码中找答案的念头!现在当我回过头来这些所谓抽象的轮廓时, 我就清楚地知道它讲的是什么了。

所以古人云“天将降大任于斯人也,必先苦其心志,劳其筋骨,饿其体肤”是有道理的,因为只有亲身经历过一些磨难后得到的东西才是真实的!

好了,关于理念的问题,就完了,这里再做一下总结:

1. 从代码中找答案——Read The Fucking Source Code。

2. 以不变应万变——坚持看一个版本的代码直至理清它的骨架和脉络。

二. 里程碑

研究Android 2.3期间,主要是经历了以下五个时间点,如图1所示:

图1 研究Android 2.3的里程碑

         从2011年06月21日第一篇博客文章开始,到2013年06月03日结束对Android 2.3的研究,一共是差不多两年的时间,一个从无到有的过程。其中,最痛苦的莫过于是2011年12月下旬到2012年06月12日这6个多月的时间里 面,整理了2011年12月下旬前的所有博客文章,形成了《Android系统源代码情景分析》一书,并且最终在2012年10月下旬正式上市。

总的来说,就是在两年的时间里面,获得了以下的两个产出:

1. 《老罗的Android之旅》博客专栏93篇文章,1857224次访问,4156条评论,13440积分,排名154。

2. 《Android系统源代码情景分析》一书3大篇16章,830页,1570000字。

以上产出除了能帮助到广大的网友之外,也让自己理清了Android系统的骨架和脉络。这些骨架和脉络接下来再总结。2013年06月03日之后,将何去何从?接下来老罗也会简单说明。

三. 看过的书

在2011年06月21日开始写博客之前,其实已经看过不少的书。在2011年06月21日之后,也一边写博客一边看过不少的书。这个书单很长,下面我主要分类列出一些主要的和经典的。

语言:

《深度探索C++对象模型》,对应的英文版是《Inside C+++ Object Model》

程序编译、链接、加载:

《链接器和加载器》,对应的英文版是《Linker and Loader》

《程序员的自我修养:链接、装载和库》

操作系统:

《Linux内核设计与实现》,对应的英文版是《Linux Kernel Development》

《深入理解Linux内核》,对应的英文版是《Understanding the Linux Kernel》

《深入Linux内核架构》,对应的英文版是《Professional Linux Kernel Architecture》

《Linux内核源代码情景分析》

网络:

《Linux 网络体系结构:Linux内核中网络协议的设计与实现》,对应的英文版是《The Linux Networking Architecture: Design and Implementation of Network Protocols in the Linux Kernel》

《深入理解LINUX网络技术内幕》,对应的英文版是《 Understanding Linux Network Internals》

设备驱动:

《Linux设备驱动程序》,对应的英文版是《Linux Device Drivers》

《精通Linux设备驱动程序开发》,对应的英文版是《Essential Linux Device Drivers》

虚拟机:

《Java SE 7虚拟机规范》

《深入Java虚拟机》,对应的英文版是《Inside the Java Virtual Machine》

《Oracle JRockit: The Definitive Guide》

嵌入式:

《嵌入式Linux开发》,对应的英文版是《Embedded Linux Primer》

《构建嵌入式Linux系统》,对应的英文版是《Building Embedded Linux Systems》

ARM体系架构:

《ARM嵌入式系统开发:软件设计与优化》,对应的英文版是《ARM System Developer’s Guide: Designing and Optimizing System Software》

综合:

《深入理解计算机系统》,对应的英文版是《Computer Systems: A Programmer’s Perspective》

上面介绍的这些书,都是属于进阶级别的,所以要求要有一定的语言基础以及操作系统基础。此外,对于看书,老罗有一些观点,供大家参考:

1. 书不是要用的时候才去看的,要养成经常看书、终身学习的习惯。

2. 不要只看与目前自己工作相关的书,IT技术日新月异,三五年河东,三五年河西。

3. 书看得多了,就会越看越快,学习新的东西时也越容易进入状态。

对于Android应用开发,力推官方文档:

http://developer.android.com/training/index.html

http://developer.android.com/guide/components/index.html

http://developer.android.com/tools/index.html

四. 研究过的内容

整个博客的内容看似松散,实际上都是有组织有计划的,目标是打通整个Android系统,从最上面的应用层,到最下面的Linux内核层。简单来说,博客的所有文章可以划分为“三横三纵”,如图2所示:

图2 Android系统研究之“三横三纵”

        接下来,老罗就分别描述这三条横线和纵线,并且给出对应的博客文章链接。

1. 准备 — Preparation — 横线

主要就是:

(1)通过阅读相关的书籍来了解Linux内核和Android应用基础知识

Android学习启动篇

(2)搭建好Android源代码环境

在Ubuntu上下载、编译和安装Android最新源代码

在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)

如何单独编译Android源代码中的模块

制作可独立分发的Android模拟器

(3)Android系统有很多C++代码,这些C++代码用到了很多智能指针,因此有必要了解一下Android系统在C/C++ Runtime Framework中提供的智能指针

Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析

2. 专用驱动 — Proprietary Drivers — 横线

这些专用驱动就是指Logger、Binder和Ashmem,它们整个Android系统的基石:

(1)Logger

 浅谈Android系统开发中LOG的使用

Android日志系统驱动程序Logger源代码分析

Android应用程序框架层和系统运行库层日志系统源代码分析

Android日志系统Logcat源代码简要分析

(2)Binder

Android进程间通信(IPC)机制Binder简要介绍和学习计划

浅谈Service Manager成为Android进程间通信(IPC)机制Binder守护进程之路

浅谈Android系统进程间通信(IPC)机制Binder中的Server和Client获得Service Manager接口之路

Android系统进程间通信(IPC)机制Binder中的Server启动过程源代码分析

Android系统进程间通信(IPC)机制Binder中的Client获得Server远程接口过程源代码分析

Android系统进程间通信Binder机制在应用程序框架层的Java接口源代码分析

(3)Ashmem

Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划

Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析

Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析

Android系统匿名共享内存(Anonymous Shared Memory)C++调用接口分析

3. 硬件抽象层 — HAL — 纵线

硬件抽层象最适合用作Android系统的学习入口,它从下到上涉及到了Android系统的各个层次:

Android硬件抽象层(HAL)概要介绍和学习计划

在Ubuntu上为Android系统编写Linux内核驱动程序

在Ubuntu上为Android系统内置C可执行程序测试Linux内核驱动程序

在Ubuntu上为Android增加硬件抽象层(HAL)模块访问Linux内核驱动程序

在Ubuntu为Android硬件抽象层(HAL)模块编写JNI方法提供Java访问硬件服务接口

在Ubuntu上为Android系统的Application Frameworks层增加硬件访问服务

在Ubuntu上为Android系统内置Java应用程序测试Application Frameworks层的硬件服务

4. 应用程序组件 — Application Component — 纵线

应用程序组件是Android系统的核心,为开发者提供了贴心的服务。应用程序组件有四种,分别是Activity、Service、Broadcast Receiver和Content Provider。围绕应用程序组件,又有应用程序进程、消息循环和安装三个相关模块。

(1)Activity

Android应用程序的Activity启动过程简要介绍和学习计划

Android应用程序启动过程源代码分析

Android应用程序内部启动Activity过程(startActivity)的源代码分析

Android应用程序在新的进程中启动新的Activity的方法和过程分析

解开Android应用程序组件Activity的”singleTask”之谜

(2)Service

Android系统在新进程中启动自定义服务过程(startService)的原理分析

Android应用程序绑定服务(bindService)的过程源代码分析

(3)Broadcast Receiver

Android系统中的广播(Broadcast)机制简要介绍和学习计划

Android应用程序注册广播接收器(registerReceiver)的过程分析

Android应用程序发送广播(sendBroadcast)的过程分析

(4)Content Provider

Android应用程序组件Content Provider简要介绍和学习计划

Android应用程序组件Content Provider应用实例

Android应用程序组件Content Provider的启动过程源代码分析

Android应用程序组件Content Provider在应用程序之间共享数据的原理分析

Android应用程序组件Content Provider的共享数据更新通知机制分析

(5)进程

Android系统进程Zygote启动过程的源代码分析

Android应用程序进程启动过程的源代码分析

(6)消息循环

Android应用程序消息处理机制(Looper、Handler)分析

Android应用程序键盘(Keyboard)消息处理机制分析

Android应用程序线程消息循环模型分析

(7)安装

Android应用程序安装过程源代码分析

Android系统默认Home应用程序(Launcher)的启动过程源代码分析

5. 用户界面架构 — UI — 纵线

大家对老罗现在还在写Android 2.3的UI架构意见最大,认为已经过时了。老罗认为持有这种观点的人,都是没有经过认真思考的。老罗承认,从Android 4.0开始,UI部分发生了比较大的变化。但是请注意,这些变化都是在Android  2.3的UI架构基础之上进行的,也就是说,Android  2.3的UI架构并没有过时。你不能说Android 4.0在Android  2.3之上增加了一些feature,就说Android  2.3过时了。

下面这张是从Android官网拿过来的最新UI渲染流程图,也就是4.2的UI渲染流程图

图2 Android 4.2的UI渲染流程

        从这张图可以看出关于Android的UI架构的三条主线:

(1)每一个Window的Surface都怎样渲染的?不管怎么样,最后渲染出来的都是一个Buffer,交给SurfaceFlinger合成到Display上。

(2)SurfaceFlinger是怎样合成每一个Window的Surface的?

(3)WindowManamgerService是怎么样管理Window的?

第(1)和第(2)两个点在2.3和4.2之间有变化,主要是因为增加了GPU的支持,具体就表现为Window的Surface在渲染的时候使用了 GPU,而SurfaceFlinger在合成每一个Window的Surface的时候,也使用了GPU或者Overlay和Blitter这些硬件加 速,但是主体流程都没有变,也就是说,Window的Surface渲染好之后,最终依然是交给SurfaceFlinger来合成。此外,虽然我还没有 开始看4.2的代码,但是可以看得出,4.2里面的HWComposer,只不过是封装和抽象了2.3就有的Overlay和Blitter,而 SurfaceTexture的作用与2.3的SurfaceComposerClient、SurfaceControl也是类似的。第(3)点基本上 就没有什么变化,除非以后要支持多窗口。

通过上述对比,只想强调一点:Android 2.3的UI架构并没有过时,是值得去研究的,并且在2.3的基础上去研究4.2的UI架构,会更有帮助。

仁者见仁,智者见智,Android 2.3的UI架构的说明就到此为止,接下来它的分析路线,都是围绕上述三个点来进行的。

首先是以开机动画为切入点,了解Linux内核里面的驱动:

Android系统的开机画面显示过程分析

FB驱动抽象了显卡,上面的用户空间程序就是通过它来显示UI的。

HAL层的Gralloc模块对FB驱动进行了封装,以方便SurfaceFlinger对它进行访问:

Android帧缓冲区(Frame Buffer)硬件抽象层(HAL)模块Gralloc的实现原理分析

SurfaceFlinger负责合成各个应用程序窗口的UI,也就是将各个窗口的UI合成,并且通过FB显示在屏幕上。在对SurfaceFlinger进行分析之前,我们首先了解应用程序是如何使用的它的:

Android应用程序与SurfaceFlinger服务的关系概述和学习计划

Android应用程序与SurfaceFlinger服务的连接过程分析

Android应用程序与SurfaceFlinger服务之间的共享UI元数据(SharedClient)的创建过程分析

Android应用程序请求SurfaceFlinger服务创建Surface的过程分析

Android应用程序请求SurfaceFlinger服务渲染Surface的过程分析

万事俱备,可以开始分析SurfaceFlinger了:

Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划

Android系统Surface机制的SurfaceFlinger服务的启动过程分析

Android系统Surface机制的SurfaceFlinger服务对帧缓冲区(Frame Buffer)的管理分析

Android系统Surface机制的SurfaceFlinger服务的线程模型分析

Android系统Surface机制的SurfaceFlinger服务渲染应用程序UI的过程分析

SurfaceFlinger操作的对象是应用程序窗口,因此,我们要掌握应用程序窗口的组成:

Android应用程序窗口(Activity)实现框架简要介绍和学习计划

Android应用程序窗口(Activity)的运行上下文环境(Context)的创建过程分析

Android应用程序窗口(Activity)的窗口对象(Window)的创建过程分析

Android应用程序窗口(Activity)的视图对象(View)的创建过程分析

Android应用程序窗口(Activity)与WindowManagerService服务的连接过程分析

Android应用程序窗口(Activity)的绘图表面(Surface)的创建过程分析

Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析

应用程序窗口是由WindowManagerService进行管理的,并且也是WindowManagerService负责提供窗口信息给SurfaceFlinger的,因此,我们最后分析WindowManagerService:

Android窗口管理服务WindowManagerService的简要介绍和学习计划

Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析

Android窗口管理服务WindowManagerService对窗口的组织方式分析

Android窗口管理服务WindowManagerService对输入法窗口(Input Method Window)的管理分析

Android窗口管理服务WindowManagerService对壁纸窗口(Wallpaper Window)的管理分析

Android窗口管理服务WindowManagerService计算窗口Z轴位置的过程分析

Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析

Android窗口管理服务WindowManagerService切换Activity窗口(App Transition)的过程分析

Android窗口管理服务WindowManagerService显示窗口动画的原理分析

上述内容都研究清楚之后,Android系统的UI架构的骨架就清晰了。但是前面所研究的应用程序窗口还是太抽象了,我们有必要研究一下那些组成应用程序窗口内容的UI控件是怎么实现的,以TextView和SurfaceView为代表:

Android控件TextView的实现原理分析

Android视图SurfaceView的实现原理分析

最后,分析Android系统的UI架构,怎能不提它的资源管理框架?它有效地分离了代码和UI:

Android资源管理框架(Asset Manager)简要介绍和学习计划

Android应用程序资源的编译和打包过程分析

Android应用程序资源管理器(Asset Manager)的创建过程分析

Android应用程序资源的查找过程分析

分析这里,Android系统的UI架构就分析完成了,看出什么门道来没有?是的,我们以开机动画为切入点,从Linux内核空间的FB驱动,一直分析到 用户空间中HAL层模块Gralloc、C/C++ Runtime Framework层的SurfaceFlinger、Java Runtime Framework层的WindowMangerService、Window、Widget,以及资源管理框架,从下到上,披荆斩棘。

6. Dalvik虚拟机 — 横线

Android系统的应用程序及部分应用程序框架是使用Java语言开发的,它们运行在Dalvik虚拟机之上,还有另外一部分应用唾弃框架在使用 C/C++语言开发的。使用Java语言开发的应用程序框架老罗称之为Java Runtime Framework,而使用C/C++语言开发的应用程序框架老罗称之为C/C++ Runtime Framework,它们被Dalvik虚拟机一分为二。通过前面的学习,其实我们都已经了解Android系统的Java Runtime Framework和C/C++ Runtime Framework,因此,我们最后将注意力集中在Dalvik虚拟机上:

Dalvik虚拟机简要介绍和学习计划

Dalvik虚拟机的启动过程分析

Dalvik虚拟机的运行过程分析

Dalvik虚拟机JNI方法的注册过程分析

Dalvik虚拟机进程和线程的创建过程分析

学习完成“三横三纵”这六条主线之后,我们就可以自豪地说,从上到下地把Android系统打通,并且将它的骨架和脉络也理清了!

对 于“准备”、“专用驱动”、“HAL”、“应用程序组件”这四条主线,老罗极力推荐大家看《Android系统源代码情况分析》一书,内容比博客文章要系 统、详细很多,不说其它的,就单单是讲Binder进程间通信机制的那一章,就物超所值。在《Android系统源代码情景分析》一书中,老罗最引以为豪 的就是讲Binder进程间通信机制的第5章,网上或者其它书上绝对是找不到这么详尽的分析资料。

五. 将来要做的事情

接下来要做的主要是三件事情:

1. 继续研究Android系统

本来以为前段时间的Google I/O会发布Android 4.3或者5.0,然后老罗就以最新发布的版本为蓝本来进行研究。既然没有发布新版本,那么就只有以现在的最新发布版本 4.2为蓝本进行研究了。如前所述,4.2与2.3相比,最大的不同之处是默认增加了GPU支持,因此,老罗在接下来的一段时间里,将着重研究4.2的 GPU支持。

2. 停止博客更新

这两年投入在博客上的精力太多了,博客上的文章基本上熬夜熬出来的。大多数时候,一个话题要花一个星期左右的时间来看代码,然后再花四个星期左右的时间将 文章写出来。本来是这样计划的,依靠《Android系统源代码情景分析》一书的销量,可以在经济上得到一定的回报,然后可以继续在博客上投入,直至把 4.x版本的GPU支持写完,最后再整理出一本关于Android系统UI架构的书。但是最近询问了一下书的销量,差强人意,达不到预期目标。由于没有形 成良性循环,因此没有办法,只好停止博客更新。老罗需要把精力投入在其它事情上,希望大家谅解!

3. 仍然会持续地进行一些小分享

主要是一些随笔分享,这些分享主要会发布在微博或者QQ群上面,那里也方便一些和大家进行讨论。此外,老罗也乐意和大家进行一些线下分享,主要针对企业或 者单位组织的沙龙、活动和会议等,同时也可以单独地针对企业内部进行分享。不过前提当然是举办方对《老罗的Android之旅》专栏或者《Android 系统源代码情景分析》一书的内容感兴趣,并且邀请老罗去参加。

如果需要邀请老罗去参加分享,可以通过微博或者邮箱和老罗联系,非常感谢大家两年以来的支持!

邮箱:shyluo@gmail.com

微博:http://weibo.com/shengyangluo

[转载]Android视图SurfaceView的实现原理分析 - 老罗的Android之旅 - 博客频道 - CSDN.NET

mikel阅读(900)

[转载]Android视图SurfaceView的实现原理分析 – 老罗的Android之旅 – 博客频道 – CSDN.NET.

Android系统中,有一种特殊的视图,称为SurfaceView,它拥有独立的绘图表面,即它不与其宿主窗口共享同一个绘图表面。由于拥有独立的 绘图表面,因此SurfaceView的UI就可以在一个独立的线程中进行行绘制。又由于不占用主线程资源,SurfaceView一方面可以实现复杂而 高效的UI,另一方面又不会导致用户输入得不到及时响应。在本文中,我们就详细分析SurfaceView的实现原理。

在前面Android控件TextView的实现原理分析一 文中提到,普通的Android控件,例如TextView、Button和CheckBox等,它们都是将自己的UI绘制在宿主窗口的绘图表面之上,这 意味着它们的UI是在应用程序的主线程中进行绘制的。由于应用程序的主线程除了要绘制UI之外,还需要及时地响应用户输入,否则的话,系统就会认为应用程 序没有响应了,因此就会弹出一个ANR对话框出来。对于一些游戏画面,或者摄像头预览、视频播放来说,它们的UI都比较复杂,而且要求能够进行高效的绘 制,因此,它们的UI就不适合在应用程序的主线程中进行绘制。这时候就必须要给那些需要复杂而高效UI的视图生成一个独立的绘图表面,以及使用一个独立的 线程来绘制这些视图的UI。

在前面Android应用程序与SurfaceFlinger服务的关系概述和学习计划Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划这 两个系统的文章中,我们主要分析了Android应用程序窗口是如何通过SurfaceFlinger服务来绘制自己的UI的。一般来说,每一个窗口在 SurfaceFlinger服务中都对应有一个Layer,用来描述它的绘图表面。对于那些具有SurfaceView的窗口来说,每一个 SurfaceView在SurfaceFlinger服务中还对应有一个独立的Layer或者LayerBuffer,用来单独描述它的绘图表面,以区 别于它的宿主窗口的绘图表面。

无论是LayerBuffer,还是Layer,它们都是以LayerBase为基类的,也就是说,SurfaceFlinger服务把所有的 LayerBuffer和Layer都抽象为LayerBase,因此就可以用统一的流程来绘制和合成它们的UI。由于LayerBuffer的绘制和合 成与Layer的绘制和合成是类似的,因此本文不打算对LayerBuffer的绘制和合成操作进行分析。需要深入理解LayerBuffer的绘制和合 成操作的,可以参考Android应用程序与SurfaceFlinger服务的关系概述和学习计划Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划这两个系统的文章。

为了接下来可以方便地描述SurfaceView的实现原理分析,我们假设在一个Activity窗口的视图结构中,除了有一个DecorView顶层视 图之外,还有两个TextView控件,以及一个SurfaceView视图,这样该Activity窗口在SurfaceFlinger服务中就对应有 两个Layer或者一个Layer的一个LayerBuffer,如图1所示:

图1 SurfaceView及其宿主Activity窗口的绘图表面示意图

         在图1中,Activity窗口的顶层视图DecorView及其两个TextView控件的UI都是绘制在SurfaceFlinger服务中的同一 个Layer上面的,而SurfaceView的UI是绘制在SurfaceFlinger服务中的另外一个Layer或者LayerBuffer上的。

注意,用来描述SurfaceView的Layer或者LayerBuffer的Z轴位置是小于用来其宿主Activity窗口的Layer的Z轴位置 的,但是前者会在后者的上面挖一个“洞”出来,以便它的UI可以对用户可见。实际上,SurfaceView在其宿主Activity窗口上所挖的“洞” 只不过是在其宿主Activity窗口上设置了一块透明区域。

从总体上描述了SurfaceView的大致实现原理之后,接下来我们就详细分析它的具体实现过程,包括它的绘图表面的创建过程、在宿主窗口上面进行挖洞的过程,以及绘制过程。

1. SurfaceView的绘图表面的创建过程

由于SurfaceView具有独立的绘图表面,因此,在它的UI内容可以绘制之前,我们首先要将它的绘图表面创建出来。尽管SurfaceView不与 它的宿主窗口共享同一个绘图表面,但是它仍然是属于宿主窗口的视图结构的一个结点的,也就是说,SurfaceView仍然是会参与到宿主窗口的某些执行 流程中去。

从前面Android应用程序窗口(Activity)的绘图表面(Surface)的创建过程分析一 文可以知道,每当一个窗口需要刷新UI时,就会调用ViewRoot类的成员函数performTraversals。ViewRoot类的成员函数 performTraversals在执行的过程中,如果发现当前窗口的绘图表面还没有创建,或者发现当前窗口的绘图表面已经失效了,那么就会请求 WindowManagerService服务创建一个新的绘图表面,同时,它还会通过一系列的回调函数来让嵌入在窗口里面的SurfaceView有机 会创建自己的绘图表面。

接下来,我们就从ViewRoot类的成员函数performTraversals开始,分析SurfaceView的绘图表面的创建过程,如图2所示:

图2 SurfaceView的绘图表面的创建过程

        这个过程可以分为8个步骤,接下来我们就详细分析每一个步骤。

Step 1. ViewRoot.performTraversals

  1. public final class ViewRoot extends Handler implements ViewParent,  
  2.         View.AttachInfo.Callbacks {  
  3.     ……  
  4.   
  5.     private void performTraversals() {  
  6.         // cache mView since it is used so much below…  
  7.         final View host = mView;  
  8.         ……  
  9.   
  10.         final View.AttachInfo attachInfo = mAttachInfo;  
  11.   
  12.         final int viewVisibility = getHostVisibility();  
  13.         boolean viewVisibilityChanged = mViewVisibility != viewVisibility  
  14.                 || mNewSurfaceNeeded;  
  15.         ……  
  16.   
  17.   
  18.         if (mFirst) {  
  19.             ……  
  20.   
  21.             if (!mAttached) {  
  22.                 host.dispatchAttachedToWindow(attachInfo, 0);  
  23.                 mAttached = true;  
  24.             }  
  25.               
  26.             ……  
  27.         }  
  28.    
  29.         ……  
  30.   
  31.         if (viewVisibilityChanged) {  
  32.             ……  
  33.   
  34.             host.dispatchWindowVisibilityChanged(viewVisibility);  
  35.   
  36.             ……  
  37.         }  
  38.   
  39.         ……  
  40.   
  41.         mFirst = false;  
  42.   
  43.         ……  
  44.     }  
  45.   
  46.     ……  
  47. }  

这个函数定义在文件frameworks/base/core/java/android/view/ViewRoot.java中。

ViewRoot类的成员函数performTraversals的详细实现可以参考Android应用程序窗口(Activity)的绘图表面(Surface)的创建过程分析Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析这两篇文章,这里我们只关注与SurfaceView的绘图表面的创建相关的逻辑。

我们首先分析在ViewRoot类的成员函数performTraversals中四个相关的变量host、attachInfo、viewVisibility和viewVisibilityChanged。

变量host与ViewRoot类的成员变量mView指向的是同一个DecorView对象,这个DecorView对象描述的当前窗口的顶层视图。

变量attachInfo与ViewRoot类的成员变量mAttachInfo指向的是同一个AttachInfo对象。在Android系统中,每一个视图附加到它的宿主窗口的时候,都会获得一个AttachInfo对象,用来被附加的窗口的信息。

变量viewVisibility描述的是当前窗口的可见性。

变量viewVisibilityChanged描述的是当前窗口的可见性是否发生了变化。

ViewRoot类的成员变量mFirst表示当前窗口是否是第一次被刷新UI。如果是的话,那么它的值就会等于true,说明当前窗口的绘图表面还 未创建。在这种情况下,如果ViewRoot类的另外一个成员变量mAttached的值也等于true,那么就表示当前窗口还没有将它的各个子视图附加 到它的上面来。这时候ViewRoot类的成员函数performTraversals就会从当前窗口的顶层视图开始,通知每一个子视图它要被附加到宿主 窗口上去了,这是通过调用变量host所指向的一个DecorView对象的成员函数dispatchAttachedToWindow来实现的。 DecorView类的成员函数dispatchAttachedToWindow是从父类ViewGroup继承下来的,在后面的Step 2中,我们再详细分析ViewGroup类的成员数dispatchAttachedToWindow的实现。

接下来, ViewRoot类的成员函数performTraversals判断当前窗口的可见性是否发生了变化,即检查变量 viewVisibilityChanged的值是否等于true。如果发生了变化,那么就会从当前窗口的顶层视图开始,通知每一个子视图它的宿主窗口的 可见发生变化了,这是通过调用变量host所指向的一个DecorView对象的成员函数 dispatchWindowVisibilityChanged来实现的。DecorView类的成员函数 dispatchWindowVisibilityChanged是从父类ViewGroup继承下来的,在后面的Step 5中,我们再详细分析ViewGroup类的成员数dispatchWindowVisibilityChanged的实现。

我们假设当前窗口有一个SurfaceView,那么当该SurfaceView接收到它被附加到宿主窗口以及它的宿主窗口的可见性发生变化的通知时,就 会相应地将自己的绘图表面创建出来。接下来,我们就分别分析ViewGroup类的成员数dispatchAttachedToWindow和 dispatchWindowVisibilityChanged的实现,以便可以了解SurfaceView的绘图表面的创建过程。

Step 2. ViewGroup.dispatchAttachedToWindow

  1. public abstract class ViewGroup extends View implements ViewParent, ViewManager {  
  2.     ……  
  3.   
  4.     // Child views of this ViewGroup  
  5.     private View[] mChildren;  
  6.     // Number of valid children in the mChildren array, the rest should be null or not  
  7.     // considered as children  
  8.     private int mChildrenCount;  
  9.     ……  
  10.   
  11.     @Override  
  12.     void dispatchAttachedToWindow(AttachInfo info, int visibility) {  
  13.         super.dispatchAttachedToWindow(info, visibility);  
  14.         visibility |= mViewFlags & VISIBILITY_MASK;  
  15.         final int count = mChildrenCount;  
  16.         final View[] children = mChildren;  
  17.         for (int i = 0; i < count; i++) {  
  18.             children[i].dispatchAttachedToWindow(info, visibility);  
  19.         }  
  20.     }  
  21.   
  22.     ……  
  23. }  

这个函数定义在文件frameworks/base/core/java/android/view/ViewGroup.java中。

ViewGroup类的成员变量mChildren保存的是当前正在处理的视图容器的子视图,而另外一个成员变量mChildrenCount保存的是这些子视图的数量。

ViewGroup类的成员函数dispatchAttachedToWindow的实现很简单,它只是简单地调用当前正在处理的视图容器的每一个子 视图的成员函数dispatchAttachedToWindow,以便可以通知这些子视图,它们被附加到宿主窗口上去了。

当前正在处理的视图容器即为当前正在处理的窗口的顶层视图,由于前面我们当前正在处理的窗口有一个SurfaceView,因此这一步就会调用到该SurfaceView的成员函数dispatchAttachedToWindow。

由于SurfaceView类的成员函数dispatchAttachedToWindow是从父类View继承下来的,因此,接下来我们就继续分析View类的成员函数dispatchAttachedToWindow的实现。

Step 3. View.dispatchAttachedToWindow

  1. public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {  
  2.     ……  
  3.   
  4.     AttachInfo mAttachInfo;  
  5.     ……  
  6.   
  7.     void dispatchAttachedToWindow(AttachInfo info, int visibility) {  
  8.         //System.out.println(“Attached! ” + this);  
  9.         mAttachInfo = info;  
  10.         ……  
  11.   
  12.         onAttachedToWindow();  
  13.   
  14.         ……  
  15.     }  
  16.   
  17.     ……  
  18. }  

这个函数定义在文件frameworks/base/core/java/android/view/View.java中。

View类的成员函数dispatchAttachedToWindow首先将参数info所指向的一个AttachInfo对象保存在自己的成员变 量mAttachInfo中,以便当前视图可以获得其所附加在的窗口的相关信息,接下来再调用另外一个成员函数onAttachedToWindow来让 子类有机会处理它被附加到宿主窗口的事件。

前面我们已经假设了当前处理的是一个SurfaceView。SurfaceView类重写了父类View的成员函数 onAttachedToWindow,接下来我们就继续分析SurfaceView的成员函数onAttachedToWindow的实现,以便可以了 解SurfaceView的绘图表面的创建过程。

Step 4. SurfaceView.onAttachedToWindow

  1. public class SurfaceView extends View {  
  2.     ……  
  3.   
  4.     IWindowSession mSession;  
  5.     ……  
  6.   
  7.     @Override  
  8.     protected void onAttachedToWindow() {  
  9.         super.onAttachedToWindow();  
  10.         mParent.requestTransparentRegion(this);  
  11.         mSession = getWindowSession();  
  12.         ……  
  13.     }  
  14.   
  15.     ……  
  16. }  

这个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。

SurfaceView类的成员函数onAttachedToWindow做了两件重要的事。

第一件事情是通知父视图,当前正在处理的SurfaceView需要在宿主窗口的绘图表面上挖一个洞,即需要在宿主窗口的绘图表面上设置一块透明区域。当 前正在处理的SurfaceView的父视图保存在父类View的成员变量mParent中,通过调用这个成员变量mParent所指向的一个 ViewGroup对象的成员函数requestTransparentRegion,就可以通知到当前正在处理的SurfaceView的父视图,当前 正在处理的SurfaceView需要在宿主窗口的绘图表面上设置一块透明区域。在后面第2部分的内容中,我们再详细分析SurfaceView在宿主窗 口的绘图表面的挖洞过程。

第二件事情是调用从父类View继承下来的成员函数getWindowSession来获得一个实现了IWindowSession接口的Binder 代理对象,并且将该Binder代理对象保存在SurfaceView类的成员变量mSession中。从前面Android应用程序窗口(Activity)实现框架简要介绍和学习计划这 个系列的文章可以知道,在Android系统中,每一个应用程序进程都有一个实现了IWindowSession接口的Binder代理对象,这个 Binder代理对象是用来与WindowManagerService服务进行通信的,View类的成员函数getWindowSession返回的就 是该Binder代理对象。在接下来的Step 8中,我们就可以看到,SurfaceView就可以通过这个实现了IWindowSession接口的Binder代理对象来请求 WindowManagerService服务为自己创建绘图表面的。

这一步执行完成之后,返回到前面的Step 1中,即ViewRoot类的成员函数performTraversals中,我们假设当前窗口的可见性发生了变化,那么接下来就会调用顶层视图的成员函 数dispatchWindowVisibilityChanged,以便可以通知各个子视图,它的宿主窗口的可见性发生化了。

窗口的顶层视图是使用DecorView类来描述的,而DecroView类的成员函数dispatchWindowVisibilityChanged 是从父类ViewGroup类继承下来的,因此,接下来我们就继续分析GroupView类的成员函数 dispatchWindowVisibilityChanged的实现,以便可以了解包含在当前窗口里面的一个SurfaceView的绘图表面的创建 过程。

Step 5. ViewGroup.dispatchWindowVisibilityChanged

  1. public abstract class ViewGroup extends View implements ViewParent, ViewManager {   
  2.     ……  
  3.   
  4.     @Override  
  5.     public void dispatchWindowVisibilityChanged(int visibility) {  
  6.         super.dispatchWindowVisibilityChanged(visibility);  
  7.         final int count = mChildrenCount;  
  8.         final View[] children = mChildren;  
  9.         for (int i = 0; i < count; i++) {  
  10.             children[i].dispatchWindowVisibilityChanged(visibility);  
  11.         }  
  12.     }  
  13.   
  14.     ……  
  15. }  

这个函数定义在文件frameworks/base/core/java/android/view/ViewGroup.java中。

ViewGroup类的成员函数dispatchWindowVisibilityChanged的实现很简单,它只是简单地调用当前正在处理的视图容器 的每一个子视图的成员函数dispatchWindowVisibilityChanged,以便可以通知这些子视图,它们所附加在的宿主窗口的可见性发 生变化了。

当前正在处理的视图容器即为当前正在处理的窗口的顶层视图,由于前面我们当前正在处理的窗口有一个SurfaceView,因此这一步就会调用到该SurfaceView的成员函数dispatchWindowVisibilityChanged。

由于SurfaceView类的成员函数dispatchWindowVisibilityChanged是从父类View继承下来的,因此,接下来我们 就继续分析View类的成员函数dispatchWindowVisibilityChanged的实现。

Step 6. View.dispatchWindowVisibilityChanged

  1. public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {  
  2.     ……  
  3.   
  4.     public void dispatchWindowVisibilityChanged(int visibility) {  
  5.         onWindowVisibilityChanged(visibility);  
  6.     }  
  7.   
  8.     ……  
  9. }  

这个函数定义在文件frameworks/base/core/java/android/view/View.java中。

View类的成员函数dispatchWindowVisibilityChanged的实现很简单,它只是调用另外一个成员函数onWindowVisibilityChanged来让子类有机会处理它所附加在的宿主窗口的可见性变化事件。

前面我们已经假设了当前处理的是一个SurfaceView。SurfaceView类重写了父类View的成员函数 onWindowVisibilityChanged,接下来我们就继续分析SurfaceView的成员函数 onWindowVisibilityChanged的实现,以便可以了解SurfaceView的绘图表面的创建过程。

Step 7. SurfaceView.onWindowVisibilityChanged

  1. public class SurfaceView extends View {  
  2.     ……  
  3.   
  4.     boolean mRequestedVisible = false;  
  5.     boolean mWindowVisibility = false;  
  6.     boolean mViewVisibility = false;  
  7.     …..  
  8.   
  9.     @Override  
  10.     protected void onWindowVisibilityChanged(int visibility) {  
  11.         super.onWindowVisibilityChanged(visibility);  
  12.         mWindowVisibility = visibility == VISIBLE;  
  13.         mRequestedVisible = mWindowVisibility && mViewVisibility;  
  14.         updateWindow(falsefalse);  
  15.     }  
  16.   
  17.     ……  
  18. }  

这个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。

SurfaceView类有三个用来描述可见性的成员变量mRequestedVisible、mWindowVisibility和 mViewVisibility。其中,mWindowVisibility表示SurfaceView的宿主窗口的可见 性,mViewVisibility表示SurfaceView自身的可见性。只有当mWindowVisibility和 mViewVisibility的值均等于true的时候,mRequestedVisible的值才为true,表示SurfaceView是可见的。

参数visibility描述的便是当前正在处理的SurfaceView的宿主窗口的可见性,因此,SurfaceView类的成员函数 onWindowVisibilityChanged首先将它记录在成员变量mWindowVisibility,接着再综合另外一个成员变量 mViewVisibility来判断当前正在处理的SurfaceView是否是可见的,并且记录在成员变量mRequestedVisible中。

最后,SurfaceView类的成员函数onWindowVisibilityChanged就会调用另外一个成员函数updateWindow来更新 当前正在处理的SurfaceView。在更新的过程中,如果发现当前正在处理的SurfaceView还没有创建绘图表面,那么就地请求 WindowManagerService服务为它创建一个。

接下来,我们就继续分析SurfaceView类的成员函数updateWindow的实现,以便可以了解SurfaceView的绘图表面的创建过程。

Step 8. SurfaceView.updateWindow

  1. public class SurfaceView extends View {  
  2.     ……  
  3.   
  4.     final Surface mSurface = new Surface();  
  5.     ……  
  6.   
  7.     MyWindow mWindow;  
  8.     …..  
  9.   
  10.     int mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;  
  11.     ……  
  12.   
  13.     int mRequestedType = –1;  
  14.     ……  
  15.   
  16.     private void updateWindow(boolean force, boolean redrawNeeded) {  
  17.         if (!mHaveFrame) {  
  18.             return;  
  19.         }  
  20.         ……  
  21.   
  22.         int myWidth = mRequestedWidth;  
  23.         if (myWidth <= 0) myWidth = getWidth();  
  24.         int myHeight = mRequestedHeight;  
  25.         if (myHeight <= 0) myHeight = getHeight();  
  26.   
  27.         getLocationInWindow(mLocation);  
  28.         final boolean creating = mWindow == null;  
  29.         final boolean formatChanged = mFormat != mRequestedFormat;  
  30.         final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;  
  31.         final boolean visibleChanged = mVisible != mRequestedVisible  
  32.                 || mNewSurfaceNeeded;  
  33.         final boolean typeChanged = mType != mRequestedType;  
  34.         if (force || creating || formatChanged || sizeChanged || visibleChanged  
  35.             || typeChanged || mLeft != mLocation[0] || mTop != mLocation[1]  
  36.             || mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) {  
  37.             ……  
  38.   
  39.             try {  
  40.                 final boolean visible = mVisible = mRequestedVisible;  
  41.                 mLeft = mLocation[0];  
  42.                 mTop = mLocation[1];  
  43.                 mWidth = myWidth;  
  44.                 mHeight = myHeight;  
  45.                 mFormat = mRequestedFormat;  
  46.                 mType = mRequestedType;  
  47.                 ……  
  48.   
  49.                 // Places the window relative  
  50.                 mLayout.x = mLeft;  
  51.                 mLayout.y = mTop;  
  52.                 mLayout.width = getWidth();  
  53.                 mLayout.height = getHeight();  
  54.                 ……  
  55.   
  56.                 mLayout.memoryType = mRequestedType;  
  57.   
  58.                 if (mWindow == null) {  
  59.                     mWindow = new MyWindow(this);  
  60.                     mLayout.type = mWindowType;  
  61.                     ……  
  62.                     mSession.addWithoutInputChannel(mWindow, mLayout,  
  63.                             mVisible ? VISIBLE : GONE, mContentInsets);  
  64.                 }  
  65.                 ……  
  66.   
  67.                 mSurfaceLock.lock();  
  68.                 try {  
  69.                     ……  
  70.   
  71.                     final int relayoutResult = mSession.relayout(  
  72.                         mWindow, mLayout, mWidth, mHeight,  
  73.                             visible ? VISIBLE : GONE, false, mWinFrame, mContentInsets,  
  74.                             mVisibleInsets, mConfiguration, mSurface);  
  75.                     ……  
  76.   
  77.                 } finally {  
  78.                     mSurfaceLock.unlock();  
  79.                 }  
  80.   
  81.                 ……  
  82.             } catch (RemoteException ex) {  
  83.             }  
  84.   
  85.             …..  
  86.         }  
  87.     }  
  88.   
  89.     ……  
  90. }  

这个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。

在分析SurfaceView类的成员函数updateWindow的实现之前,我们首先介绍一些相关的成员变量的含义,其中,mSurface、mWindow、mWindowType和mRequestedType这四个成员变量是最重要的。

SurfaceView类的成员变量mSurface指向的是一个Surface对象,这个Surface对象描述的便是SurfaceView专有 的绘图表面。对于一般的视图来说,例如,TextView或者Button,它们是没有专有的绘图表面的,而是与专宿主窗口共享同一个绘图表面,因此,它 们就不会像SurfaceView一样,有一个专门的类型为Surface的成员变量来描述自己的绘图表面。

在前面Android应用程序窗口(Activity)与WindowManagerService服务的连接过程分析一 文提到,每一个Activity窗口都关联有一个W对象。这个W对象是一个实现了IWindow接口的Binder本地对象,它是用来传递给 WindowManagerService服务的,以便WindowManagerService服务可以通过它来和它所关联的Activity窗口通 信。例如,WindowManagerService服务通过这个W对象来通知它所关联的Activity窗口的大小或者可见性发生变化了。同时,这个W 对象还用来在WindowManagerService服务这一侧唯一地标志一个窗口,也就是说,WindowManagerService服务会为这个 W对象创建一个WindowState对象。

SurfaceView类的成员变量mWindow指向的是一个MyWindow对象。MyWindow类是从BaseIWindow类继承下来的, 后者与W类一样,实现了IWindow接口。也就是说,每一个SurfaceView都关联有一个实现了IWindow接口的Binder本地对象,就如 第一个Activity窗口都关联有一个实现了IWindow接口的W对象一样。从这里我们就可以推断出,每一个SurfaceView在 WindowManagerService服务这一侧都对应有一个WindowState对象。从这一点来看,WindowManagerService 服务认为Activity窗口和SurfaceView的地位是一样的,即认为它们都是一个窗口,并且具有绘图表面。接下来我们就会通过 SurfaceView类的成员函数updateWindow的实现来证实这个推断。

SurfaceView类的成员变量mWindowType描述的是SurfaceView的窗口类型,它的默认值等于 TYPE_APPLICATION_MEDIA。也就是说,我们在创建一个SurfaceView的时候,默认是用来显示多媒体的,例如,用来显示视频。 SurfaceView还有另外一个窗口类型TYPE_APPLICATION_MEDIA_OVERLAY,它是用来在视频上面显示一个Overlay 的,这个Overlay可以用来显示视字幕等信息。

我们假设一个Activity窗口嵌入有两个SurfaceView,其中一个SurfaceView的窗口类型为 TYPE_APPLICATION_MEDIA,另外一个SurfaceView的窗口类型为 TYPE_APPLICATION_MEDIA_OVERLAY,那么在WindowManagerService服务这一侧就会对应有三个 WindowState对象,其中,用来描述SurfaceView的WindowState对象是附加在用来描述Activity窗口的 WindowState对象上的。从前面Android窗口管理服务WindowManagerService计算窗口Z轴位置的过程分析一 文可以知道,如果一个WindowState对象所描述的窗口的类型为TYPE_APPLICATION_MEDIA或者 TYPE_APPLICATION_MEDIA_OVERLAY,那么它就会位于它所附加在的窗口的下面。也就是说,类型为 TYPE_APPLICATION_MEDIA或者TYPE_APPLICATION_MEDIA_OVERLAY的窗口的Z轴位置是小于它所附加在的窗 口的Z轴位置的。同时,如果一个窗口同时附加有类型为TYPE_APPLICATION_MEDIA和 TYPE_APPLICATION_MEDIA_OVERLAY的两个窗口,那么类型为TYPE_APPLICATION_MEDIA_OVERLAY的 窗口的Z轴大于类型为TYPE_APPLICATION_MEDIA的窗口的Z轴位置。

从上面的描述就可以得出一个结论:如果一个Activity窗口嵌入有两个类型分别为TYPE_APPLICATION_MEDIA和 TYPE_APPLICATION_MEDIA_OVERLAY的SurfaceView,那么该Activity窗口的Z轴位置大于类型为 TYPE_APPLICATION_MEDIA_OVERLAY的SurfaceView的Z轴位置,而类型为 TYPE_APPLICATION_MEDIA_OVERLAY的SurfaceView的Z轴位置又大于类型为 TYPE_APPLICATION_MEDIA的窗口的Z轴位置。

注意,我们在创建了一个SurfaceView之后,可以调用它的成员函数setZOrderMediaOverlay、setZOrderOnTop或 者setWindowType来修改该SurfaceView的窗口类型,也就是修改该SurfaceView的成员变量mWindowType的值。

SurfaceView类的成员变量mRequestedType描述的是SurfaceView的绘图表面的类型,一般来说,它的值可能等于SURFACE_TYPE_NORMAL,也可能等于SURFACE_TYPE_PUSH_BUFFERS。

当一个SurfaceView的绘图表面的类型等于SURFACE_TYPE_NORMAL的时候,就表示该SurfaceView的绘图表面所使用的内 存是一块普通的内存。一般来说,这块内存是由SurfaceFlinger服务来分配的,我们可以在应用程序内部自由地访问它,即可以在它上面填充任意的 UI数据,然后交给SurfaceFlinger服务来合成,并且显示在屏幕上。在这种情况下,SurfaceFlinger服务使用一个Layer对象 来描述该SurfaceView的绘图表面。

当一个SurfaceView的绘图表面的类型等于SURFACE_TYPE_PUSH_BUFFERS的时候,就表示该SurfaceView的绘 图表面所使用的内存不是由SurfaceFlinger服务分配的,因而我们不能够在应用程序内部对它进行操作。例如,当一个SurfaceView是用 来显示摄像头预览或者视频播放的时候,我们就会将它的绘图表面的类型设置为SURFACE_TYPE_PUSH_BUFFERS,这样摄像头服务或者视频 播放服务就会为该SurfaceView绘图表面创建一块内存,并且将采集的预览图像数据或者视频帧数据源源不断地填充到该内存中去。注意,这块内存有可 能是来自专用的硬件的,例如,它可能是来自视频卡的。在这种情况下,SurfaceFlinger服务使用一个LayerBuffer对象来描述该 SurfaceView的绘图表面。

从上面的描述就得到一个重要的结论:绘图表面类型为SURFACE_TYPE_PUSH_BUFFERS的SurfaceView的UI是不能由应用程序 来控制的,而是由专门的服务来控制的,例如,摄像头服务或者视频播放服务,同时,SurfaceFlinger服务会使用一种特殊的 LayerBuffer来描述这种绘图表面。使用LayerBuffer来描述的绘图表面在进行渲染的时候,可以使用硬件加速,例如,使用copybit 或者overlay来加快渲染速度,从而可以获得更流畅的摄像头预览或者视频播放。

注意,我们在创建了一个SurfaceView之后,可以调用它的成员函数getHolder获得一个SurfaceHolder对象,然后再调用该 SurfaceHolder对象的成员函数setType来修改该SurfaceView的绘图表面的类型,即修改该SurfaceView的成员变量 mRequestedType的值。

介绍完成SurfaceView类的成员变量mSurface、mWindow、mWindowType和mRequestedType的含义之后,我们再介绍其它几个接下来要用到的其它成员变量的含义:

–mHaveFrame,用来描述SurfaceView的宿主窗口的大小是否已经计算好了。只有当宿主窗口的大小计算之后,SurfaceView才可以更新自己的窗口。

–mRequestedWidth,用来描述SurfaceView最后一次被请求的宽度。

–mRequestedHeight,用来描述SurfaceView最后一次被请求的高度。

–mRequestedFormat,用来描述SurfaceView最后一次被请求的绘图表面的像素格式。

–mNewSurfaceNeeded,用来描述SurfaceView是否需要新创建一个绘图表面。

–mLeft、mTop、mWidth、mHeight,用来描述SurfaceView上一次所在的位置以及大小。

–mFormat,用来描述SurfaceView的绘图表面上一次所设置的格式。

–mVisible,用来描述SurfaceView上一次被设置的可见性。

–mType,用来描述SurfaceView的绘图表面上一次所设置的类型。

–mUpdateWindowNeeded,用来描述SurfaceView是否被WindowManagerService服务通知执行一次UI更新操作。

–mReportDrawNeeded,用来描述SurfaceView是否被WindowManagerService服务通知执行一次UI绘制操作。

–mLayout,指向的是一个WindowManager.LayoutParams对象,用来传递SurfaceView的布局参数以及属性值给WindowManagerService服务,以便WindowManagerService服务可以正确地维护它的状态。

理解了上述成员变量的含义的之后,接下来我们就可以分析SurfaceView类的成员函数updateWindow创建绘图表面的过程了,如下所示:

(1). 判断成员变量mHaveFrame的值是否等于false。如果是的话,那么就说明现在还不是时候为SurfaceView创建绘图表面面,因为它的宿主窗口还没有准备就绪。

(2). 获得SurfaceView当前要使用的宽度和高度,并且保存在变量myWidth和myHeight中。注意,如果SurfaceView没有被请求设 置宽度或者高度,那么就通过调用父类View的成员函数getWidth和getHeight来获得它默认所使用的宽度和高度。

(3). 调用父类View的成员函数getLocationInWindow来获得SurfaceView的左上角位置,并且保存在成员变量mLocation所描述的一个数组中。

(4). 判断以下条件之一是否成立:

–SurfaceView的绘图表面是否还未创建,即成员变量mWindow的值是否等于null;

–SurfaceView的绘图表面的像素格式是否发生了变化,即成员变量mFormat和mRequestedFormat的值是否不相等;

–SurfaceView的大小是否发生了变化,即变量myWidth和myHeight是否与成员变量mWidth和mHeight的值不相等;

–SurfaceView的可见性是否发生了变化,即成员变量mVisible和mRequestedVisible的值是否不相等,或者成员变量NewSurfaceNeeded的值是否等于true;

–SurfaceView的绘图表面的类型是否发生了变化,即成员变量mType和mRequestedType的值是否不相等;

–SurfaceView的位置是否发生了变化,即成员变量mLeft和mTop的值是否不等于前面计算得到的mLocation[0]和mLocation[1]的值;

–SurfaceView是否被WindowManagerService服务通知执行一次UI更新操作,即成员变量mUpdateWindowNeeded的值是否等于true;

–SurfaceView是否被WindowManagerService服务通知执行一次UI绘制操作,即成员变量mReportDrawNeeded的值是否等于true;

–SurfaceView类的成员函数updateWindow是否被调用者强制要求刷新或者绘制SurfaceView,即参数force或者redrawNeeded的值是否等于true。

只要上述条件之一成立,那么SurfaceView类的成员函数updateWindow就需要对SurfaceView的各种信息进行更新,即执行以下第5步至第7步操作。

(5). 将SurfaceView接下来要设置的可见性、位置、大小、绘图表面像素格式和类型分别记录在成员变量mVisible、mLeft、mTop、 mWidth、mHeight、mFormat和mType,同时还会将这些信息整合到成员变量mLayout所指向的一个 WindowManager.LayoutParams对象中去,以便接下来可以传递给WindowManagerService服务。

(6). 检查成员变量mWindow的值是否等于null。如果等于null的话,那么就说明该SurfaceView还没有增加到 WindowManagerService服务中去。在这种情况下,就会创建一个MyWindow对象保存在该成员变量中,并且调用成员变量 mSession所描述的一个Binder代理对象的成员函数addWithoutInputChannel来将该MyWindow对象传递给 WindowManagerService服务。在前面的Step 4中提到,SurfaceView类的成员变量mSession指向的是一个实现了IWindowSession接口的Binder代理对象,该 Binder代理对象引用的是运行在WindowManagerService服务这一侧的一个Session对象。Session类的成员函数 addWithoutInputChannel与另外一个成员函数add的实现是类似的,它们都是用来在WindowManagerService服务内 部为指定的窗口增加一个WindowState对象,具体可以参考前面Android应用程序窗口(Activity)与WindowManagerService服务的连接过程分析一 文。不过,Session类的成员函数addWithoutInputChannel只是在WindowManagerService服务内部为指定的窗 口增加一个WindowState对象,而Session类的成员函数add除了会在WindowManagerService服务内部为指定的窗口增加 一个WindowState对象之外,还会为该窗口创建一个用来接收用户输入的通道,具体可以参考Android应用程序键盘(Keyboard)消息处理机制分析一文。

(7). 调用成员变量mSession所描述的一个Binder代理对象的成员函数relayout来请求WindowManagerService服务对SurfaceView的UI进行布局。从前面Android应用程序窗口(Activity)的绘图表面(Surface)的创建过程分析一 文可以知道,WindowManagerService服务在对一个窗口进行布局的时候,如果发现该窗口的绘制表面还未创建,或者需要需要重新创建,那么 就会为请求SurfaceFlinger服务为该窗口创建一个新的绘图表面,并且将该绘图表面返回来给调用者。在我们这个情景 中,WindowManagerService服务返回来的绘图表面就会保存在成员变量mSurface。注意,这一步由于可能会修改 SurfaceView的绘图表面,即修改成员变量mSurface的指向的一个Surface对象的内容,因此,就需要在获得成员变量 mSurfaceLock所描述的一个锁的情况下执行,避免其它线程同时修改该绘图表面的内容,这是因为我们可能会使用一个独立的线程来来绘制 SurfaceView的UI。

执行完成上述步骤之后,SurfaceView的绘图表面的创建操作就执行完成了,而当SurfaceView有了绘图表面之后,我们就可以使用独立的 线程来绘制它的UI了,不过,在绘制之前,我们还需要在SurfaceView的宿主窗口上挖一个洞,以便绘制出来的UI不会被挡住。

2. SurfaceView的挖洞过程

SurfaceView的窗口类型一般都是TYPE_APPLICATION_MEDIA或者 TYPE_APPLICATION_MEDIA_OVERLAY,也就是说,它的Z轴位置是小于其宿主窗口的Z位置的。为了保证SurfaceView的 UI是可见的,SurfaceView就需要在其宿主窗口的上面挖一个洞出来,实际上就是在其宿主窗口的绘图表面上设置一块透明区域,以便可以将自己显示 出来。

从SurfaceView的绘图表面的创建过程可以知道,SurfaceView在被附加到宿主窗口之上的时候,会请求在宿主窗口上设置透明区域,而每当 其宿主窗口刷新自己的UI的时候,就会将所有嵌入在它里面的SurfaceView所设置的透明区域收集起来,然后再通知 WindowManagerService服务为其设置一个总的透明区域。

从SurfaceView的绘图表面的创建过程可以知道,SurfaceView在被附加到宿主窗口之上的时候,SurfaceView类的成员函数 onAttachedToWindow就会被调用。SurfaceView类的成员函数onAttachedToWindow在被调用的期间,就会请求在 宿主窗口上设置透明区域。接下来,我们就从SurfaceView类的成员函数onAttachedToWindow开始,分析SurfaceView的 挖洞过程,如图3所示:

图3 SurfaceView的挖洞过程

         这个过程可以分为6个步骤,接下来我们就详细分析每一个步骤。

Step 1. SurfaceView.onAttachedToWindow

  1. public class SurfaceView extends View {  
  2.     ……  
  3.   
  4.     @Override  
  5.     protected void onAttachedToWindow() {  
  6.         super.onAttachedToWindow();  
  7.         mParent.requestTransparentRegion(this);  
  8.         ……  
  9.     }  
  10.   
  11.     ……  
  12. }  

这个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。

SurfaceView类的成员变量mParent是从父类View继承下来的,用来描述当前正在处理的SurfaceView的父视图。我们假设当 前正在处理的SurfaceView的父视图就为其宿主窗口的顶层视图,因此,接下来SurfaceView类的成员函数 onAttachedToWindow就会调用DecorView类的成员函数requestTransparentRegion来请求在宿主窗口之上挖 一个洞。

DecorView类的成员函数requestTransparentRegion是从父类ViewGroup继承下来的,因此,接下来我们就继续分析ViewGroup类的成员函数requestTransparentRegion的实现。

Step 2. ViewGroup.requestTransparentRegion

  1. public abstract class ViewGroup extends View implements ViewParent, ViewManager {  
  2.     ……  
  3.   
  4.     public void requestTransparentRegion(View child) {  
  5.         if (child != null) {  
  6.             child.mPrivateFlags |= View.REQUEST_TRANSPARENT_REGIONS;  
  7.             if (mParent != null) {  
  8.                 mParent.requestTransparentRegion(this);  
  9.             }  
  10.         }  
  11.     }  
  12.   
  13.     ……  
  14. }  

这个函数定义在文件frameworks/base/core/java/android/view/ViewGroup.java中。

参数child描述的便是要在宿主窗口设置透明区域的SurfaceView,ViewGroup类的成员函数 requestTransparentRegion首先将它的成员变量mPrivateFlags的值的 View.REQUEST_TRANSPARENT_REGIONS位设置为1,表示它要在宿主窗口上设置透明区域,接着再调用从父类View继承下来的 成员变量mParent所指向的一个视图容器的成员函数requestTransparentRegion来继续向上请求设置透明区域,这个过程会一直持 续到当前正在处理的视图容器为窗口的顶层视图为止。

前面我们已经假设了参数child所描述的SurfaceView是直接嵌入在宿主窗口的顶层视图中的,而窗口的顶层视图的父视图是使用一个 ViewRoot对象来描述的,也就是说,当前正在处理的视图容器的成员变量mParent指向的是一个ViewRoot对象,因此,接下来我们就继续分 析ViewRoot类的成员函数requestTransparentRegion的实现,以便可以继续了解SurfaceView的挖洞过程。

Step 3. ViewRoot.requestTransparentRegion

  1. public final class ViewRoot extends Handler implements ViewParent,  
  2.         View.AttachInfo.Callbacks {  
  3.     ……  
  4.   
  5.     public void requestTransparentRegion(View child) {  
  6.         // the test below should not fail unless someone is messing with us  
  7.         checkThread();  
  8.         if (mView == child) {  
  9.             mView.mPrivateFlags |= View.REQUEST_TRANSPARENT_REGIONS;  
  10.             // Need to make sure we re-evaluate the window attributes next  
  11.             // time around, to ensure the window has the correct format.  
  12.             mWindowAttributesChanged = true;  
  13.             requestLayout();  
  14.         }  
  15.     }  
  16.   
  17.     ……  
  18. }  

这个函数定义在文件frameworks/base/core/java/android/view/ViewRoot.java中。

ViewRoot类的成员函数requestTransparentRegion首先调用另外一个成员函数checkThread来检查当前执行的线 程是否是应用程序的主线程,如果不是的话,那么就会抛出一个类型为CalledFromWrongThreadException的异常。

通过了上面的检查之后,ViewRoot类的成员函数requestTransparentRegion再检查参数child所描述的视图是否就是当前正 在处理的ViewRoot对象所关联的窗口的顶层视图,即检查它与ViewRoot类的成员变量mView是否是指向同一个View对象。由于一个 ViewRoot对象有且仅有一个子视图,因此,如果上述检查不通过的话,那么就说明调用者正在非法调用ViewRoot类的成员函数 requestTransparentRegion来设置透明区域。

通过了上述两个检查之后,ViewRoot类的成员函数requestTransparentRegion就将成员变量mView所描述的一个窗口的顶层 视图的成员变量mPrivateFlags的值的View.REQUEST_TRANSPARENT_REGIONS位设置为1,表示该窗口被设置了一块 透明区域。

当一个窗口被请求设置了一块透明区域之后,它的窗口属性就发生变化了,因此,这时候除了要将与它所关联的一个ViewRoot对象的成员变量 mWindowAttributesChanged的值设置为true之外,还要调用该ViewRoot对象的成员函数requestLayout来请求 刷新一下窗口的UI,即请求对窗口的UI进行重新布局和绘制。

从前面Android应用程序窗口(Activity)的绘图表面(Surface)的创建过程分析一 文可以知道,ViewRoot类的成员函数requestLayout最终会调用到另外一个成员函数performTraversals来实际执行刷新窗 口UI的操作。ViewRoot类的成员函数performTraversals在刷新窗口UI的过程中,就会将嵌入在它里面的SurfaceView所 要设置的透明区域收集起来,以便可以请求WindowManagerService将这块透明区域设置到它的绘图表面上去。

接下来,我们就继续分析ViewRoot类的成员函数performTraversals的实现,以便可以继续了解SurfaceView的挖洞过程。

Step 4. ViewRoot.performTraversals

  1. public final class ViewRoot extends Handler implements ViewParent,  
  2.         View.AttachInfo.Callbacks {  
  3.     ……  
  4.   
  5.     private void performTraversals() {  
  6.         ……  
  7.   
  8.         // cache mView since it is used so much below…  
  9.         final View host = mView;  
  10.         ……  
  11.   
  12.         final boolean didLayout = mLayoutRequested;  
  13.         ……  
  14.   
  15.         if (didLayout) {  
  16.             ……  
  17.   
  18.             host.layout(00, host.mMeasuredWidth, host.mMeasuredHeight);  
  19.   
  20.             ……  
  21.   
  22.             if ((host.mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) != 0) {  
  23.                 // start out transparent  
  24.                 // TODO: AVOID THAT CALL BY CACHING THE RESULT?  
  25.                 host.getLocationInWindow(mTmpLocation);  
  26.                 mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],  
  27.                         mTmpLocation[0] + host.mRight – host.mLeft,  
  28.                         mTmpLocation[1] + host.mBottom – host.mTop);  
  29.   
  30.                 host.gatherTransparentRegion(mTransparentRegion);  
  31.                 ……  
  32.   
  33.                 if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {  
  34.                     mPreviousTransparentRegion.set(mTransparentRegion);  
  35.                     // reconfigure window manager  
  36.                     try {  
  37.                         sWindowSession.setTransparentRegion(mWindow, mTransparentRegion);  
  38.                     } catch (RemoteException e) {  
  39.                     }  
  40.                 }  
  41.             }  
  42.   
  43.             ……  
  44.         }  
  45.    
  46.         ……  
  47.   
  48.         boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw();  
  49.   
  50.         if (!cancelDraw && !newSurface) {  
  51.             ……  
  52.   
  53.             draw(fullRedrawNeeded);  
  54.   
  55.             ……  
  56.         }   
  57.   
  58.         ……  
  59.     }  
  60.   
  61.     ……  
  62. }  

这个函数定义在文件frameworks/base/core/java/android/view/ViewRoot.java中。

ViewRoot类的成员函数performTraversals的具体实现可以参考前面Android应用程序窗口(Activity)实现框架简要介绍和学习计划这个系列的文章以及Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析一文,这里我们只关注窗口收集透明区域的逻辑。

ViewRoot类的成员函数performTraversals是在窗口的UI布局完成之后,并且在窗口的UI绘制之前,收集嵌入在它里面的 SurfaceView所设置的透明区域的,这是因为窗口的UI布局完成之后,各个子视图的大小和位置才能确定下来,这样SurfaceView才知道自 己要设置的透明区域的位置和大小。

变量host与ViewRoot类的成员变量mView指向的是同一个DecorView对象,这个DecorView对象描述的便是当前正在处理的窗口 的顶层视图。从前面的Step 3可以知道,如果当前正在处理的窗口的顶层视图内嵌有SurfaceView,那么用来描述它的一个DecorView对象的成员变量 mPrivateFlags的值的View.REQUEST_TRANSPARENT_REGIONS位就会等于1。在这种情况下,ViewRoot类的 成员函数performTraversals就知道需要在当前正在处理的窗口的上面设置一块透明区域了。这块透明区域的收集过程如下所示:

(1). 计算顶层视图的位置和大小,即计算顶层视图所占据的区域。

(2). 将顶层视图所占据的区域作为窗口的初始化透明区域,保存在ViewRoot类的成员变量mTransparentRegion中。

(3). 从顶层视图开始,从上到下收集每一个子视图所要设置的区域,最终收集到的总透明区域也是保存在ViewRoot类的成员变量mTransparentRegion中。

(4). 检查ViewRoot类的成员变量mTransparentRegion和mPreviousTransparentRegion所描述的区域是否相等。 如果不相等的话,那么就说明窗口的透明区域发生了变化,这时候就需要调用ViewRoot类的的静态成员变量sWindowSession所描述的一个 Binder代理对象的成员函数setTransparentRegion通知WindowManagerService为窗口设置由成员变量 mTransparentRegion所指定的透明区域。

其中,第(3)步是通过调用变量host所描述的一个DecorView对象的成员函数gatherTransparentRegion来实现的。 DecorView类的成员函数gatherTransparentRegion是从父类ViewGroup继承下来的,因此,接下来我们就继续分析 ViewGroup类的成员函数gatherTransparentRegion的实现,以便可以了解SurfaceView的挖洞过程。

Step 5. ViewGroup.gatherTransparentRegion

  1. public abstract class ViewGroup extends View implements ViewParent, ViewManager {  
  2.     ……  
  3.   
  4.     @Override  
  5.     public boolean gatherTransparentRegion(Region region) {  
  6.         // If no transparent regions requested, we are always opaque.  
  7.         final boolean meOpaque = (mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) == 0;  
  8.         if (meOpaque && region == null) {  
  9.             // The caller doesn’t care about the region, so stop now.  
  10.             return true;  
  11.         }  
  12.         super.gatherTransparentRegion(region);  
  13.         final View[] children = mChildren;  
  14.         final int count = mChildrenCount;  
  15.         boolean noneOfTheChildrenAreTransparent = true;  
  16.         for (int i = 0; i < count; i++) {  
  17.             final View child = children[i];  
  18.             if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {  
  19.                 if (!child.gatherTransparentRegion(region)) {  
  20.                     noneOfTheChildrenAreTransparent = false;  
  21.                 }  
  22.             }  
  23.         }  
  24.         return meOpaque || noneOfTheChildrenAreTransparent;  
  25.     }  
  26.   
  27.     ……  
  28. }  

这个函数定义在文件frameworks/base/core/java/android/view/ViewGroup.java中。

ViewGroup类的成员函数gatherTransparentRegion首先是检查当前正在处理的视图容器是否被请求设置透明区域,即检查成 员变量mPrivateFlags的值的 View.REQUEST_TRANSPARENT_REGIONS位是否等于1。如果不等于1,那么就说明不用往下继续收集窗口的透明区域了,因为在这 种情况下,当前正在处理的视图容器及其子视图都不可能设置有透明区域。另一方面,如果参数region的值等于null,那么就说明调用者不关心当前正在 处理的视图容器的透明区域,而是关心它是透明的,还是不透明的。在上述两种情况下,ViewGroup类的成员函数 gatherTransparentRegion都不用进一步处理了。

假设当前正在处理的视图容器被请求设置有透明区域,并且参数region的值不等于null,那么接下来ViewGroup类的成员函数gatherTransparentRegion就执行以下两个操作:

(1). 调用父类View的成员函数gatherTransparentRegion来检查当前正在处理的视图容器是否需要绘制。如果需要绘制的话,那么就会将它 所占据的区域从参数region所占据的区域移除,这是因为参数region所描述的区域开始的时候是等于窗口的顶层视图的大小的,也就是等于窗口的整个 大小的。

(2). 调用当前正在处理的视图容器的每一个子视图的成员函数gatherTransparentRegion来继续往下收集透明区域。

在接下来的Step 6中,我们再详细分析当前正在处理的视图容器的每一个子视图的透明区域的收集过程,现在我们主要分析View类的成员函数gatherTransparentRegion的实现,如下所示:

  1. public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {  
  2.     ……  
  3.   
  4.     public boolean gatherTransparentRegion(Region region) {  
  5.         final AttachInfo attachInfo = mAttachInfo;  
  6.         if (region != null && attachInfo != null) {  
  7.             final int pflags = mPrivateFlags;  
  8.             if ((pflags & SKIP_DRAW) == 0) {  
  9.                 // The SKIP_DRAW flag IS NOT set, so this view draws. We need to  
  10.                 // remove it from the transparent region.  
  11.                 final int[] location = attachInfo.mTransparentLocation;  
  12.                 getLocationInWindow(location);  
  13.                 region.op(location[0], location[1], location[0] + mRight – mLeft,  
  14.                         location[1] + mBottom – mTop, Region.Op.DIFFERENCE);  
  15.             } else if ((pflags & ONLY_DRAWS_BACKGROUND) != 0 && mBGDrawable != null) {  
  16.                 // The ONLY_DRAWS_BACKGROUND flag IS set and the background drawable  
  17.                 // exists, so we remove the background drawable’s non-transparent  
  18.                 // parts from this transparent region.  
  19.                 applyDrawableToTransparentRegion(mBGDrawable, region);  
  20.             }  
  21.         }  
  22.         return true;  
  23.     }  
  24.   
  25.     ……  
  26. }  

这个函数定义在文件frameworks/base/core/java/android/view/View.java中。

View类的成员函数gatherTransparentRegion首先是检查当前正在处理的视图的前景是否需要绘制,即检查成员变量 mPrivateFlags的值的SKIP_DRAW位是否等于0。如果等于0的话,那么就说明当前正在处理的视图的前景是需要绘制的。在这种情况 下,View类的成员函数gatherTransparentRegion就会将当前正在处理的视图所占据的区域从参数region所描述的区域中移除, 以便当前正在处理的视图的前景可以显示出来。

另一方面,如果当前正在处理的视图的前景不需要绘制,但是该视图的背景需要绘制,并且该视图是设置有的,即成员变量mPrivateFlags的值的 SKIP_DRAW位不等于0,并且成员变量mBGDrawable的值不等于null,这时候View类的成员函数 gatherTransparentRegion就会调用另外一个成员函数applyDrawableToTransparentRegion来将该背景 中的不透明区域从参数region所描述的区域中移除,以便当前正在处理的视图的背景可以显示出来。

回到ViewGroup类的成员函数gatherTransparentRegion中,当前正在处理的视图容器即为当前正在处理的窗口的顶层视图,前面 我们已经假设它里面嵌入有一个SurfaceView子视图,因此,接下来就会收集该SurfaceView子视图所设置的透明区域,这是通过调用 SurfaceView类的成员函数gatherTransparentRegion来实现的。

接下来,我们就继续分析SurfaceView类的成员函数gatherTransparentRegion的实现,以便可以继续了解SurfaceView的挖洞过程。

Step 6. SurfaceView.gatherTransparentRegion

  1. public class SurfaceView extends View {  
  2.     ……  
  3.   
  4.     @Override  
  5.     public boolean gatherTransparentRegion(Region region) {  
  6.         if (mWindowType == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {  
  7.             return super.gatherTransparentRegion(region);  
  8.         }  
  9.   
  10.         boolean opaque = true;  
  11.         if ((mPrivateFlags & SKIP_DRAW) == 0) {  
  12.             // this view draws, remove it from the transparent region  
  13.             opaque = super.gatherTransparentRegion(region);  
  14.         } else if (region != null) {  
  15.             int w = getWidth();  
  16.             int h = getHeight();  
  17.             if (w>0 && h>0) {  
  18.                 getLocationInWindow(mLocation);  
  19.                 // otherwise, punch a hole in the whole hierarchy  
  20.                 int l = mLocation[0];  
  21.                 int t = mLocation[1];  
  22.                 region.op(l, t, l+w, t+h, Region.Op.UNION);  
  23.             }  
  24.         }  
  25.         if (PixelFormat.formatHasAlpha(mRequestedFormat)) {  
  26.             opaque = false;  
  27.         }  
  28.         return opaque;  
  29.     }  
  30.   
  31.     ……  
  32. }  

这个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。

SurfaceVie类的成员函数gatherTransparentRegion首先是检查当前正在处理的SurfaceView是否是用作窗口面 板的,即它的成员变量mWindowType的值是否等于 WindowManager.LayoutParams.TYPE_APPLICATION_PANEL。如果等于的话,那么就会调用父类View的成员 函数gatherTransparentRegion来检查该面板是否需要绘制。如果需要绘制,那么就会将它所占据的区域从参数region所描述的区域 移除。

假设当前正在处理的SurfaceView不是用作窗口面板的,那么SurfaceVie类的成员函数gatherTransparentRegion接 下来就会直接检查当前正在处理的SurfaceView是否是需要在宿主窗口的绘图表面上进行绘制,即检查成员变量mPrivateFlags的值的 SKIP_DRAW位是否等于1。如果需要的话,那么也会调用父类View的成员函数gatherTransparentRegion来将它所占据的区域 从参数region所描述的区域移除。

假设当前正在处理的SurfaceView不是用作窗口面板,并且也是不需要在宿主窗口的绘图表面上进行绘制的,而参数region的值又不等于 null,那么SurfaceVie类的成员函数gatherTransparentRegion就会先计算好当前正在处理的SurfaceView所占 据的区域,然后再将该区域添加到参数region所描述的区域中去,这样就可以得到窗口的一个新的透明区域。

最后,SurfaceVie类的成员函数gatherTransparentRegion判断当前正在处理的SurfaceView的绘图表面的像素格式 是否设置有透明值。如果有的话,那么就会将变量opaque的值设置为false,否则的话,变量opaque的值就保持为true。变量opaque的 值最终会返回给调用者,这样调用者就可以知道当前正在处理的SurfaceView的绘图表面是否是半透明的了。

至此,我们就分析完成SurfaceView的挖洞过程了,接下来我们继续分析SurfaceView的绘制过程。

3. SurfaceView的绘制过程

SurfaceView虽然具有独立的绘图表面,不过它仍然是宿主窗口的视图结构中的一个结点,因此,它仍然是可以参与到宿主窗口的绘制流程中去的。从前面Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析一文可以知道,窗口在绘制的过程中,每一个子视图的成员函数draw或者dispatchDraw都会被调用到,以便它们可以绘制自己的UI。

SurfaceView类的成员函数draw和dispatchDraw的实现如下所示:

  1. public class SurfaceView extends View {  
  2.     ……  
  3.   
  4.     @Override  
  5.     public void draw(Canvas canvas) {  
  6.         if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {  
  7.             // draw() is not called when SKIP_DRAW is set  
  8.             if ((mPrivateFlags & SKIP_DRAW) == 0) {  
  9.                 // punch a whole in the view-hierarchy below us  
  10.                 canvas.drawColor(0, PorterDuff.Mode.CLEAR);  
  11.             }  
  12.         }  
  13.         super.draw(canvas);  
  14.     }  
  15.   
  16.     @Override  
  17.     protected void dispatchDraw(Canvas canvas) {  
  18.         if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {  
  19.             // if SKIP_DRAW is cleared, draw() has already punched a hole  
  20.             if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {  
  21.                 // punch a whole in the view-hierarchy below us  
  22.                 canvas.drawColor(0, PorterDuff.Mode.CLEAR);  
  23.             }  
  24.         }  
  25.         // reposition ourselves where the surface is   
  26.         mHaveFrame = true;  
  27.         updateWindow(falsefalse);  
  28.         super.dispatchDraw(canvas);  
  29.     }  
  30.   
  31.     ……  
  32. }  

这两个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。

SurfaceView类的成员函数draw和dispatchDraw的参数canvas所描述的都是建立在宿主窗口的绘图表面上的画布,因此,在这块画布上绘制的任何UI都是出现在宿主窗口的绘图表面上的。

本来SurfaceView类的成员函数draw是用来将自己的UI绘制在宿主窗口的绘图表面上的,但是这里我们可以看到,如果当前正在处理的 SurfaceView不是用作宿主窗口面板的时候,即其成员变量mWindowType的值不等于 WindowManager.LayoutParams.TYPE_APPLICATION_PANEL的时候,SurfaceView类的成员函数 draw只是简单地将它所占据的区域绘制为黑色。

本来SurfaceView类的成员函数dispatchDraw是用来绘制SurfaceView的子视图的,但是这里我们同样看到,如果当前正在处理 的SurfaceView不是用作宿主窗口面板的时候,那么SurfaceView类的成员函数dispatchDraw只是简单地将它所占据的区域绘制 为黑色,同时,它还会通过调用另外一个成员函数updateWindow更新自己的UI,实际上就是请求WindowManagerService服务对 自己的UI进行布局,以及创建绘图表面,具体可以参考前面第1部分的内容。

从SurfaceView类的成员函数draw和dispatchDraw的实现就可以看出,SurfaceView在其宿主窗口的绘图表面上面所做的操 作就是将自己所占据的区域绘为黑色,除此之外,就没有其它更多的操作了,这是因为SurfaceView的UI是要展现在它自己的绘图表面上面的。接下来 我们就分析如何在SurfaceView的绘图表面上面进行UI绘制。

从前面Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析一文可以知道,如果要在一个绘图表面进行UI绘制,那么就顺序执行以下的操作:

(1). 在绘图表面的基础上建立一块画布,即获得一个Canvas对象。

(2). 利用Canvas类提供的绘图接口在前面获得的画布上绘制任意的UI。

(3). 将已经填充好了UI数据的画布缓冲区提交给SurfaceFlinger服务,以便SurfaceFlinger服务可以它合成到屏幕上去。

SurfaceView提供了一个SurfaceHolder接口,通过这个SurfaceHolder接口就可以执行上述的第(1)和引(3)个操作,示例代码如下所示:

  1. SurfaceView sv = (SurfaceView )findViewById(R.id.surface_view);  
  2.   
  3. SurfaceHolder sh = sv.getHolder();  
  4.   
  5. Cavas canvas = sh.lockCanvas()  
  6.   
  7. //Draw something on canvas  
  8. ……  
  9.   
  10. sh.unlockCanvasAndPost(canvas);  

注意,只有在一个SurfaceView的绘图表面的类型不是SURFACE_TYPE_PUSH_BUFFERS的时候,我们才可以自由地在上面绘制 UI。我们使用SurfaceView来显示摄像头预览或者播放视频时,一般就是会将它的绘图表面的类型设置为 SURFACE_TYPE_PUSH_BUFFERS。在这种情况下,SurfaceView的绘图表面所使用的图形缓冲区是完全由摄像头服务或者视频播 放服务来提供的,因此,我们就不可以随意地去访问该图形缓冲区,而是要由摄像头服务或者视频播放服务来访问,因为该图形缓冲区有可能是在专门的硬件里面分 配的。

另外还有一个地方需要注意的是,上述代码既可以在应用程序的主线程中执行,也可以是在一个独立的线程中执行。如果上述代码是在应用程序的主线程中执行,那 么就需要保证它们不会占用过多的时间,否则的话,就会导致应用程序的主线程不能及时地响应用户输入,从而导致ANR问题。

为了方便起见,我们假设一个SurfaceView的绘图表面的类型不是SURFACE_TYPE_PUSH_BUFFERS,接下来,我们就从 SurfaceView的成员函数getHolder开始,分析这个SurfaceView的绘制过程,如下所示:

图4 SurfaceView的绘制过程

       这个过程可以分为5个步骤,接下来我们就详细分析每一个步骤。

Step 1. SurfaceView.getHolder

  1. public class SurfaceView extends View {  
  2.     ……  
  3.   
  4.     public SurfaceHolder getHolder() {  
  5.         return mSurfaceHolder;  
  6.     }  
  7.   
  8.     ……  
  9.   
  10.     private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {  
  11.         ……  
  12.     }  
  13.   
  14.     ……  
  15. }  

这个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。

SurfaceView类的成员函数getHolder的实现很简单,它只是将成员变量mSurfaceHolder所指向的一个SurfaceHolder对象返回给调用者。

Step 2. SurfaceHolder.lockCanvas

  1. public class SurfaceView extends View {  
  2.     ……  
  3.   
  4.     final ReentrantLock mSurfaceLock = new ReentrantLock();  
  5.     final Surface mSurface = new Surface();  
  6.     ……  
  7.   
  8.     private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {  
  9.         ……  
  10.   
  11.         public Canvas lockCanvas() {  
  12.             return internalLockCanvas(null);  
  13.         }  
  14.   
  15.         ……  
  16.   
  17.         private final Canvas internalLockCanvas(Rect dirty) {  
  18.             if (mType == SURFACE_TYPE_PUSH_BUFFERS) {  
  19.                 throw new BadSurfaceTypeException(  
  20.                         “Surface type is SURFACE_TYPE_PUSH_BUFFERS”);  
  21.             }  
  22.             mSurfaceLock.lock();  
  23.             ……  
  24.   
  25.             Canvas c = null;  
  26.             if (!mDrawingStopped && mWindow != null) {  
  27.                 Rect frame = dirty != null ? dirty : mSurfaceFrame;  
  28.                 try {  
  29.                     c = mSurface.lockCanvas(frame);  
  30.                 } catch (Exception e) {  
  31.                     Log.e(LOG_TAG, “Exception locking surface”, e);  
  32.                 }  
  33.             }  
  34.             ……  
  35.   
  36.             if (c != null) {  
  37.                 mLastLockTime = SystemClock.uptimeMillis();  
  38.                 return c;  
  39.             }  
  40.             ……  
  41.   
  42.             mSurfaceLock.unlock();  
  43.   
  44.             return null;    
  45.         }   
  46.   
  47.         ……           
  48.     }  
  49.   
  50.     ……  
  51. }  

这个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。

SurfaceHolder类的成员函数lockCanvas通过调用另外一个成员函数internalLockCanvas来在当前正在处理的SurfaceView的绘图表面上建立一块画布返回给调用者。

SurfaceHolder类的成员函数internalLockCanvas首先是判断当前正在处理的SurfaceView的绘图表面的类型是否 是SURFACE_TYPE_PUSH_BUFFERS,如果是的话,那么就会抛出一个类型为BadSurfaceTypeException的异常,原 因如前面所述。

由于接下来SurfaceHolder类的成员函数internalLockCanvas要在当前正在处理的SurfaceView的绘图表面上建立一块 画布,并且返回给调用者访问,而这块画布不是线程安全的,也就是说它不能同时被多个线程访问,因此,就需要对当前正在处理的SurfaceView的绘图 表面进行锁保护,这是通过它的锁定它的成员变量mSurfaceLock所指向的一个ReentrantLock对象来实现的。

注意,如果当前正在处理的SurfaceView的成员变量mWindow的值等于null,那么就说明它的绘图表面还没有创建好,这时候就无法创建一块 画布返回给调用者。同时,如果当前正在处理的SurfaceView的绘图表面已经创建好,但是该SurfaceView当前是处于停止绘制的状态,即它 的成员变量mDrawingStopped的值等于true,那么也是无法创建一块画布返回给调用者的。

假设当前正在处理的SurfaceView的绘制表面已经创建好,并且它不是处于停止绘制的状态,那么SurfaceHolder类的成员函数 internalLockCanvas就会通过调用该SurfaceView的成员变量mSurface所指向的一个Surface对象的成员函数 lockCanvas来创建一块画布,并且返回给调用者。注意,在这种情况下,当前正在处理的SurfaceView的绘制表面还是处于锁定状态的。

另一方面,如果SurfaceHolder类的成员函数internalLockCanvas不能成功地在当前正在处理的SurfaceView的绘制表 面上创建一块画布,即变量c的值等于null,那么SurfaceHolder类的成员函数internalLockCanvas在返回一个null值调 用者之前,还会将该SurfaceView的绘制表面就会解锁。

从前面第1部分的内容可以知道,SurfaceView类的成员变量mSurface描述的是就是SurfaceView的专有绘图表面,接下来我们就继 续分析它所指向的一个Surface对象的成员函数lockCanvas的实现,以便可以了解SurfaceView的画布的创建过程。

Step 3. Surface.lockCanvas

Surface类的成员函数lockCanvas的具体实现可以参考前面Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析一文,它大致就是通过JNI方法来在当前正在处理的绘图表面上获得一个图形缓冲区,并且将这个图形绘冲区封装在一块类型为Canvas的画布中返回给调用者使用。

调用者获得了一块类型为Canvas的画布之后,就可以调用Canvas类所提供的绘图函数来绘制任意的UI了,例如,调用Canvas类的成员函数drawLine、drawRect和drawCircle可以分别用来画直线、矩形和圆。

调用者在画布上绘制完成所需要的UI之后,就可以将这块画布的图形绘冲区的UI数据提交给SurfaceFlinger服务来处理了,这是通过调用SurfaceHolder类的成员函数unlockCanvasAndPost来实现的。

Step 4. SurfaceHolder.unlockCanvasAndPost

  1. public class SurfaceView extends View {  
  2.     ……  
  3.   
  4.     final ReentrantLock mSurfaceLock = new ReentrantLock();  
  5.     final Surface mSurface = new Surface();  
  6.     ……  
  7.   
  8.     private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {  
  9.         ……  
  10.   
  11.         public void unlockCanvasAndPost(Canvas canvas) {  
  12.             mSurface.unlockCanvasAndPost(canvas);  
  13.             mSurfaceLock.unlock();  
  14.         }   
  15.   
  16.         ……           
  17.     }  
  18.   
  19.     ……  
  20. }  

这个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。

SurfaceHolder类的成员函数unlockCanvasAndPost是通过调用当前正在处理的SurfaceView的成员变量 mSurface所指向的一个Surface对象的成员函数unlockCanvasAndPost来将参数canvas所描述的一块画布的图形缓冲区提 交给SurfaceFlinger服务处理的。

提交完成参数canvas所描述的一块画布的图形缓冲区给SurfaceFlinger服务之后,SurfaceHolder类的成员函数 unlockCanvasAndPost再调用当前正在处理的SurfaceView的成员变量mSurfaceLock所指向的一个 ReentrantLock对象的成员函数unlock来解锁当前正在处理的SurfaceView的绘图表面,因为在前面的Step 2中,我们曾经将该绘图表面锁住了。

接下来,我们就继续分析Surface类的成员函数unlockCanvasAndPost的实现,以便可以了解SurfaceView的绘制过程。

Step 5. Surface.unlockCanvasAndPost

Surface类的成员函数unlockCanvasAndPost的具体实现同样是可以参考前面Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析一文,它大致就是将在前面的Step 3中所获得的一个图形缓冲区提交给SurfaceFlinger服务,以便SurfaceFlinger服务可以在合适的时候将该图形缓冲区合成到屏幕上去显示,这样就可以将对应的SurfaceView的UI展现出来了。

至此,我们就分析完成SurfaceView的绘制过程了,整个SurfaceView的实现原理也就分析完了。总结来说,就是SurfaceView有以下三个特点:

A. 具有独立的绘图表面;

B. 需要在宿主窗口上挖一个洞来显示自己;

C. 它的UI绘制可以在独立的线程中进行,这样就可以进行复杂的UI绘制,并且不会影响应用程序的主线程响应用户输入。

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

[转载]Android 中文 API (100) —— ScrollView - 农民伯伯 - 博客园

mikel阅读(947)

[转载]Android 中文 API (100) —— ScrollView – 农民伯伯 – 博客园.

前言

春节即至,谨代表Android中文翻译组全体同仁祝大家身体健 康,工作顺利!从第一篇译稿2010年8月27发布至今天2011年1月27整5个月,共发布100篇译文,3个合集,在新的一年里,翻译组仍将坚持 Android相关的翻译工作,秉承开源、合作、共享和坚持的信念打持久战,感谢大家的关心和支持!

本章内容是 Android.widget.ScrollView,版本为Android 2.3 r1,翻译来自”pengyouhong”,再次感谢”pengyouhong” !期待你一起参与Android API的翻译,联系我over140@gmail.com。

 

声明

欢迎转载,但请保留文章原始出处:)

博客园:http://www.cnblogs.com/

Android中文翻译组:http://androidbox.sinaapp.com/

 

正文

一、结构

public class ScrollView extends FrameLayout

        

java.lang.Object

android.view.View

         android.view.ViewGroup

                   android.widget.FrameLayout

                            android.widget.ScrollView

 

二、概述

  一种可供用户滚动的层次结构布局容器,允许显示比实际多的内容。ScrollView是一种FrameLayout,意味需要在其上放置有自己滚动内容的子元素。子元素可以是一个复杂的对象的布局管理器。通常用的子元素是垂直方向的LinearLayout,显示在最上层的垂直方向可以让用户滚动的箭头。

  TextView类也有自己的滚动功能,所以不需要使用ScrollView,但是只有两个结合使用,才能保证显示较多内容时候的效率。但只有两者结合使用才可以实现在一个较大的容器中一个文本视图效果。

  ScrollView只支持垂直方向的滚动。

 

三、构造函数

public ScrollView (Context context)

创建一个默认属性的ScrollView实例。

public ScrollView (Context context, AttributeSet attrs)

创建一个带有attrs属性的ScrollView 实例。

public ScrollView (Context context, AttributeSet attrs, int defStyle)

创建一个带有attrs属性,并且指定其默认样式的ScrollView实例。

 

四、公共方法

   public void addView (View child)

         添加子视图。如果事先没有给子视图设置layout参数,会采用当前ViewGroup的默认参数来设置子视图。

                参数

                       child       所添加的子视图

  public void addView (View child, int index)

  添加子视图。如果事先没有给子视图设置layout参数,会采用当前ViewGroup的默认参数来设置子视图。

    参数

   child       所添加的子视图

                index      添加子视图的位置

        

  public void addView (View child, int index, ViewGroup.LayoutParams params)

  根据指定的layout参数添加子视图

  参数

               child       所添加的子视图

               index      添加子视图的位置

               params   为子视图设置的layout参数

 

  public void addView (View child, ViewGroup.LayoutParams params)

  根据指定的layout参数添加子视图。

  参数

               child       所添加的子视图

               params   为子视图设置的layout参数

 

  public boolean arrowScroll (int direction)

  响应点击上下箭头时对滚动条滚动的处理。

                参数

                       direction        按下的箭头所对应的方向

  返回值

  如果我们处理(消耗)了此事件返回true,否则返回false

  public void computeScroll ()

  被父视图调用,用于必要时候对其子视图的值(mScrollX和mScrollY)进行更新。典型的情况如:父视图中某个子视图使用一个Scroller对象来实现滚动操作,会使得此方法被调用。

  public boolean dispatchKeyEvent (KeyEvent event)

         发送一个key事件给当前焦点路径的下一个视图。此焦点路径从视图树的顶层执行直到当前焦点视图。如果此视图为焦点视图,将为自己发送。否则,会为当前焦点路径的下一个节点发送。此方法也会激起一个key监听器。

  参数

                       event     发送的key事件

  返回值

  事件被处理返回true,否则返回false

  public void draw (Canvas canvas)

  手动绘制视图(及其子视图)到指定的画布(Canvas)。这个视图必须在调用这个函数之前做好了整体布局。当实现一个视图时,不需要继承这个方法;相反,你应该实现onDraw(Canvas)方法。

  参数

  canvas    绘制视图的画布

 

  public boolean executeKeyEvent (KeyEvent event)

  当接收到key事件时,用户可以调用此函数来使滚动视图执行滚动,类似于处理由视图体系发送的事件。

  参数

  event     需要执行key的事件

  返回值

  事件被处理返回true,否则返回false

 

  public void fling (int VelocityY)

  滚动视图的滑动(fling)手势。(译者注: 如何监听android的屏幕滑动停止事件

  参数

                       VelocityY      Y方向的初始速率。正值表示手指/光标向屏幕下方滑动,而内容将向上滚动。

 

  public boolean fullScroll (int direction)

  对响应“home/end”短按时响应滚动处理。此方法将视图滚动到顶部或者底部,并且将焦点置于新的可视区域的最顶部/最底部组件。若没有适合的组件做焦点,当前的ScrollView会收回焦点。

  参数

  direction 滚动方向:FOCUS_UP表示视图向上滚动;FOCUS_DOWN表示视图向下滚动

  返回值

  key事件被消耗(consumed)返回true,其他情况返回false

 

  public int getMaxScrollAmount ()

  返回值

  当前滚动视图响应箭头事件能够滚动的最大数。

 

  public boolean isFillViewport ()

  指示当前ScrollView的内容是否被拉伸以填充视图可视范围(译者注:viewport可视范围,参见决定Scrollviewer里面Control的可视范围)。

  返回值

  内容填充视图返回true,否则返回false

 

  public boolean isSmoothScrollingEnabled ()

  返回值

               按箭头方向滚动时,是否显示滚动的平滑效果。

 

  public boolean onInterceptTouchEvent (MotionEvent ev)

  实现此方法是为了拦截所有触摸屏幕时的运动事件。可以像处理发送给子视图的事件一样去监视这些事件,并且获取当前手势在任意点的ownership

  使用此方法时候需要注意,因为它与View.onTouchEvent(MotionEvent)有相当复杂的交互,并且前提需要正确执行View.onTouchEvent(MotionEvent)。事件将按照如下顺序接收到:

  1.    收到down事件

  2.    Down事件或者由视图组的一个子视图处理,或者被用户自己的onTouchEvent()方法处理;此处理意味你应该执行onTouchEvent()时返回true,这样才能继续看到剩下的手势(取代找一个父视图处理)。如果onTouchEvent()返回true时,你不会收到onInterceptTouchEvent()的任何事件并且所有对触摸的处理必须在onTouchEvent()中发生。

  3.    如果此方法返回false,接下来的事件(up to and including the final up)将最先被传递当此,然后是目标的onTouchEvent()

  4.    如果返回true,将不会收到以下任何事件:目标view将收到同样的事件但是会伴随ACTION_CANCEL,并且所有的更进一步的事件将会传递到你自己的onTouchEvent()方法中而不会再在这里出现。

  参数

              ev          体系向下发送的动作事件

  返回值

  如果将运动事件从子视图中截获并且通过onTouchEvent()发送到当前ViewGroup ,返回true。当前目标将会收到ACTION_CANCEL事件,并且不再会有其他消息传递到此。

              (译者注:onInterceptTouchEventonTouchEvent调用时序

 

  public boolean onTouchEvent (MotionEvent ev)

  执行此方法为了处理触摸屏幕的运动事件。

  参数

       ev          运动事件

  返回值

  事件被处理返回true,其它返回false

 

  public boolean pageScroll (int direction)

  响应短按“page up/ down”时候对滚动的处理。此方法将向上或者向下滚动一屏,并且将焦点置于新可视区域的最上/最下。如果没有适合的component作为焦点,当前scrollView将收回焦点。

  参数

       direction        滚动方向:FOCUS_UP表示向上翻一页,FOCUS_DOWN表示向下翻一页。

  返回值

  key事件被消耗(cosumed)返回true,其他返回false

  public void requestChildFocus (View child, View focused)

  当父视图的一个子视图的要获得焦点时,调用此方法。

  参数

       child              要获得焦点的父视图的子视图。此视图包含了焦点视图。如果没有特殊徐要求,此视图实际上就是焦点视图。

                focused        子视图的子孙视图并且此子孙视图是真正的焦点视图

  public boolean requestChildRectangleOnScreen (View child, Rect rectangle, boolean immediate)

  当组里的某个子视图需要被定位在屏幕的某个矩形范围时,调用此方法。重载此方法的ViewGroup可确认以下几点:

      * 子项目将是组里的直系子项

      * 矩形将在子项目的坐标体系中

  重载此方法的ViewGroup应该支持以下几点:

      * 若矩形已经是可见的,则没有东西会改变

      * 为使矩形区域全部可见,视图将可以被滚动显示

  参数

  child        发出请求的子视图

  rectangle    子项目坐标系内的矩形,即此子项目希望在屏幕上的定位

  immediate   设为true,则禁止动画和平滑移动滚动条

        返回值

              进行了滚动操作的这个组(group),是否处理此操作。

  public void requestLayout ()

  当有改变引起当前视图重新布局时,调用此函数。它将规划一个视图树的layout路径。

  public void scrollTo (int x, int y)

  设置当前视图滚动到的位置。此函数会引起对onScrollChanged(int, int, int, int)函数的调用并且会让视图更新。

当前版本取消了在子视图中的滚动。

  参数

       x     滚动到的X位置

                y     滚动到的Y位置

  public void setFillViewport (boolean fillViewport)

  设置当前滚动视图是否将内容高度拉伸以填充视图可视范围(译者注:viewport可视范围,参见决定Scrollviewer里面Control的可视范围)。

  参数

       fillViewport    设置为true表示拉伸内容高度来适应视口边界;其他设为false

  public void setOverScrollMode (int mode)

  为视图设置over-scroll模式。有效的over-scroll模式有OVER_SCROLL_ALWAYS(缺省值),OVER_SCROLL_IF_CONTENT_SCROLLS(只允许当视图内容大过容器时,进行over-scrolling)和OVER_SCROLL_NEVER。只有当视图可以滚动时,此项设置才起作用。

译者注:这个函数是2.3 r1 中新增的,API Level 9关于over-scroll这里译为弹性滚动,即,参见帖子:类似iPhone的弹性ListView滚动

         参数

                   mode       The new over-scroll mode for this view.

  public void setSmoothScrollingEnabled (boolean smoothScrollingEnabled)

  用来设置箭头滚动是否可以引发视图滚动。

  参数

                smoothScrollingEnabled    设置箭头滚动是否可以引起内容的滚动的bool

  public final void smoothScrollBy (int dx, int dy)

  类似于scrollBy(int, int),但是滚动时候是平缓的而不是立即滚动到某处。

  参数

       dx   X方向滚动的像素数

       dy   Y方向滚动的像素数

 

  public final void smoothScrollTo (int x, int y)

  类似于scrollTo(int, int),但是滚动时候是平缓的而不是立即滚动到某处。

  参数

       x     要滚动到位置的X坐标

               y     要滚动到位置的Y坐标

 

五、受保护方法

  protected int computeScrollDeltaToGetChildRectOnScreen (Rect rect)

  计算X方向滚动的总合,以便在屏幕上显示子视图的完整矩形(或者,若矩形宽度超过屏幕宽度,至少要填满第一个屏幕大小)。

  参数

       rect        矩形

  返回值

  滚动差值

  protected int computeVerticalScrollOffset ()

  计算垂直方向滚动条的滑块的偏移。此值用来计算滚动条轨迹的滑块的位置。

  范围可以以任意单位表示,但是必须与computeVerticalScrollRange()computeVerticalScrollExtent()的单位一致。

  缺省的偏移是在当前视图滚动的偏移。

  返回值

  滚动条的滑块垂直方向的偏移。

  protected int computeVerticalScrollRange ()

  滚动视图的可滚动范围是所有子元素的高度。

  返回值

  由垂直方向滚动条代表的所有垂直范围,缺省的范围是当前视图的画图高度。

 

  protected float getBottomFadingEdgeStrength ()

  返回滚动底部的能见度。能见度的值的范围是0.0(没有消失)到1.0(完全消失)之间。缺省的执行返回值为0.0或者1.0,而不是他们中间的某个值。滚动时子类需要重载这个方法来提供一个平缓的渐隐的实现。

  返回值

  滚动底部能见度,值的范围在浮点数0.0f1.0f之间。

 

  protected float getTopFadingEdgeStrength ()

  返回滚动顶部的能见度。能见度的值的范围是0.0(没有消失)到1.0(完全消失)之间。缺省的执行返回值为0.0或者1.0,而不是他们中间的某个值。滚动时子类需要重载这个方法来提供一个平缓的渐隐的实现。

  返回值

  滚动顶部能见度,值的范围在浮点数0.0f1.0f之间。

 

  protected void measureChild (View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)

  要求当前视图的一个子视图测量自己,同时兼顾到当前视图的MeasureSpec的要求和它的空白。子视图必须有MarginLayoutParams。比较复杂的工作是在getChildMeasureSpec中完成的。

  参数

  child              需要自己测量的子视图

  parentWidthMeasureSpec        当前视图要求的宽度

  parentHeightMeasureSpec              当前视图要求的宽度

 

  protected void measureChildWithMargins (View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)

  要求当前视图的一个子视图测量自己,同时兼顾到当前视图的MeasureSpec的要求和它的空白和边界。子视图必须有MarginLayoutParams。比较复杂的工作是在getChildMeasureSpec中完成的。

  参数

  child             需要测量的子视图

  parentWidthMeasureSpec       当前视图要求的宽度

  widthUsed    水平方向上由父视图使用的空白 (也可能是视图的其他子视图使用的)

  parentHeightMeasureSpec      当前视图要求的宽度

  heightUsed 垂直方向上由父视图使用的空白 (也可能是视图的其他子视图使用的)

 

  protected void onLayout (boolean changed, int l, int t, int r, int b)

  当前视图需要为子视图分配大小和位置时候调用,子类继承必须要重载此方法并调用自己子视图的layout函数。

  参数

  changed       当前视图的新的大小或者位置

  l     相对父视图,左边界位置

  t     相对父视图,上边界位置

  r     相对父视图,右边界位置

  b    相对父视图,下边界位置

  protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)

  测量视图以确定其内容宽度和高度。此方法被measure(int, int)调用。需要被子类重写以提供对其内容准确高效的测量。

  约定:当重写此方法时,你必须调用setMeasuredDimension(int, int)来保存当前视图view的宽度和高度。不成功调用此方法将会导致一个IllegalStateException异常,是由measure(int, int)抛出。所以调用父类的onMeasure(int, int)方法是必须的。

  父类的实现是以背景大小为默认大小,除非MeasureSpec(测量细则)允许更大的背景。子类可以重写onMeasure(int,int)以对其内容提供更佳的尺寸。

  如果此方法被重写,那么子类的责任是确认测量高度和测量宽度要大于视图view的最小宽度和最小高度(getSuggestedMinimumHeight() getSuggestedMinimumWidth()),使用这两个方法可以取得最小宽度和最小高度。

  参数

  widthMeasureSpec   受主窗口支配的水平空间要求。这个需求通过 View.MeasureSpec.进行编码。

             heightMeasureSpec  受主窗口支配的垂直空间要求。这个需求通过 View.MeasureSpec.进行编码。

 

protected void onOverScrolled (int scrollX, int scrollY, boolean clampedX, boolean clampedY)

         overScrollBy(int, int, int, int, int, int, int, int, boolean)调用,来对一个over-scroll操作的结果进行响应。(译者注:这个函数是2.3 r1 中新增的,API Level 9

  参数

  scrollX     新的X滚动像素值

  scrollY     新的Y滚动像素值

  clampedX        scrollXover-scroll的边界限制时,值为true

  clampedY        scrollYover-scroll的边界限制时,值为true

  protected boolean onRequestFocusInDescendants (int direction, Rect previouslyFocusedRect)

  当在滚动视图的子视图中查找焦点视图时,需要注意不要将焦点设置在滚动出屏幕外的控件上。此方法会比执行缺省的ViewGroup代价高,否则此行为也会设置为缺省

  参数

               direction 指定下列常量之一:FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT

previouslyFocusedRect 能够给出一个较好的提示的矩形(当前视图的坐标系统)表示焦点从哪里得来。如果没有提示为null

  返回值

  是否取得了焦点

  protected void onSizeChanged (int w, int h, int oldw, int oldh)

布局期间当视图的大小发生改变时调用。如果只是添加到视图,调用时显示的是旧值0。(译者注:也就是添加到视图时,oldwoldh返回的是0)。

  参数

  w     视图当前宽度

  h     视图当前高度

  oldw       视图改变前的宽度

  oldh       视图改变前的高度

 

六、补充

示例代码

下载:Demo_ScrollView.rar

 

七、译者其他译文

android 中文 api (64) —— Scroller

 

八、通知

由于年前事情较多,合集4将推迟到年后发布,时间约为2011年2月9日或10日,请大家谅解 🙂