本文章的目的是通过分析monkeyrunner是如何实现截屏来作为一个例子尝试投石问路为下一篇文章做准备,往下一篇文章本人有意分析下monkeyrunner究竟是如何和目标测试机器通信的,所以最好的办法本人认为是先跟踪一个调用示例从高层到底层进行分析,本人以前分析操作系统源代码的时候就是先从用户层的write这个api入手,然后一路打通到vfs文件系统层,到设备驱动层的,其效果比单纯的理论描述更容易理解和接受。

在整个代码分析过程中会设计到以下的库,希望想动手分析的同学们准备好源码:

  • monkeyrunner
  • chimpchat
  • ddmlib
想来google对自动化测试框架的命名很有趣,有叫猴子(Monkey)的,也有叫大猩猩(Chimp)的。

1. 究竟是哪个禽兽动了我的截图?

首先我们先看takeSnapshot的入口函数是在MonkeyDevice这个class里面的(因为所有的代码都是反编译的,所以代码排版方便可能有点别扭). MonkeyDevice.class takeSnapshot():

/*     */   @MonkeyRunnerExported(doc="Gets the device's screen buffer, yielding a screen capture of the entire display.", returns="A MonkeyImage object (a bitmap wrapper)")
/* */ public MonkeyImage takeSnapshot()
/* */ {
/* 92 */ IChimpImage image = this.impl.takeSnapshot();
/* 93 */ return new MonkeyImage(image);
/* */ }

这是我们的monkeyrunner测试脚本尝试去截屏的入口函数,所做的事情大概如下

  • 调用MonkeyDevice的成员变量impl的takeSnapshot()函数(往下我们会看impl是怎么传进来的)去获得截图并赋予给IChimpImage的变量
  • 把截图转换成MonkeyImage并返回给用户
这里重点是impl这个变量是怎么回事,它是在MonkeyDevice的构造函数中被赋值的:
public MonkeyDevice(IChimpDevice impl)
/* */ {
/* 75 */ this.impl = impl;
/* */ }

其中IChimpDevice是一个接口,里面定义好了MonkeyDevice需要和目标测试机器通讯的规范:

public abstract interface IChimpDevice
{
public abstract ChimpManager getManager(); public abstract void dispose(); public abstract HierarchyViewer getHierarchyViewer(); public abstract IChimpImage takeSnapshot(); public abstract void reboot(@Nullable String paramString); public abstract Collection<String> getPropertyList(); public abstract String getProperty(String paramString); public abstract String getSystemProperty(String paramString); public abstract void touch(int paramInt1, int paramInt2, TouchPressType paramTouchPressType); public abstract void press(String paramString, TouchPressType paramTouchPressType); public abstract void press(PhysicalButton paramPhysicalButton, TouchPressType paramTouchPressType); public abstract void drag(int paramInt1, int paramInt2, int paramInt3, int paramInt4, int paramInt5, long paramLong); public abstract void type(String paramString); public abstract String shell(String paramString); public abstract String shell(String paramString, int paramInt); public abstract boolean installPackage(String paramString); public abstract boolean removePackage(String paramString); public abstract void startActivity(@Nullable String paramString1, @Nullable String paramString2, @Nullable String paramString3, @Nullable String paramString4, Collection<String> paramCollection, Map<String, Object> paramMap, @Nullable String paramString5, int paramInt); public abstract void broadcastIntent(@Nullable String paramString1, @Nullable String paramString2, @Nullable String paramString3, @Nullable String paramString4, Collection<String> paramCollection, Map<String, Object> paramMap, @Nullable String paramString5, int paramInt); public abstract Map<String, Object> instrument(String paramString, Map<String, Object> paramMap); public abstract void wake(); public abstract Collection<String> getViewIdList(); public abstract IChimpView getView(ISelector paramISelector); public abstract IChimpView getRootView(); public abstract Collection<IChimpView> getViews(IMultiSelector paramIMultiSelector);
}

