[转载]Android改进版CoverFlow效果控件 - 热气球 - 博客园

mikel阅读(859)

[转载]Android改进版CoverFlow效果控件 – 热气球 – 博客园.

  最近研究了一下如何在Android上实现CoverFlow效果的控件,其实早在2010年,就有Neil Davies开发并开源出了这个控件,Neil大神的这篇博客地址http://www.inter-fuser.com/2010/02/android-coverflow-widget-v2.html。 首先是阅读源码,弄明白核心思路后,自己重新写了一遍这个控件,并加入了详尽的注释以便日后查阅;而后在使用过程中,发现了有两点可以改进:(1)初始图 片位于中间,左边空了一半空间,比较难看,可以改为重复滚动地展示、(2)由于图片一开始就需要加载出来,所以对内存开销较大,很容易OOM,需要对图片 的内存空间进行压缩。

这个自定义控件包括4个部分,用于创建及提供图片对象的ImageAdapter,计算图片旋转角度等的自定义控件GalleryFlow,压 缩采样率解析Bitmap的工具类BitmapScaleDownUtil,以及承载自定义控件的Gallery3DActivity。

首先是ImageAdapter,代码如下:

复制代码
  1 package pym.test.gallery3d.widget;
  2 
  3 import pym.test.gallery3d.util.BitmapScaleDownUtil;
  4 import android.content.Context;
  5 import android.graphics.Bitmap;
  6 import android.graphics.Bitmap.Config;
  7 import android.graphics.Canvas;
  8 import android.graphics.LinearGradient;
  9 import android.graphics.Matrix;
 10 import android.graphics.Paint;
 11 import android.graphics.PaintFlagsDrawFilter;
 12 import android.graphics.PorterDuff.Mode;
 13 import android.graphics.PorterDuffXfermode;
 14 import android.graphics.Shader.TileMode;
 15 import android.view.View;
 16 import android.view.ViewGroup;
 17 import android.widget.BaseAdapter;
 18 import android.widget.Gallery;
 19 import android.widget.ImageView;
 20 
 21 /**
 22  * @author pengyiming
 23  * @date 2013-9-30
 24  * @function GalleryFlow适配器
 25  */
 26 public class ImageAdapter extends BaseAdapter
 27 {
 28     /* 数据段begin */
 29     private final String TAG = "ImageAdapter";
 30     private Context mContext;
 31     
 32     //图片数组
 33     private int[] mImageIds ;
 34     //图片控件数组
 35     private ImageView[] mImages;
 36     //图片控件LayoutParams
 37     private GalleryFlow.LayoutParams mImagesLayoutParams;
 38     /* 数据段end */
 39 
 40     /* 函数段begin */
 41     public ImageAdapter(Context context, int[] imageIds)
 42     {
 43         mContext = context;
 44         mImageIds = imageIds;
 45         mImages = new ImageView[mImageIds.length];
 46         mImagesLayoutParams = new GalleryFlow.LayoutParams(Gallery.LayoutParams.WRAP_CONTENT, Gallery.LayoutParams.WRAP_CONTENT);
 47     }
 48     
 49     /**
 50      * @function 根据指定宽高创建待绘制的Bitmap,并绘制到ImageView控件上
 51      * @param imageWidth
 52      * @param imageHeight
 53      * @return void
 54      */
 55     public void createImages(int imageWidth, int imageHeight)
 56     {
 57         // 原图与倒影的间距5px
 58         final int gapHeight = 5;
 59         
 60         int index = 0;
 61         for (int imageId : mImageIds)
 62         {
 63             /* step1 采样方式解析原图并生成倒影 */
 64             // 解析原图,生成原图Bitmap对象
 65 //            Bitmap originalImage = BitmapFactory.decodeResource(mContext.getResources(), imageId);
 66             Bitmap originalImage = BitmapScaleDownUtil.decodeSampledBitmapFromResource(mContext.getResources(), imageId, imageWidth, imageHeight);
 67             int width = originalImage.getWidth();
 68             int height = originalImage.getHeight();
 69             
 70             // Y轴方向反向,实质就是X轴翻转
 71             Matrix matrix = new Matrix();
 72             matrix.setScale(1, -1);
 73             // 且仅取原图下半部分创建倒影Bitmap对象
 74             Bitmap reflectionImage = Bitmap.createBitmap(originalImage, 0, height / 2, width, height / 2, matrix, false);
 75             
 76             /* step2 绘制 */
 77             // 创建一个可包含原图+间距+倒影的新图Bitmap对象
 78             Bitmap bitmapWithReflection = Bitmap.createBitmap(width, (height + gapHeight + height / 2), Config.ARGB_8888);
 79             // 在新图Bitmap对象之上创建画布
 80             Canvas canvas = new Canvas(bitmapWithReflection);
 81             // 抗锯齿效果
 82             canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG));
 83             // 绘制原图
 84             canvas.drawBitmap(originalImage, 0, 0, null);
 85             // 绘制间距
 86             Paint gapPaint = new Paint();
 87             gapPaint.setColor(0xFFCCCCCC);
 88             canvas.drawRect(0, height, width, height + gapHeight, gapPaint);
 89             // 绘制倒影
 90             canvas.drawBitmap(reflectionImage, 0, height + gapHeight, null);
 91             
 92             /* step3 渲染 */
 93             // 创建一个线性渐变的渲染器用于渲染倒影
 94             Paint paint = new Paint();
 95             LinearGradient shader = new LinearGradient(0, height, 0, (height + gapHeight + height / 2), 0x70ffffff, 0x00ffffff, TileMode.CLAMP);
 96             // 设置画笔渲染器
 97             paint.setShader(shader);
 98             // 设置图片混合模式
 99             paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
100             // 渲染倒影+间距
101             canvas.drawRect(0, height, width, (height + gapHeight + height / 2), paint);
102             
103             /* step4 在ImageView控件上绘制 */
104             ImageView imageView = new ImageView(mContext);
105             imageView.setImageBitmap(bitmapWithReflection);
106             imageView.setLayoutParams(mImagesLayoutParams);
107             // 打log
108             imageView.setTag(index);
109             
110             /* step5 释放heap */
111             originalImage.recycle();
112             reflectionImage.recycle();
113 //          bitmapWithReflection.recycle();
114             
115             mImages[index++] = imageView;
116         }
117     }
118 
119     @Override
120     public int getCount()
121     {
122         return Integer.MAX_VALUE;
123     }
124     
125     @Override
126     public Object getItem(int position)
127     {
128         return mImages[position];
129     }
130     
131     @Override
132     public long getItemId(int position)
133     {
134         return position;
135     }
136     
137     @Override
138     public View getView(int position, View convertView, ViewGroup parent)
139     {
140         return mImages[position % mImages.length];
141     }
142     /* 函数段end */
143 }
复制代码

其次是GalleryFlow,代码如下:

复制代码
  1 package pym.test.gallery3d.widget;
  2 
  3 import android.content.Context;
  4 import android.graphics.Camera;
  5 import android.graphics.Matrix;
  6 import android.util.AttributeSet;
  7 import android.util.Log;
  8 import android.view.View;
  9 import android.view.animation.Transformation;
 10 import android.widget.Gallery;
 11 
 12 /**
 13  * @author pengyiming
 14  * @date 2013-9-30
 15  * @function 自定义控件
 16  */
 17 public class GalleryFlow extends Gallery
 18 {
 19     /* 数据段begin */
 20     private final String TAG = "GalleryFlow";
 21     
 22     // 边缘图片最大旋转角度
 23     private final float MAX_ROTATION_ANGLE = 75;
 24     // 中心图片最大前置距离
 25     private final float MAX_TRANSLATE_DISTANCE = -100;
 26     // GalleryFlow中心X坐标
 27     private int mGalleryFlowCenterX;
 28     // 3D变换Camera
 29     private Camera mCamera = new Camera();
 30     /* 数据段end */
 31 
 32     /* 函数段begin */
 33     public GalleryFlow(Context context, AttributeSet attrs)
 34     {
 35         super(context, attrs);
 36         
 37         // 开启,在滑动过程中,回调getChildStaticTransformation()
 38         this.setStaticTransformationsEnabled(true);
 39     }
 40     
 41     /**
 42      * @function 获取GalleryFlow中心X坐标
 43      * @return
 44      */
 45     private int getCenterXOfCoverflow()
 46     {
 47         return (getWidth() - getPaddingLeft() - getPaddingRight()) / 2 + getPaddingLeft();
 48     }
 49     
 50     /**
 51      * @function 获取GalleryFlow子view的中心X坐标
 52      * @param childView
 53      * @return
 54      */
 55     private int getCenterXOfView(View childView)
 56     {
 57         return childView.getLeft() + childView.getWidth() / 2;
 58     }
 59     
 60     /**
 61      * @note step1 系统调用measure()方法时,回调此方法;表明此时系统正在计算view的大小
 62      */
 63     @Override
 64     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
 65     {
 66         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 67         
 68         mGalleryFlowCenterX = getCenterXOfCoverflow();
 69         Log.d(TAG, "onMeasure, mGalleryFlowCenterX = " + mGalleryFlowCenterX);
 70     }
 71     
 72     /**
 73      * @note step2 系统调用layout()方法时,回调此方法;表明此时系统正在给child view分配空间
 74      * @note 必定在onMeasure()之后回调,但与onSizeChanged()先后顺序不一定
 75      */
 76     @Override
 77     protected void onLayout(boolean changed, int l, int t, int r, int b)
 78     {
 79         super.onLayout(changed, l, t, r, b);
 80         
 81         mGalleryFlowCenterX = getCenterXOfCoverflow();
 82         Log.d(TAG, "onLayout, mGalleryFlowCenterX = " + mGalleryFlowCenterX);
 83     }
 84     
 85     /**
 86      * @note step2 系统调用measure()方法后,当需要绘制此view时,回调此方法;表明此时系统已计算完view的大小
 87      * @note 必定在onMeasure()之后回调,但与onSizeChanged()先后顺序不一定
 88      */
 89     @Override
 90     protected void onSizeChanged(int w, int h, int oldw, int oldh)
 91     {
 92         super.onSizeChanged(w, h, oldw, oldh);
 93         
 94         mGalleryFlowCenterX = getCenterXOfCoverflow();
 95         Log.d(TAG, "onSizeChanged, mGalleryFlowCenterX = " + mGalleryFlowCenterX);
 96     }
 97     
 98     @Override
 99     protected boolean getChildStaticTransformation(View childView, Transformation t)
100     {
101         // 计算旋转角度
102         float rotationAngle = calculateRotationAngle(childView);
103         
104         // 计算前置距离
105         float translateDistance = calculateTranslateDistance(childView);
106         
107         // 开始3D变换
108         transformChildView(childView, t, rotationAngle, translateDistance);
109         
110         return true;
111     }
112     
113     /**
114      * @function 计算GalleryFlow子view的旋转角度
115      * @note1 位于Gallery中心的图片不旋转
116      * @note2 位于Gallery中心两侧的图片按照离中心点的距离旋转
117      * @param childView
118      * @return
119      */
120     private float calculateRotationAngle(View childView)
121     {
122         final int childCenterX = getCenterXOfView(childView);
123         float rotationAngle = 0;
124         
125         rotationAngle = (mGalleryFlowCenterX - childCenterX) / (float) mGalleryFlowCenterX * MAX_ROTATION_ANGLE;
126         
127         if (rotationAngle > MAX_ROTATION_ANGLE)
128         {
129             rotationAngle = MAX_ROTATION_ANGLE;
130         }
131         else if (rotationAngle < -MAX_ROTATION_ANGLE)
132         {
133             rotationAngle = -MAX_ROTATION_ANGLE;
134         }
135         
136         return rotationAngle;
137     }
138     
139     /**
140      * @function 计算GalleryFlow子view的前置距离
141      * @note1 位于Gallery中心的图片前置
142      * @note2 位于Gallery中心两侧的图片不前置
143      * @param childView
144      * @return
145      */
146     private float calculateTranslateDistance(View childView)
147     {
148         final int childCenterX = getCenterXOfView(childView);
149         float translateDistance = 0;
150         
151         if (mGalleryFlowCenterX == childCenterX)
152         {
153             translateDistance = MAX_TRANSLATE_DISTANCE;
154         }
155         
156         return translateDistance;
157     }
158     
159     /**
160      * @function 开始变换GalleryFlow子view
161      * @param childView
162      * @param t
163      * @param rotationAngle
164      * @param translateDistance
165      */
166     private void transformChildView(View childView, Transformation t, float rotationAngle, float translateDistance)
167     {
168         t.clear();
169         t.setTransformationType(Transformation.TYPE_MATRIX);
170         
171         final Matrix imageMatrix = t.getMatrix();
172         final int imageWidth = childView.getWidth();
173         final int imageHeight = childView.getHeight();
174         
175         mCamera.save();
176         
177         /* rotateY */
178         // 在Y轴上旋转,位于中心的图片不旋转,中心两侧的图片竖向向里或向外翻转。
179         mCamera.rotateY(rotationAngle);
180         /* rotateY */
181         
182         /* translateZ */
183         // 在Z轴上前置,位于中心的图片会有放大的效果
184         mCamera.translate(0, 0, translateDistance);
185         /* translateZ */
186         
187         // 开始变换(我的理解是:移动Camera,在2D视图上产生3D效果)
188         mCamera.getMatrix(imageMatrix);
189         imageMatrix.preTranslate(-imageWidth / 2, -imageHeight / 2);
190         imageMatrix.postTranslate(imageWidth / 2, imageHeight / 2);
191         
192         mCamera.restore();
193     }
194     /* 函数段end */
195 }
复制代码

Bitmap解析用具BitmapScaleDownUtil,代码如下:

复制代码
 1 package pym.test.gallery3d.util;
 2 
 3 import android.content.res.Resources;
 4 import android.graphics.Bitmap;
 5 import android.graphics.BitmapFactory;
 6 import android.view.Display;
 7 
 8 /**
 9  * @author pengyiming
10  * @date 2013-9-30
11  * @function Bitmap缩放处理工具类
12  */
13 public class BitmapScaleDownUtil
14 {
15     /* 数据段begin */
16     private final String TAG = "BitmapScaleDownUtil";
17     /* 数据段end */
18 
19     /* 函数段begin */
20     /**
21      * @function 获取屏幕大小
22      * @param display
23      * @return 屏幕宽高
24      */
25     public static int[] getScreenDimension(Display display)
26     {
27         int[] dimension = new int[2];
28         dimension[0] = display.getWidth();
29         dimension[1] = display.getHeight();
30         
31         return dimension;
32     }
33     
34     /**
35      * @function 以取样方式加载Bitmap 
36      * @param res
37      * @param resId
38      * @param reqWidth
39      * @param reqHeight
40      * @return 取样后的Bitmap
41      */
42     public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight)
43     {
44         // step1,将inJustDecodeBounds置为true,以解析Bitmap真实尺寸
45         final BitmapFactory.Options options = new BitmapFactory.Options();
46         options.inJustDecodeBounds = true;
47         BitmapFactory.decodeResource(res, resId, options);
48 
49         // step2,计算Bitmap取样比例
50         options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
51 
52         // step3,将inJustDecodeBounds置为false,以取样比列解析Bitmap
53         options.inJustDecodeBounds = false;
54         return BitmapFactory.decodeResource(res, resId, options);
55     }
56 
57     /**
58      * @function 计算Bitmap取样比例
59      * @param options
60      * @param reqWidth
61      * @param reqHeight
62      * @return 取样比例
63      */
64     private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
65     {
66         // 默认取样比例为1:1
67         int inSampleSize = 1;
68 
69         // Bitmap原始尺寸
70         final int width = options.outWidth;
71         final int height = options.outHeight;
72 
73         // 取最大取样比例
74         if (height > reqHeight || width > reqWidth)
75         {
76             final int widthRatio = Math.round((float) width / (float) reqWidth);
77             final int heightRatio = Math.round((float) height / (float) reqHeight);
78 
79             // 取样比例为X:1,其中X>=1
80             inSampleSize = Math.max(widthRatio, heightRatio);
81         }
82 
83         return inSampleSize;
84     }
85     /* 函数段end */
86 }
复制代码

测试控件的Gallery3DActivity,代码如下:

复制代码
 1 package pym.test.gallery3d.main;
 2 
 3 import pym.test.gallery3d.R;
 4 import pym.test.gallery3d.util.BitmapScaleDownUtil;
 5 import pym.test.gallery3d.widget.GalleryFlow;
 6 import pym.test.gallery3d.widget.ImageAdapter;
 7 import android.app.Activity;
 8 import android.content.Context;
 9 import android.os.Bundle;
10 
11 /**
12  * @author pengyiming
13  * @date 2013-9-30
14  */
15 public class Gallery3DActivity extends Activity
16 {
17     /* 数据段begin */
18     private final String TAG = "Gallery3DActivity";
19     private Context mContext;
20     
21     // 图片缩放倍率(相对屏幕尺寸的缩小倍率)
22     public static final int SCALE_FACTOR = 8;
23     
24     // 图片间距(控制各图片之间的距离)
25     private final int GALLERY_SPACING = -10;
26     
27     // 控件
28     private GalleryFlow mGalleryFlow;
29     /* 数据段end */
30 
31     /* 函数段begin */
32     @Override
33     protected void onCreate(Bundle savedInstanceState)
34     {
35         super.onCreate(savedInstanceState);
36         mContext = getApplicationContext();
37         
38         setContentView(R.layout.gallery_3d_activity_layout);
39         initGallery();
40     }
41     
42     private void initGallery()
43     {
44         // 图片ID
45         int[] images = {
46                 R.drawable.picture_1,
47                 R.drawable.picture_2,
48                 R.drawable.picture_3,
49                 R.drawable.picture_4,
50                 R.drawable.picture_5,
51                 R.drawable.picture_6,
52                 R.drawable.picture_7 };
53 
54         ImageAdapter adapter = new ImageAdapter(mContext, images);
55         // 计算图片的宽高
56         int[] dimension = BitmapScaleDownUtil.getScreenDimension(getWindowManager().getDefaultDisplay());
57         int imageWidth = dimension[0] / SCALE_FACTOR;
58         int imageHeight = dimension[1] / SCALE_FACTOR;
59         // 初始化图片
60         adapter.createImages(imageWidth, imageHeight);
61 
62         // 设置Adapter,显示位置位于控件中间,这样使得左右均可"无限"滑动
63         mGalleryFlow = (GalleryFlow) findViewById(R.id.gallery_flow);
64         mGalleryFlow.setSpacing(GALLERY_SPACING);
65         mGalleryFlow.setAdapter(adapter);
66         mGalleryFlow.setSelection(Integer.MAX_VALUE / 2);
67     }
68     /* 函数段end */
69 }
复制代码

see效果图~~~

[转载]Android的三种网络通信方式 - 佬D - 博客园

mikel阅读(742)

[转载]Android的三种网络通信方式 – 佬D – 博客园.

转自http://blog.csdn.net/yuzhiboyi/article/details/7743390

Android平台有三种网络接口可以使用,他们分别是:java.net.*(标准Java接口)、Org.apache接口和Android.net.*(Android网络接口)。下面分别介绍这些接口的功能和作用。
1.标准Java接口
java.net.* 提供与联网有关的类,包括流、数据包套接字(socket)、Internet协议、常见Http处理等。比如:创建URL,以及 URLConnection/HttpURLConnection对象、设置链接参数、链接到服务器、向服务器写数据、从服务器读取数据等通信。这些在 Java网络编程中均有涉及,我们看一个简单的socket编程,实现服务器回发客户端信息。
服务端:

复制代码
public class Server implements Runnable{
    @Override
    public void run() {
        Socket socket = null;
        try {
            ServerSocket server = new ServerSocket(18888);
            //循环监听客户端链接请求
            while(true){
                System.out.println("start...");
                //接收请求
                socket = server.accept();
                System.out.println("accept...");
                //接收客户端消息
                BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String message = in.readLine();
                //发送消息,向客户端
                PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
                out.println("Server:" + message);
                //关闭流
                in.close();
                out.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if (null != socket){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
    //启动服务器
    public static void main(String[] args){
        Thread server = new Thread(new Server());
        server.start();
    }
}
复制代码

客户端,MainActivity

复制代码
public class MainActivity extends Activity {
    private EditText editText;
    private Button button;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        editText = (EditText)findViewById(R.id.editText1);
        button = (Button)findViewById(R.id.button1);

        button.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Socket socket = null;
                String message = editText.getText().toString()+ "\r\n" ;
                try {
                    //创建客户端socket,注意:不能用localhost或127.0.0.1,Android模拟器把自己作为localhost
                    socket = new Socket("<span style="font-weight: bold;">10.0.2.2</span>",18888);
                    PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter
                            (socket.getOutputStream())),true);
                    //发送数据
                    out.println(message);

                    //接收数据
                    BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    String msg = in.readLine();
                    if (null != msg){
                        editText.setText(msg);
                        System.out.println(msg);
                    }
                    else{
                        editText.setText("data error");
                    }
                    out.close();
                    in.close();
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                finally{
                    try {
                        if (null != socket){
                            socket.close();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }
}

复制代码

布局文件:

复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <TextView android:layout_width="fill_parent"
        android:layout_height="wrap_content" android:text="@string/hello" />
    <EditText android:layout_width="match_parent" android:id="@+id/editText1"
        android:layout_height="wrap_content"
        android:hint="input the message and click the send button"
        ></EditText>
    <Button android:text="send" android:id="@+id/button1"
        android:layout_width="fill_parent" android:layout_height="wrap_content"></Button>
</LinearLayout>
复制代码

启动服务器:

javac com/test/socket/Server.java
java com.test.socket.Server

运行客户端程序:

结果如图:

注意:服务器与客户端无法链接的可能原因有:
没有加访问网络的权限:<uses-permission android:name=”android.permission.INTERNET”></uses-permission>
IP地址要使用:10.0.2.2
模拟器不能配置代理。

2。Apache接口
对于大部分应用程序而言JDK本身提供的网络功能已远远不够,这时就需要Android提供的Apache HttpClient了。它是一个开源项目,功能更加完善,为客户端的Http编程提供高效、最新、功能丰富的工具包支持。
下面我们以一个简单例子来看看如何使用HttpClient在Android客户端访问Web。
首先,要在你的机器上搭建一个web应用myapp,只有很简单的一个http.jsp
内容如下:

复制代码
<%@page language="java" import="java.util.*" pageEncoding="utf-8"%>
<html>
<head>
<title>
Http Test
</title>
</head>
<body>
<%
String type = request.getParameter("parameter");
String result = new String(type.getBytes("iso-8859-1"),"utf-8");
out.println("<h1>" + result + "</h1>");
%>
</body>
</html>
复制代码

然后实现Android客户端,分别以post、get方式去访问myapp,代码如下:
布局文件:

复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView
    android:gravity="center"
    android:id="@+id/textView"  
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/hello"
    />
<Button android:text="get" android:id="@+id/get" android:layout_width="match_parent" android:layout_height="wrap_content"></Button>
<Button android:text="post" android:id="@+id/post" android:layout_width="match_parent" android:layout_height="wrap_content"></Button>
</LinearLayout>
复制代码

资源文件:
strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">通过按钮选择不同方式访问网页</string>
    <string name="app_name">Http Get</string>
</resources>

主Activity:

复制代码
public class MainActivity extends Activity {
    private TextView textView;
    private Button get,post;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        textView = (TextView)findViewById(R.id.textView);
        get = (Button)findViewById(R.id.get);
        post = (Button)findViewById(R.id.post);

        //绑定按钮监听器
        get.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                //注意:此处ip不能用127.0.0.1或localhost,Android模拟器已将它自己作为了localhost
                String uri = "http://192.168.22.28:8080/myapp/http.jsp?parameter=以Get方式发送请求";
                textView.setText(get(uri));
            }
        });
        //绑定按钮监听器
        post.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                String uri = "http://192.168.22.28:8080/myapp/http.jsp";
                textView.setText(post(uri));
            }
        });
    }
    /**
     * 以get方式发送请求,访问web
     * @param uri web地址
     * @return 响应数据
     */
    private static String get(String uri){
        BufferedReader reader = null;
        StringBuffer sb = null;
        String result = "";
        HttpClient client = new DefaultHttpClient();
        HttpGet request = new HttpGet(uri);
        try {
            //发送请求,得到响应
            HttpResponse response = client.execute(request);

            //请求成功
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK){
                reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
                sb = new StringBuffer();
                String line = "";
                String NL = System.getProperty("line.separator");
                while((line = reader.readLine()) != null){
                    sb.append(line);
                }
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        finally{
            try {
                if (null != reader){
                    reader.close();
                    reader = null;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (null != sb){
            result =  sb.toString();
        }
        return result;
    }
    /**
     * 以post方式发送请求,访问web
     * @param uri web地址
     * @return 响应数据
     */
    private static String post(String uri){
        BufferedReader reader = null;
        StringBuffer sb = null;
        String result = "";
        HttpClient client = new DefaultHttpClient();
        HttpPost request = new HttpPost(uri);

        //保存要传递的参数
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        //添加参数
        params.add(new BasicNameValuePair("parameter","以Post方式发送请求"));

        try {
            //设置字符集
            HttpEntity entity = new UrlEncodedFormEntity(params,"utf-8");
            //请求对象
            request.setEntity(entity);
            //发送请求
            HttpResponse response = client.execute(request);

            //请求成功
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK){
                System.out.println("post success");
                reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
                sb = new StringBuffer();
                String line = "";
                String NL = System.getProperty("line.separator");
                while((line = reader.readLine()) != null){
                    sb.append(line);
                }
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        finally{
            try {
                //关闭流
                if (null != reader){
                    reader.close();
                    reader = null;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (null != sb){
            result =  sb.toString();
        }
        return result;
    }
}
复制代码

运行结果如下:

3.android.net编程:
常常使用此包下的类进行Android特有的网络编程,如:访问WiFi,访问Android联网信息,邮件等功能。这里不详细讲。

[转载]使用Jquery实现Win8开始菜单效果的站点导航 - Just a dream. - 博客园

mikel阅读(1057)

[转载]使用Jquery实现Win8开始菜单效果的站点导航 – Just a dream. – 博客园.

前言:

本人是个Metro控,自我感觉到处都充斥着Metro的元素,个人认为这种风格强调表现以及内容,以简洁著称,不过也不是大部分都喜欢,也有一些 人和你讨厌这种风格~不过本人非常喜欢这种风格,看我博客的风格就知道了,嘿嘿,也正在用wp8的手机lumia920,win8刚出来的时候对开始菜单 也比较喜欢,不过有点不习惯,无意间看到了这个网站导航,有一段时间研究过他的源码,无奈前段知识捉襟见肘,后来不了了之,直到有一天闲逛的时候看到懒人图库的的资源,果断下载下来收藏了,以后有机会的话做网站考虑用它做成站点地图~

效果图一:

效果图二

 感谢原文作者的分享 谢谢~!

两款源码

[转载]Web 开发人员必备的12款 Chrome 扩展程序 - 梦想天空(山边小溪) - 博客园

mikel阅读(825)

[转载]Web 开发人员必备的12款 Chrome 扩展程序 – 梦想天空(山边小溪) – 博客园.

之前已经分享过一些帮助 Web 开发人员和设计师的 Chrome 扩展,这次我们继续展示一组很有用的 Chrome 应用程序。这些免费的 Chrome 应用程序可以简化您的工作流程,为了加快您的工作流程,从而快速帮助你提高你的工作效率。

谷歌浏览器是一个功能强大的现代浏览器,许多人都喜欢使用它。在这里,我们给大家带来 Chrome 应用程序的另一个有趣的收集——专注于网页设计和开发的12款最重要的 Chrome 扩展程序。

您可能感兴趣的相关文章

 

Awesome Screenshot: Capture & Anno

捕获整个网页或其中任何部分,使用矩形,圆形,箭头,线条和文字标注,模糊敏感信息,一键注释。

 

CSS Grady

生成 CSS 渐变从来没有现在这么简单,自动生成兼容多个浏览器的 CSS3 代码片段。

 

ColorPicker 0.9

Web 开发人员的取色器/滴管。从目前的网页 DOM 中的任何元素挑选颜色或改变颜色。

 

Pixlr Express

Pixlr Express 是一款流畅和充满现代气息的照片编辑器,很多自带的照片效果,遮罩,贴纸和文本工具。

 

Pixlr Editor

Pixlr 是一个全功能的照片编辑器,直接在您的浏览器使用,无需下载,无需等待,完全免费。

 

CSS-Shack

CSS-Shack 允许您创建图层样式(就像图像编辑软件),并将其导出为 CSS 文件,或者复制到剪贴板。

 

Moqups

最好的 HTML5 应用程序之一,用于创建清晰的实物模型,线框图和交互原型。

 

Palette for Chrome

让你可以从任何图像创建一个调色板,只需右键单击图片,然后选择调色板 Chrome 的选项。

 

Google Font Previewer for Chrome

让您从谷歌字体目录中的字体选择带有几个文本样式选项的字体,并在当前选项卡中预览。

 

CSSViewer

CSSViewer 是一个简单的 CSS 属性查看器,最初是尼古拉斯·休恩编写的一款 Firefox 插件。

 

Speed Tracer (by Google)

Speed​​ Tracer 是一个来帮助你识别和修复您的Web应用程序性能问题的工具,使用可视化的度量方式分析应用程序运行。

 

BuiltWith Technology Profiler

BuiltWith 扩展让你轻松点击一下按钮就能知道当前网站是使用什么技术构建的。

[转载]SQLSERVER中的元数据锁 - 桦仔 - 博客园

mikel阅读(1265)

[转载]SQLSERVER中的元数据锁 – 桦仔 – 博客园.

网上对于元数据锁的资料真的非常少

元数据锁一般会出现在DDL语句里

下面列出数据库引擎可以锁定的资源
资源 说明
RID 用于锁定堆(heap)中的某一行
KEY 用于锁定索引上的某一行,或者某个索引键
PAGE 锁定数据库中的一个8KB页,例如数据页或索引页
EXTENT 一组连续的8页(区)
HOBT 锁定整个堆或B树的锁
TABLE 锁定包括所有数据和索引的整个表
FILE 数据库文件
APPLICATION 应用程序专用的资源
METADATA    元数据锁
ALLOCATION_UNIT 分配单元
DATABASE 整个数据库
锁住元数据的目的跟其他的锁是一样的,都是保证事务的一致性
实验环境:SQLServer2005 ,SQLServer2012,如果没有特别说明的话,SQL语句都是在SQLSERVER2005上运行
例如,在会话一里drop掉ABC表
复制代码
 1 --session 1
 2 USE [pratice]
 3 GO
 4 CREATE TABLE ABC(ID INT)
 5 GO
 6 
 7 --------------------------
 8 BEGIN TRAN
 9 DROP TABLE ABC
10 --COMMIT TRAN
复制代码

 

在会话二里使用元数据函数读取ABC这张表的objectid

复制代码
1  --session 2
2 USE [pratice]
3 GO
4 ---------------------------------------
5 BEGIN TRAN
6 SELECT OBJECT_ID('ABC')
7 --COMMIT TRAN
复制代码

这时候就会看到元数据锁,否则就会出问题

我们看一下在session一里面当drop掉表ABC的时候申请了哪些锁

复制代码
 1 USE [pratice]
 2 GO
 3 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
 4 GO
 5 
 6 BEGIN TRAN
 7 DROP TABLE ABC
 8 
 9 --COMMIT TRAN
10 
11 
12 SELECT
13 [request_session_id],
14 c.[program_name],
15 DB_NAME(c.[dbid]) AS dbname,
16 [resource_type],
17 [request_status],
18 [request_mode],
19 [resource_description],OBJECT_NAME(p.[object_id]) AS objectname,
20 p.[index_id]
21 FROM sys.[dm_tran_locks] AS a LEFT JOIN sys.[partitions] AS p
22 ON a.[resource_associated_entity_id]=p.[hobt_id]
23 LEFT JOIN sys.[sysprocesses] AS c ON a.[request_session_id]=c.[spid]
24 WHERE c.[dbid]=DB_ID('pratice') AND a.[request_session_id]=@@SPID  ----要查询申请锁的数据库
25 ORDER BY [request_session_id],[resource_type]
复制代码

SQLSERVER会锁住一些系统表,例如:syshobts、sysallocunits等,以便对这些系统表进行更新

还有看到SQLSERVER在元数据上加了架构锁

架构锁:数据库引擎在表数据定义语言(DDL)操作(例如添加列或删除表)的过程中使用架构修改(sch-m)锁

以阻止其他用户对这个表格的访问

数据库引擎在编译和执行查询时使用架构稳定(sch-s)锁(稳定stable),sch-s锁不会阻止其他事务访问表格里的数据,但是,

会阻止对表格做修改性的DDL操作和DML操作

 

这些元数据应该是位于resource数据库中

resource数据库:包含SQLSERVER附带的所有系统对象副本的只读数据库,resource数据库是不能备份的,而且在SSMS里是看不见的

关于resource数据库:SQL Server 2005的Resource数据库

Resource 数据库是只读数据库,它包含了 SQL Server 2005 中的所有系统对象。

SQL Server 系统对象(例如 sys.objects)在物理上存在于 Resource 数据库中,

但在逻辑上,它们出现在每个数据库的 sys 架构中。Resource 数据库不包含用户数据或用户元数据。


当查询某些系统表的时候也会加上元数据锁

复制代码
 1 USE [pratice]
 2 GO
 3 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
 4 GO
 5 
 6 BEGIN TRAN
 7 select object_id from sys.tables  where name = 'xxx' 
 8 
 9 --COMMIT TRAN
10 
11 
12 SELECT
13 [request_session_id],
14 c.[program_name],
15 DB_NAME(c.[dbid]) AS dbname,
16 [resource_type],
17 [request_status],
18 [request_mode],
19 [resource_description],OBJECT_NAME(p.[object_id]) AS objectname,
20 p.[index_id]
21 FROM sys.[dm_tran_locks] AS a LEFT JOIN sys.[partitions] AS p
22 ON a.[resource_associated_entity_id]=p.[hobt_id]
23 LEFT JOIN sys.[sysprocesses] AS c ON a.[request_session_id]=c.[spid]
24 WHERE c.[dbid]=DB_ID('pratice') AND a.[request_session_id]=@@SPID  ----要查询申请锁的数据库
25 ORDER BY [request_session_id],[resource_type]
复制代码


令本人不明白的是:在查询时,有时候也会加上元数据锁

建表脚本:

View Code

 

查看申请的锁

复制代码
 1 USE [pratice]
 2 GO
 3 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
 4 GO
 5 
 6 BEGIN TRAN
 7 SELECT * FROM ct1 WHERE c1=50
 8 
 9 --COMMIT TRAN
10 
11 
12 SELECT
13 [request_session_id],
14 c.[program_name],
15 DB_NAME(c.[dbid]) AS dbname,
16 [resource_type],
17 [request_status],
18 [request_mode],
19 [resource_description],OBJECT_NAME(p.[object_id]) AS objectname,
20 p.[index_id]
21 FROM sys.[dm_tran_locks] AS a LEFT JOIN sys.[partitions] AS p
22 ON a.[resource_associated_entity_id]=p.[hobt_id]
23 LEFT JOIN sys.[sysprocesses] AS c ON a.[request_session_id]=c.[spid]
24 WHERE c.[dbid]=DB_ID('pratice') AND a.[request_session_id]=@@SPID  ----要查询申请锁的数据库
25 ORDER BY [request_session_id],[resource_type]
复制代码


但是在SQLSERVER2012

无论是
BEGIN TRAN
select object_id from sys.tables with (nolock) where name = ‘xxx’
还是
BEGIN TRAN
SELECT * FROM ct1 WHERE c1=50

都看不到元数据锁了

 

1 BEGIN TRAN
2 select object_id from sys.tables with (nolock) where name = 'xxx'

 

1 BEGIN TRAN
2 select object_id from sys.tables with (nolock) where name = 'xxx'

 

可能SQLSERVER2012隐藏了元数据锁,觉得就算显示出元数据锁对于排查阻塞也没有多大意义,干脆隐藏算了

但是这里并不是说SQLSERVER2012没有了元数据锁

元数据是一种资源,可以锁定的资源,元数据锁并不是一种锁类型!!!


相关文章:

http://social.msdn.microsoft.com/Forums/zh-CN/10c07757-741d-4473-888c-174c9c91f038
http://social.msdn.microsoft.com/Forums/zh-CN/c5c20bed-3fb7-414e-ade5-fb70c532cd84
http://msdn.microsoft.com/zh-cn/library/ms187812(v=sql.105).aspx

[转载]EasyUI在MVC4中需要部分刷新页面时load()后页面变形问题! - 政政糖 - 博客园

mikel阅读(1304)

[转载]EasyUI在MVC4中需要部分刷新页面时load()后页面变形问题! – 政政糖 – 博客园.

  最近在使用MVC4与EasUI过程中遇到些容易导致界面变形的问题,纠结了很久,但其实当发现问题在哪里时,倒觉得最终还是自己对MVC4的概念没把握好,OK,show time.  本示例Contact 页面的部分标签loadAbout页面。

1. 首先复原一下问题,相信应该会有后来的朋友也遇见。加载JS 与CSS 。

App_Start文件下的BundleConfig.cs

1  bundles.Add(new ScriptBundle("~/bundles/jquery-easyui").Include(
2                         "~/Content/jquery-easyui-1.3.4/jquery-easyui-min.js"));
3 
4  bundles.Add(new StyleBundle("~/Content/jquery-easyui-1.3.4/themes/default/css").Include("~/Content/jquery-easyui-1.3.4/themes/default/easyui.css"));

以上内容在配置时需要注意:默认download下来的JQuery.easyui.min.js 名称要改为JQuery-easyui-min.js,否则加载不成功;其次尤其要注意stylebundle的virtualpath问题,必须是 XXX/Default/XXX 才可以,要到达CSS的目录,但名称可以自定义;如果为XXX/Default的话,不好意思,认不到相应的CSS。

2. 在_Layout.cshtml 页加载相应的引用;

1   @Scripts.Render("~/bundles/jquery-easyui")
2  @Styles.Render("~/Content/css")

  使用link标签将样式表放在文档head中,且在script标签前。

  原因是:另外样式放在底部的加载情况是:当页面逐步加载时,文字首先显示,接着是图片。最后,当样式表正确下载了之后,已经呈现的文字  和图片就要用新的样式重绘。就好像格子铺里,东西都按顺序摆好了,但是老板说,这个东西得这样、那样摆,又得挨个重新摆了。

  将script脚本放在底部

原因是:脚本放在页面顶部同样会引起页面阻塞,阻止页面逐渐呈现。

  http协议1.1规范,建议浏览器从每个主机并行下载两个组件,并行下载的数 量的优劣取决于带宽和CPU,过多的并行下载也会降低性能。并行  下载的优点很明显,组件可以并行下载,但是下载脚本时并行下载是禁用的,是为了保证脚 本能够按照正确的顺序执行。因为脚本不能并行下  载,为避免组件的下载延迟,最好将脚本放在页面底部。

3. Controller代码调用页面,使用PartialView,如此框架便会自动过滤掉母版页的样式与脚本,成为一个干净的partial.

1   public PartialViewResult About()
2         {
3             ViewBag.Message = "Your app description page.";
5             return PartialView();
6         }

4. Contact页面

复制代码
 1 <section class="contact">
 2     <h2>日历 - Calendar</h2>  
 3     <div id="contantDiv" class="demo-info" style="margin-bottom: 10px">  
 4         <div class="demo-tip icon-tip"></div>  
 5         <div>单击选择日期</div>  
 6     </div>  
 7 </section>
 8 
 9 <script type="text/javascript">
10     function loadit() {
11         $("#contantDiv").load("About");}
12 </script>
复制代码

5. About页面

1  <div id="nihao" class="easyui-calendar" style="width: 180px; height: 180px;"></div>
2  @* @Scripts.Render("~/bundles/jquery")*@ //此处是主要问题点,一不小心,就会让人很捉鸡。
3  @Scripts.Render("~/bundles/jquery-easyui")

 

如果不增加标红的jQuery引用,则一切正常;

如果加上后,某处似乎会出现重复的问题,如下:

此问题,原因其实很简单,easyui 的控件是需要动态画上去的,而之前母版页加载的easyui.js 在Load()方法调用后,是不会再执行了,因为那货已经在母版页加载过一次了,此次本来就是使用了Ajax的部分页面重画,当然不能再麻烦人家了。有时 心不平气不和就会费神费力浪费青春,回过头来,原来解决问题的方案早都在哪里等Cao了。

 

世界是你们的,也是我们的,但归根结底是他们的。

[转载]EasyUI这个框架用了好久了,总结一下遇到的问题和解决方法 - 光之暗面 - 博客园

mikel阅读(1420)

[转载]EasyUI这个框架用了好久了,总结一下遇到的问题和解决方法 – 光之暗面 – 博客园.

1. JQuery EasyUI动态添加控件或者ajax加载页面后不能自动渲染问题的解决方法:

我们之所以在页面中,只要书写相应easyui的class,Easyui就能成功渲染页面,这是因为解析器在默认情况下,parser会在文档装载完成的时候($(document).ready)被调用一次,而且是渲染整个页面。

然而当页面装载完后,如果用JavaScript生成的DOM中包含了Easyui支持控件的class,比如说,用JavaScript生成了以下代码:

<a id="tt" href="#" class="easyui-linkbutton" data-options="iconCls:'icon-search'">easyui</a>

虽然页面上有这样的DOM了,但是没有被渲染为Easyui的linkbutton插件,原因是Easyui并不会一直监听页面,所以不会主动渲染,这时候就需要手工调用Easyui的parser进行解析了。

手工调用需要注意以下几点:

解析目标为指定DOM的所有子孙元素,不包含这个DOM自身:

比如上面代码生成的HTML,id=”tt”是我们想要的LinkButton,像下面代码去手工解析的话是得不到你想要的结果的:

 $.parser.parse($('#tt'));

道理很简单,parser只渲染tt的子孙元素,并不包括tt自身,而它的子孙元素并不包含任何Easyui支持的控件class,所以这个地方就得不到你想要的效果了,应该这样写:

 $.parser.parse($('#tt').parent());

渲染tt的父节点的所有子孙元素就可以了,不管你的javascript输出什么DOM,直接渲染其父节点就可以保证页面能被正确解析。

 

2. 解决EasyUI窗体阻止ASP.NET控件事件代码:

原因是窗体内的控件不在form表单中了,无法渲染为ASP.NET控件,所以我们要手动给这个id=’dialog’的窗体放到form表单中

$('#dialog').parent().appendTo($("form:first"))

 

3. 如何让EasyUI弹出窗体跳出框架(原网址:原文链接):

只要在最外面的框架页面加个div,然后用parent.div的id就可以的。但是必须得弹出框得是一个页面(iframe指向的新页面)。

复制代码
//父页面代码

<div id="div_info"></div>

 //子页面代码

//外层弹出框1
function openFirstWin(url, title, width, height) {
var strWidth;
var strHeight;
if (width == null)
strWidth = 800;
else
strWidth = width;
if (height == null)
strHeight = 500;
else
strHeight = height;
if (url != null) {
//var content = '<iframe name=\"first\" scrolling=\"auto\" frameborder=\"0\" src=\"' + url + '\" style=\"width:100%\;height:100%\;\"></iframe>';
var content = createIFrame(url);
parent.$('#div_info').window({
close: false,
modal: true,
draggable: false,
title: title,
animate: true,
content: content,
minimizable: false,
width: strWidth,
height: strHeight,
top: (parent.$(parent.window).height() - height) * 0.5,
left: (parent.$(window).width() + 200 - strWidth) * 0.5
});
}
return false;
}

 
复制代码

 

[转载]图解Android - Android GUI 系统 (2) - 窗口管理系统 - 漫天尘沙 - 博客园

mikel阅读(1194)

[转载]图解Android – Android GUI 系统 (2) – 窗口管理系统 – 漫天尘沙 – 博客园.

图解Android – Zygote 和 System Server 启动分析一 文里,我们已经知道Android 应用程序是怎么创建出来,大概的流程是 ActivityManagerService -> Zygote -> Fork App, 然后应用程序在ActivityThread 中Loop 进入无限的等待循环中处理来自AcitivyManagerService的消息。如果一个Android的应用有Acitivity, 那它起来后的第一件事情就是将自己显示出来,这个过程是怎样的? 这就是本章节要讨论的话题。

Android 中跟窗口管理相关(不包括显示和按键处理)主要有两个进程,Acitivty所在进程 和 WndowManagerService 所在进程(SystemServer).  上图中用不同颜色区分。它们的分工是,Activity进程内主要完成的是窗口内View的管理,而WindowManager Service 管的是来自与不同的Acitivity的窗口。

1. Acitivty显示前的准备工作

图解Android – Zygote, System Server 启动分析中 我们已经知道,一个新的应用被fork完后,第一个调用的方法就是 ActivityThread的main(),这个函数主要做的事情就是创建了一个ActivityThread线程,然后loop()开始等待。当收到 来自 ActivityManager 的 LAUNCH_ACTIVITY 消息后,Activity开始了他的显示之旅。下图显示了Activity在显示前的准备流程。


图中分为三大块, 最右上角是Acitivity应用的初始化。中间部分是Acitivity Window的显示准备工作,最左边这张图,则是window真正的显示过程。因为本章关注的是Window显示前的准备工作,我们把注意力放在中间那部分。

addView() 中ViewRootImpl 被创建出来,在概论 一文中我们提过ViewRootImpl 的地位相当与MVC架构中的C,Controller的创建意味着这里才是真正的开始。ViewRootImpl 构造函数里创建了一个Surface对象(注意这是一个空的Surface,里面没有有用的信息),然后就是通过OpenSession() 创建了一个和WindowManagerService 的通话通道,然后想WMService 报道,并将自己加入到WindowManager的窗口队列中。图中土黄色部分表示在WindowManagerService 进程里运行,它在这里做了几件重要的事情,一件是new了一个WindowState对象,他是ViewRootImpl 在WMService端的代理。然后openInputChannelPair() and RegisterInputChannel(), 将这个新建窗口告知InputManagerService, 告知它接下来的用户输入事件对应的响应窗口(这部分会在Android的用户输入处理 一文中介绍),最后他创建了一个Surface Session. 看第一张类图,SurfaceSession 在native 端 有对应的实现,他最终又引用到ISurfaceComposerClient. 看到IXXX 意味着走到了一个进程的边界。对了,远处就是Surface Flinger。ISurfaceComposerClient 提供了两个接口,createSurface 和 createDisplay, 难道Surface 就在这里创建?  非也,现在还不是时候,Surface会在真正要显示的时候创建。到这里,Activity和WindowManager Service 已经建立起联系,WindowManager Service 里的工作也暂告一个段落,工作又回到了Activity(绿色)一端,makeVisible() -> invalidate() -> scheduleTravesal() -> postCallback … ViewRootImpl 向Choreographer对象注册了一个回调函数,然后对外发送ACTIVITY_RESUMED, 结束这一阶段准备工作?

Surface还没有创建,显示还没有开始,怎么就ACTIVITY_RESUMED了? 因为真正的显示在最多16ms以后就会开始。

2. Choreographer 和 Surface的创建

所有的图像显示输出都是由时钟驱动的,这个驱动信号成为VSYNC。这个名词来源于模拟电视时代,在那个年代,因为带宽的限制,每一帧图像都有分成 两次传输,先扫描偶数行(也称偶场)传输,再回到头部扫描奇数行(奇场),扫描之前,发送一个VSYNC同步信号,用于标识这个这是一场的开始。场频,也 就是VSYNC 频率决定了帧率(场频/2). 在现在的数字传输中,已经没有了场的概念,但VSYNC这一概念得于保持下来,代表了图像的刷新频率,以为着收到VSYNC信号后,我们必须将新的一帧进 行显示。

VSYNC一般由硬件产生,也可以由软件产生(如果够准确的话),Android 中VSYNC来着于HWComposer,接收者没错,就是Choreographer。Choreographer英文意思是编舞者,跳舞很讲究节奏不 是吗,必须要踩准点。Choreographer 就是用来帮助Android的动画,输入,还是显示刷新按照固定节奏来完成工作的。看看Chroreographer 和周边的类结构。

 

从图中我们可以看到, Choreographer 是ViewRootImpl 创建的,它拥有一个Receiver, 用来接收外部传入的Event,它还有一个Callback Queue, 里面存放着若干个CallbackRecord, 还有一个FrameHandler,用来handleMessage, 最后,它还跟Looper有引用关系。再看看下面这张时序图,一切就清楚了,

 

 

首先Looper调用loop() 后,线程进入进入睡眠,直到收到一个消息。Looper也支持addFd()方法,这样如果某个fd上发生了IO操作(read/write), 它也会从睡眠中起来。Choreographer实现用到了两种方式,首先他通过某种方式获取到SurfaceFlinger 进程提供的fd,然后将其交给Looper进行监听,只要SurfaceFlinger往这个fd写入VSync事件,looper便会唤醒。 Lopper唤醒后,会执行onVsync()时间,这里面没有做太多事情,而是调用Handler接口 sendMessageAtTime() 往消息队列里又送了一个消息。这个消息最终调用到了Handler (实际是FrameHandler)的handleCallback来完成上层安排的工作。为什么要绕这么大个圈?为什么不在onVSync里直接 handleCallback()? 毕竟onVSync 和 handleCallback() 都在一个线程里。这是因为MessageQueue 不光接收来自SurfaceFlinger 的VSync 事件,还有来自上层的控制消息。VSync的处理是相当频繁的,如果不将VSync信号送人MessageQueue进行排队,MessageQueue 里的事件就有可能得不到及时处理,严重的话会导致溢出。当然了,如果因为VSync信号排队而导致处理延迟,这就是设计的问题了,这也是为什么 Android文档里反复强调在Activity的onXXX()里不要做太耗时的工作,因为这些回调函数和Choreographer运行在同一个线程 里,这个线程就是所谓的UI线程。

言归正传,继续往前,VSync事件最终在doFrame()里调了三次doCallbacks()来完成不同的功能, 分别处理用户输入时间,动画刷新(动画就是定时更新的图片), 最后执行performTraversals(),这个函数里面主要是检查当前窗口当前状态,比如说是否依然可见,尺寸,方向,布局是否发生改变(可能是 由前面的用户输入触发的),分别调用performMeasure(), performLayout, performDraw()完成测量,布局和绘制工作。我们会在后面详细学习这三个函数,这里我们主要看一下第一次进入 performTraversals的情况,因为第一次会做些初始化的工作,最重要的一件就是如本章标题,创建Surface对象。

回看图2,我们可以看到Surface的创建不是在Activity进程里,而是在WindowManagerService完成的(颜色)。当一 个Activity第一次显示的时候,Android显示切换动画,因此Surface是在动画的准备过程中创建的,具体就是类 WindowStateAnimator.createSurfaced()函数。它最终创建了一个SurfaceControl 对象。SurfaceControl是Android 4.3 里新引进的类,Google从之前的Surface类里拆出部分接口,变成SurfaceControl,为什么要这样? 为了让结构更清晰,WindowManagerService 只能对Surface进行控制,但并不更新Surface里的内容,分拆之后,WindowManagerService 只能访问SurfaceControl,它主要控制Surface的创建,销毁,Z-order,透明度,显示或隐藏,等等。而真正的更新者,View会 通过Canvas的接口将内容画到Surface上。那View怎么拿到WMService创建的Surface,答案是下面的代码 里,surfaceControl 被转换成一个Surface对象,然后传回给ViewRoot, 前面创建的空的Surface现在有了实质内容。Surface通过这种方式被创建出来,Surface对应的Buffer 也相应的在SurfaceFlinger内部通过HAL层模块(GRAlloc)分配并维护在SurfaceFlinger 内部,Canvas() 通过dequeueBuffer()接口拿到Surface的一个Buffer,绘制完成后通过queueBuffer()还给 SurfaceFlinger进行绘制。

复制代码
                    SurfaceControl surfaceControl = winAnimator.createSurfaceLocked();
                    if (surfaceControl != null) {
                        outSurface.copyFrom(surfaceControl);
                        if (SHOW_TRANSACTIONS) Slog.i(TAG,
                                "  OUT SURFACE " + outSurface + ": copied");
                    } else {
                        outSurface.release();
                    }
复制代码

 

到这里,我们知道了Activity的三大工作,用户输入响应,动画,和绘制都是由一个定时器驱动的,Surface在Activity第一次启动 时由WindowManager Service创建。接下来我们具体看一下View是如何画在Surface Buffer上的,而Surface Buffer的显示则交由图解Android – Android GUI 系统 (3) – Surface Flinger 来讨论。

 

3. View的Measure, Layout 和 Draw

直接从前面提到的performMeasure()函数开始.

实际的函数调用栈比这里深的多得多,这个函数会从view的结构数顶(DecorView), 一直遍历到叶节点。但大致可以归纳成图中的三种类,DecorView, ViewGroup 和 View, 它们的类结构如下图所示:

所以可见的View(不包括DecorView 和 ViewGroup)都是一个矩形,Measure的目的就是算出这个矩形的尺寸, mMeasuredWidth 和 mMeasuredHeight (注意,这不是最终在屏幕上显示的尺寸),这两个尺寸的计算受其父View的尺寸和类型限制,这些信息存放在 MeasureSpec里。MeasureSpec 里定义了三种constraints,

  • 复制代码
            /* 父View对子View尺寸没有任何要求,其可以设任意尺寸*/
            public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    
            /* 父View为子View已经指定了大小*/
            public static final int EXACTLY     = 1 << MODE_SHIFT;
    
            /*父View没有指定子View大小,但其不能超过父View的边界 */
            public static final int AT_MOST     = 2 << MODE_SHIFT;
    复制代码

widthMeasureSpec 和 heightMeasureSpec 作为 onMeasure的参数出入,子View根据这两个值计算出自己的尺寸,最终调用 setMeasuredDimension() 更新mMeasuredWidth 和 mMeasuredHeight.

performMeasure() 结束后,所有的View都更新了自己的尺寸,接下来进入performLayout().

 

4. Windows 的管理

我们再次回到WindowManager Service 内部研究它的工作原理。

[转载]图解Android - Android GUI 系统 (1) - 概论 - 漫天尘沙 - 博客园

mikel阅读(1050)

[转载]图解Android – Android GUI 系统 (1) – 概论 – 漫天尘沙 – 博客园.

Android的GUI系统是Android最重要也最复杂的系统之一。它包括以下部分:

  1. 窗口和图形系统 – Window and View Manager System.
  2. 显示合成系统 – Surface Flinger
  3. 用户输入系统 – InputManager System
  4. 应用框架系统 – Activity Manager System.

它们之间的关系如下图所示

 

只有对这些系统的功能和工作原理有基本的了解,我们才能够解答一些经常萦绕在脑海里的问题,比如说:

  1. Activity启动过程是怎样的?Activity的onXXX()在后台都做了什么工作?Activity的show()和hide()是如何控制的?
  2. Surface是什么时候被谁创建的?
  3. Android是如何把一个个的控件画到Surface上的?然后显示到手机屏幕上的?
  4. 应用程序窗口是如何获取焦点的?用户的按键(触摸屏或键盘)是怎样传递到当前的窗口?
  5. Android是一个多窗口的系统吗?
  6. Android是怎么支持多屏互动的?(Wifi Display)
  7. 输入法窗口到底属于哪个进程?为什么不管什么应用,只要有输入框的地方都能弹出它?

本文将从框架和流程角度出发,试图对Android的GUI系统做一个简要但全面的介绍,希望借此能够帮助大家找到上述问题的答案。

所有的内容可以浓缩在下面这张图里,里面有很多的名称和概念我们需要事先解释一下:

1. Window, PhoneWindow 和 Activity

  • Activity 是 Android 应用的四大组件之一 (Activity, Service,  Content Provider,  Broadcast Receiver), 也是唯一一个与用户直接交互的组件。
  • Window 在 不同的地方有着不同的含义。在Activity里,Window 是一个抽象类,代表了一个矩形的不可见的容器,里面布局着若干个可视的区域(View). 每个Activity都会有一个Window类成员变量,mWindow. 而在WindowManagerService里,Window指的是WindowState对象,从图中可以看出,WindowState与一个 ViewRootImpl里的mWindow对象相对应。所以说,WindowManagerService里管理的Window其实是 Acitivity的ViewRoot。我们下面提到的Window,如果没有做特殊说明,均指的是WindowManagerService里的 ‘Window’ 概念,即一个特定的显示区域。从用户角度来看,Android是个多窗口的操作系统,不同尺寸的窗口区域根据尺寸,位置,z-order及是否透明等参数 叠加起来一起并最终呈现给用户。这些窗口既可以是来自一个应用,也可以来自与多个应用,这些窗口既可以显示在一个平面,也可以是不同的平面。总而言之,窗 口是有层次的显示区域,每个窗口在底层最终体现为一个个的矩形Buffer, 这些Buffer经过计算合成为一个新的Buffer,最终交付Display系统进行显示。为了辅助最后的窗口管理,Android定义了一些不同的窗 口类型:
    • 应用程序窗口 (Application Window): 包括所有应用程序自己创建的窗口,以及在应用起来之前系统负责显示的窗口。
    • 子窗口(Sub Window):比如应用自定义的对话框,或者输入法窗口,子窗口必须依附于某个应用窗口(设置相同的token)。
    • 系 统窗口(System Window): 系统设计的,不依附于任何应用的窗口,比如说,状态栏(Status Bar), 导航栏(Navigation Bar), 壁纸(Wallpaper), 来电显示窗口(Phone),锁屏窗口(KeyGuard), 信息提示窗口(Toast), 音量调整窗口,鼠标光标等等。
  • PhoneWindow 是Activity Window的扩展,是为手机或平板设备专门设计的一个窗口布局方案,就像大家在手机上看到的,一个PhoneWindow的布局大致如下:

2. View, DecorView, ViewGroup, ViewRoot

View 是一个矩形的可见区域。

ViewGroup 是一种特殊的View, 它可以包含其他View并以一定的方式进行布局。Android支持的布局有FrameLayout, LinearLayout, RelativeLayout 等。

DecorView 是FrameLayout的子类,FrameLayout 也叫单帧布局,是最简单的一种布局,所有的子View在垂直方向上按照先后顺序依次叠加,如果有重叠部分,后面的View将会把前面的View挡住。我们 经常看到的弹出框,把后面的窗口挡住一部分,就是用的FrameLayout布局。Android的窗口基本上用的都是FrameLayout布局, 所以DecorView也就是一个Activity Window的顶级View, 所有在窗口里显示的View都是它的子View.

ViewRoot . 我们可以定义所有被addView()调用的View是ViewRoot, 因为接口将会生成一个ViewRootImpl 对象,并保存在WindowManagerGlobal的mRoots[] 数组里。一个程序可能有很多了ViewRoot(只要多次调用addView()), 在WindowManagerService端看来,就是多个Window。但在Activity的默认实现里,只有mDecorView 通过addView 添加到WindowManagerService里( 见如下代码)

复制代码
 //frameworks/base/core/java/android/app/Activity.java
 void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
  }
复制代码

 

因此一般情况下,我们可以说,一个应用可以有多个Activity,每个 Activity 一个Window(PhoneWindow), 每个Window 有一个DecorView, 一个ViewRootImpl, 对应在WindowManagerService 里有一个Window(WindowState).

3. ViewRootImple,  WindowManagerImpl,  WindowManagerGlobals

WindowManagerImpl: 实现了WindowManager 和 ViewManager的接口,但大部分是调用WindowManagerGlobals的接口实现的。

WindowManagerGlobals: 一个SingleTon对象,对象里维护了三个数组:

  • mRoots[ ]: 存放所有的ViewRootImpl
  • mViews[ ]: 存放所有的ViewRoot
  • mParams[ ]: 存放所有的LayoutParams.

同时,它还维护了两个全局IBinder对象,用于访问WindowManagerService 提供的两套接口:

  • IWindowManager:  主要接口是OpenSession(), 用于在WindowManagerService 内部创建和初始化Session, 并返回IBinder对象。
  • ISession:  是Activity Window与WindowManagerService 进行对话的主要接口.

ViewRootImpl:

ViewRootImpl 在整个Android的GUI系统中占据非常重要的位置,如果把Activity和View 看作 ‘MVC’ 中的V, 把各种后台服务看作Modal,ViewRootImpl 则是’MVC’ 中的’C’ – Controller. Controller在MVC架构里承担着承上启下的作用,一般来说它的逻辑最为复杂。从下图可以看到,ViewRootImpl 与 用户输入系统(接收用户按键,触摸屏输入), 窗口系统(复杂窗口的布局,刷新,动画),显示合成系统(包括定时器Choreograph, SurfaceFlinger), 乃至Audio系统(音效输出)等均有密切的关联。研究ViewRootImpl 是研究Android整个窗口系统的核心和切入点,我们将在后面详细讨论ViewRootImpl的实现和作用。

三 者( ViewRootImpl, WindowManagerImpl, WindowManagerGlobal) 都存在于应用(有Activity)的进程空间里,一个Activity对应一个WindowManagerImpl, 一个DecorView(ViewRoot),以及一个ViewRootImpl (上面说过,实际一个Activity只有一个DecorView),而WindowManagerGlobals是一个全局对象,一个应用永远只有一 个。

注意的是,在某些情况下,一个应用可能会有几个ViewRootImpl对象,比如说ANR是弹出的对话框,或是网页里面一个视频窗口 (SurfaceView), 在WindowManagerService看来,它们也是一个窗口。同时,SystemServer的进程空间也有自己的 WindowManagerGlobals 和若干个ViewRoot, 因为WindowManagerService 内部也会管理某些系统窗口,如手机顶部的StatusBar, 手机底部的NavigationBar, 以及 锁屏(KeyGuard)窗口,这些窗口不属于某个特定的Activity。

4. WindowManager, WindowManagerService 和 WindowManagerPolicyService

WindowManager:  是一个接口类,定义了一些接口来管理Acitivity里的窗口。WindowManager 是Android应用进程空间里的一个对象,不提供IPC服务。

WindowManagerService: 是SystemServer进程里的一个Service,它的主要功能有

  • 窗 口的显示刷新。这里的’Window’ 其实是ViewRoot, 和上面WindowManager管理的’Window’ 是不一样的,前者是实实在在要进行显示的‘窗口’, 而后者只是一个View的容器,并不会显示出来。大部分情况下,Android同时只有一个Activity工作,但这并不意思着只有一个Window被 显示,Android可能会同时显示来自相同或不同应用的多个Window,比如说,屏幕的上方有一个状态栏,最下方有一个导航栏,有时会弹出一些对话 框,背景可能会显示墙纸,在应用启动过程中,会有动画效果,这个时候两个Activity的窗口会有所变形且同时显示出来,这一切都需要 WindowManager来控制何时,何地,以何种方式将所有的窗口整合在一起显示。
  • 预处理用户输入时间(GlobalKey? SystemKey), 并分发给合适的窗口进行处理。
  • 输出显示(Display)管理。包括WifiDisplay.

WindowManagerService 是Android Framework里最为庞大复杂的模块之一,我们后面会从各个方面对它进行尽可能详细的分析。

5. Token, WindowToken, AppWindowToken, ApplicationToken, appToken

Token在英语中表示标记,信物的意思,在代码中,有点类似Handle,Cookie, ID, 用来标识某个特定的对象。在Android的窗口系统中,有很多的’Token’, 它们代表着不同的含义。

WindowToken: 是在WindowManagerService 中定义的一个基类,顾名思义,它是用来标识某一个窗口。和下面的appWindowToken相比, 它不属于某个特定的Activity,  由WindowManagerService 生成的窗口均属于这种类型。

appWindowToken: 顾名思义,它是用来标识app, 跟准确的说法,是用来标识某个具体的Activity.

ApplicationToken: 指的是ActivityRecord 类里的Token子类。appWindowToken里的appToken也就是它。

appToken: 和applicationToken是一个意思。

下 图描绘了各个Token之间的关系。一个Token下面带一个WindowList队列,里面存放着隶属与这个Token的所有窗口。当一个Window 加入WindowManagerService 管理时,必须指定他的Token值,WindowManagerService维护着一个Token与WindowState的键值Hash表。

通过 ‘dumpsys window tokens’ 我们可以列出WindowManagerService当前所有的Token 和 窗口。比如,

复制代码
WINDOW MANAGER TOKENS (dumpsys window tokens)
  All tokens:
  WindowToken{4ea639c4 null}: //token = NULL
    windows=[Window{4ea7670c u0 Application Not Responding: jackpal.androidterm}, Window{4ea63a08 u0 Keyguard}]
    windowType=-1 hidden=false hasVisible=true
  AppWindowToken{4eb29760 token=Token{4eb289d4 ActivityRecord{4ea87a20 u0 com.android.launcher/com.android.launcher2.Launcher}}}: //Launcher2
    windows=[Window{4ea837c8 u0 com.android.launcher/com.android.launcher2.Launcher}]
    windowType=2 hidden=true hasVisible=true
    ...
  WindowToken{4eb1fd48 android.os.BinderProxy@4eae8a5c}:
    windows=[Window{4ea92b78 u0 PopupWindow:4ea0240c}]  //对话框
    windowType=-1 hidden=false hasVisible=false
  AppWindowToken{4eb5d6c0 token=Token{4ea35074 ActivityRecord{4ea68590 u0 jackpal.androidterm/.Term}}}:
    windows=[Window{4eb314e4 u0 jackpal.androidterm/jackpal.androidterm.Term}]
    windowType=2 hidden=false hasVisible=true
    app=true
复制代码

 

6. Surface, Layer 和 Canvas, SurfaceFlinger, Region, LayerStack

在Android中,Window与Surface一一对应。 如果说Window关心的是层次和布局,是从设计者角度定义的类,Surface则从实现角度出发,是工程师关系和考虑的类。Window的内容是变化 的,Surface需要有空间来记录每个时刻Window的内容。在Android的SurfaceFlinger实现里,通常一个Surface有两块 Buffer, 一块用于绘画,一块用于显示,两个Buffer按照固定的频率进行交换,从而实现Window的动态刷新。

Layer是SurfaceFlinger 进行合成的基本操作单元。Layer在应用请求创建Surface的时候在SurfaceFlinger内部创建,因此一个Surface对应一个 Layer, 但注意,Surface不一定对应于Window,Android中有些Surface并不跟某个Window相关,而是有程序直接创建,比如说 StrictMode, 一块红色的背景,用于提示示Java代码中的一些异常, 还有SurfaceView, 用于显示有硬件输出的视频内容等。

当多个Layer进行合成的时候,并不是整个Layer的空间都会被完全显示,根据这个Layer最终的显示效果,一个Layer可以被划分成很多的Region, Android SurfaceFlinger 定义了以下一些Region类型:

  •  TransparantRegion: 完全透明的区域,在它之下的区域将被显示出来。
  •  OpaqueRegion: 完全不透明的区域,是否显示取决于它上面是否有遮挡或是否透明。
  •  VisibleRegion: 可见区域,包括完全不透明无遮挡区域或半透明区域。 visibleRegion = Region – above OpaqueRegion.
  •  CoveredRegion: 被遮挡区域,在它之上,有不透明或半透明区域。
  •  DirtyRegion: 可见部分改变区域,包括新的被遮挡区域,和新的露出区域。

Android 系统支持多种显示设备,比如说,输出到手机屏幕,或者通过WiFi 投射到电视屏幕。Android用Display类来表示这样的设备。不是所有的Layer都会输出到所有的Display, 比如说,我们可以只将Video Layer投射到电视, 而非整个屏幕。LayerStack 就是为此设 计,LayerStack 是一个Display 对象的一个数值, 而类Layer里也有成员变量mLayerStack, 只有两者的mLayerStack 值相同,Layer才会被输出到给该Display设备。所以LayerStack 决定了每个Display设备上可以显示的Layer数目。

SurfaceFlinger的工作内容,就是定期检查所有Layer的参数更新(LayerStack等),计算新的DirtyRegion, 然后将结果推送给底层显示驱动进行显示。这里面有很多的细节,我们将在另外的章节专门研究。

上面描述的几个概念,均是针对于显示这个层面,更多是涉及到中下层模块,应用层并不参与也无需关心。对于应用而言,它关心的是如何将内容画出来。Canvas 是Java层定义的一个类,它对应与Surface上的某个区域并提供了很多的2D绘制函数(借助于底层的Skia或OpenGL)。应用只需通过 LockCanvas() 来获取一个Canvas对象,并调用它的绘画方法,然后 unLockCanvasAndPost()来通知底层将更新内容进行显示。当然,并不是所有应用程序都需要直接操作Canva, 事实上只有少量应用需要直接操作Canvas, Android提供了很多封装好的控件 Widget,应用只需提供素材,如文字,图片,属性等等,这些控件会调用Canvas提供的接口帮用户完成绘制工作。

 

7. SurfaceFlinger, HWComposer, OpenGL 和 Display

SurfaceFlinger 是一个独立的Service, 它接收所有Window的Surface作为输入,根据ZOrder, 透明度,大小,位置等参数,计算出每个Surface在最终合成图像中的位置,然后交由HWComposer或OpenGL生成最终的显示Buffer, 然后显示到特定的显示设备上。

HWComposer 是 Andrid 4.0后推出的新特性,它定义一套HAL层接口,然后各个芯片厂商根据各种硬件特点来实现。它的主要工作是将SurfaceFlinger计算好的 Layer的显示参数最终合成到一个显示Buffer上。注意的是,Surface Flinger 并非是HWComposer的唯一输入,有的Surface 不由Android的WindowManager 管理,比如说摄像头的预览输入Buffer, 可以有硬件直接写入,然后作为HWComposer的输入之一与SurfaceFlinger的输出做最后的合成。

OpenGL 是一个2D/3D图形库,需要底层硬件(GPU)和驱动的支持。在Android 4.0后,它取代Skia成为Android 的2D 绘图图形库,大部分的控件均改用它来实现,应用程序也可以直接调用OpenGl函数来实现复杂的图形界面。

Display 是Android 对输出显示设备的一个抽象,传统的Display 设备是手机上的LCD屏,在Andrid 4.1 后,Android 对SurfaceFlinger 进行了大量的改动,从而支持其他外部输入设备,比如HDMI, Wifi Display 等等。Display的输入是根据上面的LayerStack值进行过滤的所有Window的Surface, 输出是和显示设备尺寸相同的Buffer, 这个Buffer 最终送到了硬件的FB设备,或者HDMI设备,或者远处的Wifi Display Sink设备进行显示。输入到输出这条路径上有SurfaceFlinger, OpenGL 和 HWComposer。

 

有了上述概念的解析,对Android的GUI 系统应该有了一些模糊的认识,接下来我们将按下面的顺序将一步步深入其中的细节。

1. 图解Android – Android GUI 系统 (2) – 窗口管理系统

在这一章里,我们将试图解答下面几个问题:

  • Surface是什么时候,被谁创建的?
  • 窗口是何时,何地被绘制和更新?
  • 窗口的显示和隐藏是如何控制的?
  • 窗口的切换动画是怎样实现的?
  • More …

2. 图解Android – Android GUI 系统 (3) – Surface Flinger (TBD)

在这一章里,我们将探讨:

  • Surface 后面的Buffer 何时何地创建和销毁,如何管理。
  • 多个Surface是如何合成在一起。
  • 怎样实现多屏输出?
  • More …

3. 图解Android – Android GUI 系统 (4) – Activity的生命周期

  • Acitivity的生命周期是怎样的?何时创建,何时显示,何时隐藏,何时销毁?
  • 什么是UI thread? 为什么说不要在Andrid 的UI thread里做太多的事情?
  • More …

4. 图解Android – Android GUI 系统 (5) – Android的用户输入处理

  • 按键是怎么从底层driver 传递到具体的窗口进行处理的?
  • 全局键是怎么处理的?
  • ANR是如何产生的?又如何处理?
  • 等等… 

[转载]Android UI开发第三十六篇——使用Volley加载图片列表 - 张兴业 - 博客园

mikel阅读(1104)

[转载]Android UI开发第三十六篇——使用Volley加载图片列表 – 张兴业 – 博客园.

    Android开发者可能会使用Universal Image Loader或者Square`s newer Picasso这些第三方的库去处理图片的加载,那么Volley是怎么加载图片列表的呢,这一篇文章会介绍使用Volley加载图片列表。

    在Google I/O介绍Volley的时候,Ficus Kirpatrick讲了很多关于Volley如何的有助于图片加载。但是,你会发现在Volley作为图片加载解决方案的时候,Volley虽然自己处理了L2的缓存,可是它没有处理L1的缓存。它给出了ImageCache接口类,让开发者自己根据自己的需求实现自己L1缓存,这样你可以实现LRU、FIFO等算法的缓存。

     Volley加载图片时需要使用ImageLoader,ImageLoader初始化一次。展示图片的View可以使用ImageView或者Volley自定义的NetworkImageView。NetworkImageView可以使用setUrl方法获取图片。下面为两种View加载图片的方式。

 

使用ImageView:

 

public class VolleyImageViewListAdapter extends BaseAdapter{

	private static final String TAG = "VolleyListAdapter";

	private Context mContext;
	private String[] urlArrays;
	private ImageLoader mImageLoader;

	public VolleyImageViewListAdapter(Context context, String []url) {
		this.mContext = context;
		urlArrays = url;

		RequestQueue mQueue = Volley.newRequestQueue(context);  
		mImageLoader = new ImageLoader(mQueue, new BitmapCache()); 
	}

	@Override
	public int getCount() {
		return urlArrays.length;
	}

	@Override
	public Object getItem(int position) {
		return position;
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {

		ViewHolder viewHolder = null;
		if (convertView == null) {
			convertView = LayoutInflater.from(mContext).inflate(
					R.layout.volley_image_list_item, null);
			viewHolder = new ViewHolder();
			viewHolder.mTextView = (TextView) convertView
					.findViewById(R.id.tv_tips);
			viewHolder.mImageView = (ImageView) convertView
					.findViewById(R.id.iv_image);
			convertView.setTag(viewHolder);
		} else {
			viewHolder = (ViewHolder) convertView.getTag();
		}
		String url = "";
		url = urlArrays[position % urlArrays.length];

		viewHolder.mTextView.setText(position+"|"+urlArrays.length) ;
	    ImageListener listener = ImageLoader.getImageListener(viewHolder.mImageView, android.R.drawable.ic_menu_rotate, android.R.drawable.ic_delete);  
        mImageLoader.get(url, listener);  
		return convertView;
	}

	static class ViewHolder {
		TextView mTextView;
		ImageView mImageView;
	}

	public class BitmapCache implements ImageCache {  
	    private LruCache<String, Bitmap> mCache;  

	    public BitmapCache() {  
	        int maxSize = 10 * 1024 * 1024;  
	        mCache = new LruCache<String, Bitmap>(maxSize) {  
	            @Override  
	            protected int sizeOf(String key, Bitmap value) {  
	                return value.getRowBytes() * value.getHeight();  
	            }  

	        };  
	    }  

	    @Override  
	    public Bitmap getBitmap(String url) {  
	        return mCache.get(url);  
	    }  

	    @Override  
	    public void putBitmap(String url, Bitmap bitmap) {  
	        mCache.put(url, bitmap);  
	    }  

	}  
}

使用NetworkImageView:

 

 

public class VolleyListAdapter extends BaseAdapter{

	private static final String TAG = "VolleyListAdapter";

	private Context mContext;
	private String[] urlArrays;	

	public VolleyListAdapter(Context context, String []url) {
		this.mContext = context;
		urlArrays = url;
	}

	@Override
	public int getCount() {
		return urlArrays.length;
	}

	@Override
	public Object getItem(int position) {
		return position;
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {

		ViewHolder viewHolder = null;
		if (convertView == null) {
			convertView = LayoutInflater.from(mContext).inflate(
					R.layout.volley_list_item, null);
			viewHolder = new ViewHolder();
			viewHolder.mTextView = (TextView) convertView
					.findViewById(R.id.tv_tips);
			viewHolder.mImageView = (NetworkImageView) convertView
					.findViewById(R.id.iv_image);
			convertView.setTag(viewHolder);
		} else {
			viewHolder = (ViewHolder) convertView.getTag();
		}
		String url = "";
		url = urlArrays[position % urlArrays.length];

		viewHolder.mTextView.setText(position+"|"+urlArrays.length) ;	
		viewHolder.mImageView.setImageUrl(url, ImageCacheManager.getInstance().getImageLoader());

		return convertView;
	}

	static class ViewHolder {
		TextView mTextView;
		NetworkImageView mImageView;
	}
}

效果图:

 

 

 

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