MonkeyDevice的构造函数运用了面向对象的多态技术把某一个实现了IChimpDevice接口的对象赋予给成员函数IChimpDevice类型的impl成员变量,那么“某一个设备对象”又是在哪里传进来的呢?

在我们的测试代码中我们很清楚一个MonkeyDevice对象的初始化都不是直接调用构造函数实现的,而是通过调用MonkeyRunner实例的waitForConnection实现的,代码如下:
/*     */   @MonkeyRunnerExported(doc="Waits for the workstation to connect to the device.", args={"timeout", "deviceId"}, argDocs={"The timeout in seconds to wait. The default is to wait indefinitely.", "A regular expression that specifies the device name. See the documentation for 'adb' in the Developer Guide to learn more about device names."}, returns="A ChimpDevice object representing the connected device.")
/* */ public static MonkeyDevice waitForConnection(PyObject[] args, String[] kws)
/* */ {
/* 64 */ ArgParser ap = JythonUtils.createArgParser(args, kws);
/* 65 */ Preconditions.checkNotNull(ap);
/* */ long timeoutMs;
/* */ try
/* */ {
/* 69 */ double timeoutInSecs = JythonUtils.getFloat(ap, 0);
/* 70 */ timeoutMs = (timeoutInSecs * 1000.0D);
/* */ } catch (PyException e) {
/* 72 */ timeoutMs = Long.MAX_VALUE;
/* */ }
/* */
/* 75 */ IChimpDevice device = chimpchat.waitForConnection(timeoutMs, ap.getString(1, ".*"));
/* */
/* 77 */ MonkeyDevice chimpDevice = new MonkeyDevice(device);
/* 78 */ return chimpDevice;
/* */ }

该函数所做的事情就是根据用户输入的函数等待连接上一个测试设备然后返回设备并赋值给上面的MonkeyDevice中的impl成员变量。返回的device是通过chimpchat.jar这个库里面的com.android.chimpchat.ChimpChat模块中的waitForConnection方法实现的:

/*     */   public IChimpDevice waitForConnection(long timeoutMs, String deviceId)
/* */ {
/* 91 */ return this.mBackend.waitForConnection(timeoutMs, deviceId);
/* */ }

这里面又调用了ChimpChat这个类的成员变量mBackend的waitForConnection方法来获得设备,这个变量是在ChimpChat的构造函数初始化的:

/*     */   private ChimpChat(IChimpBackend backend)
/* */ {
/* 39 */ this.mBackend = backend;
/* */ }

那么这个backend参数又是从哪里传进来的呢?也就是说ChimpChat的构造函数是在哪里被调用的呢?其实就是在ChimpChat里面的getInstance的两个重载方法里面:

/*     */   public static ChimpChat getInstance(Map<String, String> options)
/* */ {
/* 48 */ sAdbLocation = (String)options.get("adbLocation");
/* 49 */ sNoInitAdb = Boolean.valueOf((String)options.get("noInitAdb")).booleanValue();
/* */
/* 51 */ IChimpBackend backend = createBackendByName((String)options.get("backend"));
/* 52 */ if (backend == null) {
/* 53 */ return null;
/* */ }
/* 55 */ ChimpChat chimpchat = new ChimpChat(backend);
/* 56 */ return chimpchat;
/* */ }
/* */
/* */
/* */
/* */ public static ChimpChat getInstance()
/* */ {
/* 63 */ Map<String, String> options = new TreeMap();
/* 64 */ options.put("backend", "adb");
/* 65 */ return getInstance(options);
/* */ }

从代码可以看到backend最终是通过createBackendByName这个方法进行初始化的,那么我们看下该方法做了什么事情:

/*     */   private static IChimpBackend createBackendByName(String backendName)
/* */ {
/* 77 */ if ("adb".equals(backendName)) {
/* 78 */ return new AdbBackend(sAdbLocation, sNoInitAdb);
/* */ }
/* 80 */ return null;
/* */ }

其实它最终实例化的就是ChimpChat.jar库里面的AdbBackend这个Class。其实这个类就是封装了adb的一个wrapper类。

到了现在我们终于定位到ChimpChat这个类里面的成员变量mBackend实际上就是AdbBackend了。那么我们就要去看下它里面的waitForConnection方法究竟是如何获得一个接口是IChimpDevice的device的(也就是我们文章开头描述的impl这个MonkeyDevice的成员变量).
/*     */   public IChimpDevice waitForConnection(long timeoutMs, String deviceIdRegex)
/* */ {
/* */ do {
/* 119 */ IDevice device = findAttachedDevice(deviceIdRegex);
/* */
/* 121 */ if ((device != null) && (device.getState() == IDevice.DeviceState.ONLINE)) {
/* 122 */ IChimpDevice chimpDevice = new AdbChimpDevice(device);
/* 123 */ this.devices.add(chimpDevice);
/* 124 */ return chimpDevice;
/* */ }
/* */ try
/* */ {
/* 128 */ Thread.sleep(200L);
/* */ } catch (InterruptedException e) {
/* 130 */ LOG.log(Level.SEVERE, "Error sleeping", e);
/* */ }
/* 132 */ timeoutMs -= 200L;
/* 133 */ } while (timeoutMs > 0L);
/* */
/* */
/* 136 */ return null;
/* */ }

方法首先通过findAttachedDevice方法获得目标设备(其实该方法里面所做的事情可以类比直接执行命令"adb devices",下文有更详细的描述), 如果该设备存在且是ONLINE状态(关于各总状态的描述请查看上一篇文章《adb概览及协议参考》)的话就去实例化一个AdbChimpDevice设备对象并返回。

经过以上的一大堆描述,最终我们的目的就是确定文章开头的takeSnapshot入口函数所用到的获取截图的device(impl)究竟是什么device,这里我们终于确定了就是ChimChat.jar这个库里面的AdbChimpDevice这个设备。
IChimpImage image = this.impl.takeSnapshot();

2. 大猩猩是如何通过AdbChimpDevice进行怒吼传递信息的

其实chimpchat这个大猩猩并不是最终处理我们的截图的库,细究下去会发现AdbChimpDevice其实只是相当于一个信息的传递着的角色,只是过程中加入了自己的一些特有信息而已。这就好比大猩猩在原始森林中没有通讯设备,只能使用原始的怒吼来通知伙伴有危险等情况了。
既然我们已经定位到截图设备是AdbChimpDevice,那么我们就去看看它里面的tapeSnapshot方法是怎么实现的:
/*     */   public IChimpImage takeSnapshot()
/* */ {
/* */ try {
/* 209 */ return new AdbChimpImage(this.device.getScreenshot());
/* */ } catch (TimeoutException e) {
/* 211 */ LOG.log(Level.SEVERE, "Unable to take snapshot", e);
/* 212 */ return null;
/* */ } catch (AdbCommandRejectedException e) {
/* 214 */ LOG.log(Level.SEVERE, "Unable to take snapshot", e);
/* 215 */ return null;
/* */ } catch (IOException e) {
/* 217 */ LOG.log(Level.SEVERE, "Unable to take snapshot", e); }
/* 218 */ return null;
/* */ }

方法代码很少,一眼就可以看到它是调用了自己的成员变量device的getScreenshot这个方法获得截图然后转换成AdbChimpImage,至于怎么转换的我们不需要去管它,无非就是不同的类如何一层层继承,最终如何通过多态继承机制进行转换而已。

这里我们关键是先去找到成员变量device又是什么设备,它里面的截图又是怎么回事。
继续分析代码可以看到该device变量也是在AdbChimpDevice的构造函数中进行定义的:
/*     */   public AdbChimpDevice(IDevice device)
/* */ {
/* 70 */ this.device = device;
/* 71 */ this.manager = createManager("127.0.0.1", 12345);
/* */
/* 73 */ Preconditions.checkNotNull(this.manager);
/* */ }

那么我们一如既往的需要找到该参数的device是在哪里传进来的。相信大家还记得上一章节描述的AdbBackend是如何实例化AdbChimpDevice的,在实例化之前会调用一个findAttachedDevice的方法的先获得一个实现了IDevice接口的对象,然后传给这里的AdbChimpDevice构造函数进行实例化的。

/*     */   public IChimpDevice waitForConnection(long timeoutMs, String deviceIdRegex)
/* */ {
/* */ do {
/* 119 */ IDevice device = findAttachedDevice(deviceIdRegex);
/* */
/* 121 */ if ((device != null) && (device.getState() == IDevice.DeviceState.ONLINE)) {
/* 122 */ IChimpDevice chimpDevice = new AdbChimpDevice(device);
/* 123 */ this.devices.add(chimpDevice);
/* 124 */ return chimpDevice;
/* */ }
/* */ try
/* */ {
/* 128 */ Thread.sleep(200L);
/* */ } catch (InterruptedException e) {
/* 130 */ LOG.log(Level.SEVERE, "Error sleeping", e);
/* */ }
/* 132 */ timeoutMs -= 200L;
/* 133 */ } while (timeoutMs > 0L);
/* */
/* */
/* 136 */ return null;
/* */ }

那么我们就需要分析下findAttachedDevice这个方法究竟找到的是怎么样的一个IDevice对象了,在分析之前先要注意这里的IDevice接口定义的都是一些底层的操作目标设备的接口方法,由此可知我们已经慢慢接近真相了。以下是其代码片段:

/*     */ public abstract interface IDevice extends IShellEnabledDevice
/* */ {
/* */ public static final String PROP_BUILD_VERSION = "ro.build.version.release";
/* */ public static final String PROP_BUILD_API_LEVEL = "ro.build.version.sdk";
/* */ public static final String PROP_BUILD_CODENAME = "ro.build.version.codename";
/* */ public static final String PROP_DEVICE_MODEL = "ro.product.model";
/* */ public static final String PROP_DEVICE_MANUFACTURER = "ro.product.manufacturer";
/* */ public static final String PROP_DEVICE_CPU_ABI = "ro.product.cpu.abi";
/* */ public static final String PROP_DEVICE_CPU_ABI2 = "ro.product.cpu.abi2";
/* */ public static final String PROP_BUILD_CHARACTERISTICS = "ro.build.characteristics";
/* */ public static final String PROP_DEBUGGABLE = "ro.debuggable";
/* */ public static final String FIRST_EMULATOR_SN = "emulator-5554";
/* */ public static final int CHANGE_STATE = 1;
/* */ public static final int CHANGE_CLIENT_LIST = 2;
/* */ public static final int CHANGE_BUILD_INFO = 4;
/* */ @Deprecated
/* */ public static final String PROP_BUILD_VERSION_NUMBER = "ro.build.version.sdk";
/* */ public static final String MNT_EXTERNAL_STORAGE = "EXTERNAL_STORAGE";
/* */ public static final String MNT_ROOT = "ANDROID_ROOT";
/* */ public static final String MNT_DATA = "ANDROID_DATA";
/* */
/* */ @NonNull
/* */ public abstract String getSerialNumber();
/* */
/* */ @Nullable
/* */ public abstract String getAvdName();
/* */
/* */ public abstract DeviceState getState();
/* */
/* */ public abstract java.util.Map<String, String> getProperties();
/* */
/* */ public abstract int getPropertyCount();
/* */
/* */ public abstract String getProperty(String paramString);
/* */
/* */ public abstract boolean arePropertiesSet();
/* */
/* */ public abstract String getPropertySync(String paramString) throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException;
/* */
/* */ public abstract String getPropertyCacheOrSync(String paramString) throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException;
/* */
/* */ public abstract boolean supportsFeature(@NonNull Feature paramFeature);
/* */
/* */ public static enum Feature
/* */ {
/* 53 */ SCREEN_RECORD,
/* 54 */ PROCSTATS;
/* */
/* */ private Feature() {}
/* */ }
/* */
/* 59 */ public static enum HardwareFeature { WATCH("watch");
/* */
/* */ private final String mCharacteristic;
/* */
/* */ private HardwareFeature(String characteristic) {
/* 64 */ this.mCharacteristic = characteristic;
/* */ }

我们继续看findAttachedDevice的源码:

/*     */   private IDevice findAttachedDevice(String deviceIdRegex)
/* */ {
/* 101 */ Pattern pattern = Pattern.compile(deviceIdRegex);
/* 102 */ for (IDevice device : this.bridge.getDevices()) {
/* 103 */ String serialNumber = device.getSerialNumber();
/* 104 */ if (pattern.matcher(serialNumber).matches()) {
/* 105 */ return device;
/* */ }
/* */ }
/* 108 */ return null;
/* */ }

简单明了,一个循环所有列出来的(好比"adb devices -l"命令)所有设备,找到想要的那个。这里的AdbChimDevice里面的this.bridge成员变量其实代表的就是一个通过socket连接到adb服务器的一个adb客户端,这就是为什么我之前说chimpchat的AdbBackend事实上就是adb的一个wrapper。

往下我们继续跟踪看这个adb的wrapper是如何getDevices的,代码跳转到ddmlib这个库里面的AndroidDebugBridge这个class:
/*      */   public IDevice[] getDevices()
/* */ {
/* 484 */ synchronized (sLock) {
/* 485 */ if (this.mDeviceMonitor != null) {
/* 486 */ return this.mDeviceMonitor.getDevices();
/* */ }
/* */ }

里面调用了AndroidDebugBridge的成员变量mDeviceMonitor的getDevices函数,那么我们看下这个成员变量究竟是定义成什么类型的:

/*      */   private DeviceMonitor mDeviceMonitor;

然后我们再跑到该DeviceMonitor类中去查看getDevices这个方法的代码:

/*     */   Device[] getDevices()
/* */ {
/* 131 */ synchronized (this.mDevices) {
/* 132 */ return (Device[])this.mDevices.toArray(new Device[this.mDevices.size()]);
/* */ }
/* */ }

代码是取得成员函数mDevices的所有device列表然后返回,那么我们看下mDevices究竟是什么类型的列表:

/*  60 */   private final ArrayList<Device> mDevices = new ArrayList();

最终mDevices实际上是Device类型的一个列表,那么找到Device这个类就达到我们的目标了,就知道本章节开始的“return new AdbChimpImage(this.device.getScreenshot());”中的那个device究竟是什么device,也就是说知道去哪里去查找getScreenshot这个方法是在什么地方实现的了。

最终定位到com.android.ddmlib.Device这个类。 通过这个章节的分析可以看出来在chimpchat里面的AdbChimpDevice其实不是最终负责去获得截图的类,它只是作为一个信息重新包装的再分发的中转站而已。ddmlib才是最终处理我们的截图的库。
 

3. ddmlib库如何通过请求adb服务器读取FrameBuffer设备进行截图

好我们继续看Device这个类究竟是如何获得我们的截图的:
/*      */   public RawImage getScreenshot()
/* */ throws TimeoutException, AdbCommandRejectedException, IOException
/* */ {
/* 558 */ return AdbHelper.getFrameBuffer(AndroidDebugBridge.getSocketAddress(), this);
/* */ }

里面只有一行代码,通过调用工具类AdbHelper的getFramebBuffer方法来获得截图,其实这里单看名字getFrameBuffer就应该猜到MonkeyRunner究竟是怎么截图的了,不就是读取目标机器的FrameBuffer 设备的缓存嘛,至于FrameBuffer设备的详细解析请大家自行google了,这里你只需要知道它是一个可以映射到用户空间的代表显卡内容的一个设备,获得它就相当于获得当前的屏幕截图就足够了。

好,我们还是继续往下分析,看getFrameBuffer是怎么实现截屏的,定位到ddmlib的AdbHelper类:
/*     */   static RawImage getFrameBuffer(InetSocketAddress adbSockAddr, Device device)
/* */ throws TimeoutException, AdbCommandRejectedException, IOException
/* */ {
/* 272 */ RawImage imageParams = new RawImage();
/* 273 */ byte[] request = formAdbRequest("framebuffer:");
/* 274 */ byte[] nudge = { 0 };
/* */
/* */
/* */
/* */
/* 279 */ SocketChannel adbChan = null;
/* */ try {
/* 281 */ adbChan = SocketChannel.open(adbSockAddr);
/* 282 */ adbChan.configureBlocking(false);
/* */
/* */
/* */
/* 286 */ setDevice(adbChan, device);
/* */
/* 288 */ write(adbChan, request);
/* */
/* 290 */ AdbResponse resp = readAdbResponse(adbChan, false);
/* 291 */ if (!resp.okay) {
/* 292 */ throw new AdbCommandRejectedException(resp.message);
/* */ }
/* */
/* */
/* 296 */ byte[] reply = new byte[4];
/* 297 */ read(adbChan, reply);
/* */
/* 299 */ ByteBuffer buf = ByteBuffer.wrap(reply);
/* 300 */ buf.order(ByteOrder.LITTLE_ENDIAN);
/* */
/* 302 */ int version = buf.getInt();
/* */
/* */
/* 305 */ int headerSize = RawImage.getHeaderSize(version);
/* */
/* */
/* 308 */ reply = new byte[headerSize * 4];
/* 309 */ read(adbChan, reply);
/* */
/* 311 */ buf = ByteBuffer.wrap(reply);
/* 312 */ buf.order(ByteOrder.LITTLE_ENDIAN);
/* */
/* */
/* 315 */ if (!imageParams.readHeader(version, buf)) {
/* 316 */ Log.e("Screenshot", "Unsupported protocol: " + version);
/* 317 */ return null;
/* */ }
/* */
/* 320 */ Log.d("ddms", "image params: bpp=" + imageParams.bpp + ", size=" + imageParams.size + ", width=" + imageParams.width + ", height=" + imageParams.height);
/* */
/* */
/* */
/* 324 */ write(adbChan, nudge);
/* */
/* 326 */ reply = new byte[imageParams.size];
/* 327 */ read(adbChan, reply);
/* */
/* 329 */ imageParams.data = reply;
/* */ } finally {
/* 331 */ if (adbChan != null) {
/* 332 */ adbChan.close();
/* */ }
/* */ }
/* */
/* 336 */ return imageParams;
/* */ }

其实过程就是根据adb协议整合命令请求字串"framebuffer:",然后连接到adb服务器把请求发送到adb服务器,最终发送到设备上的adb守护进程通过读取framebuffer设备获得当前截图。

具体流程和adb协议详细解析请看本人上一篇文章:
转载请尊重原创作者 自主博客 微信服务号及扫描码 CSDN
天地会珠海分舵 http://techgogogo.com

服务号:TechGoGoGo扫描码

:

http://blog.csdn.net/zhubaitian
 
 
 
 
 
 
 
 

MonkeyRunner源码分析之-谁动了我的截图?的更多相关文章

  1. 安卓MonkeyRunner源码分析之工作原理架构图及系列集合

    花了点时间整理了下MonkeyRunner的工作原理图,请配合本人博客里面MonkeyRunner其他源码分析文章进行阅读.下面整理成相应系列列表方便大家阅读: MonkeyRunner源码分析之-谁 ...

  2. 安卓MonkeyRunner源码分析之启动

    在工作中因为要追求完成目标的效率,所以更多是强调实战,注重招式,关注怎么去用各种框架来实现目的.但是如果一味只是注重招式,缺少对原理这个内功的了解,相信自己很难对各种框架有更深入的理解. 从几个月前开 ...

  3. 安卓MonkeyRunner源码分析之与Android设备通讯方式

    如前文<谁动了我的截图?--Monkeyrunner takeSnapshot方法源码跟踪分析>所述,本文主要会尝试描述android的自动化测试框架MonkeyRunner究竟是如何和目 ...

  4. MonkeyRunner源码分析之工作原理图-attach

    花了点时间整理了下MonkeyRunner的工作原理图: Item Description Warning Author 天地会珠海分舵 转载请注明出处! Blog Address http://bl ...

  5. MonkeyRunner源码分析之工作原理图

    http://www.androidchina.net/1315.html

  6. Monkey源码分析之事件源

    上一篇文章<Monkey源码分析之运行流程>给出了monkey运行的整个流程,让我们有一个概貌,那么往后的文章我们会尝试进一步的阐述相关的一些知识点. 这里先把整个monkey类的结构图给 ...

  7. Appium Server源码分析之作为Bootstrap客户端

    Appium Server拥有两个主要的功能: 它是个http服务器,它专门接收从客户端通过基于http的REST协议发送过来的命令 他是bootstrap客户端:它接收到客户端的命令后,需要想办法把 ...

  8. 安卓Monkey源码分析之运行流程

    在<MonkeyRunner源码分析之与Android设备通讯方式>中,我们谈及到MonkeyRunner控制目标android设备有多种方法,其中之一就是在目标机器启动一个monkey服 ...

  9. 老李推荐:第6章8节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-小结

    老李推荐:第6章8节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-小结   本章我们重点围绕处理网络过来的命令的MonkeySourceNetwork这个事 ...

随机推荐

  1. 使用js在新窗口中POST数据

    最近在做自己的博客,写文章的时候有一个预览功能,当时使用的是弹出框来进行预览,感觉体验不是很好. 然后想到了写邮件时候的新窗口预览,查了下126邮箱和qq邮箱的预览实现效果,以及各种谷歌百度stack ...

  2. android中怎么把自己须要的app启动图标集中到一个弹出框中

    先看效果图 这个是我们自己的apk点击之后的效果 下边是布局文件 activity_main.xml主布局文件 <LinearLayout xmlns:android="http:// ...

  3. Java学习路径:不走弯路,这是一条捷径

    1.如何学习编程? JAVA是一种平台.也是一种程序设计语言,怎样学好程序设计不只适用于JAVA,对C++等其它程序设计语言也一样管用.有编程高手觉得,JAVA也好C也好没什么分别,拿来就用.为什么他 ...

  4. Code Review中的几个提示

    原文:http://coolshell.cn/?p=1302  酷壳 Code Review中的几个提示 陈皓 Code Review应该是软件project最最有价值的一个活动,之前,本站发表过&l ...

  5. Codeforces Round #107 (Div. 2)---A. Soft Drinking

    Soft Drinking time limit per test 2 seconds memory limit per test 256 megabytes input standard input ...

  6. 树莓派的演奏音符3 -- LCD1602显示文章

    LCD1602它是低-cost输出装置.它具有体积小.简单的操作.低功耗优势.因此,在一些DIY 用它来输出关于产品的一些信息. 昨日在使用,尽管平局网上资源,但仍存在一些问题,确处理. 一.LCD1 ...

  7. Oracle to_char,to_date

    一.在oracle中,当想把字符串为‘2011-09-20 08:30:45’的格式转化为日期格式,我们可以使用oracle提供的to_date函数. sql语句为: SELECT to_date(' ...

  8. kd-tree注解 &amp; bzoj 2648 &amp; 2716 &amp; 3053 解决问题的方法

    [KD-TREE简介]于SYC1999大神"迷住"下一个.我开始接触这样的算法. 首先.这个概念大概能去百度百科.详细的实施.我在看RZZ的代码长大的. 我们能够想象在平面上有N个 ...

  9. char与byte差异

    很多人刚开始学习(包含I,我已经学会了一年多java该)肯会char和byte怀疑这两个数据类型,相互混淆.今天,大量的信息专门搜索,至byte和char两个数据类型进行了总结和比较.第一批成果与大家 ...

  10. js之按键总结

    js 实现键盘记录 兼容FireFox和IE 2009-01-07 11:43 作者:羽殇仁 转载请注明出处,谢谢. 本篇文章是我的第一百篇blog文章,恭喜一下! 这两天突然想弄弄js的键盘记录,所 ...