位置:首页 » 技术 » Android SurfaceView 源码分析及使用

Android SurfaceView 源码分析及使用

日期:2015-11-06 阅读:0num
Advertisement

概述

SurfaceView 是 Android 中一种比较特殊的视图(View),它跟平时时候的 TextView、Button 最大的区别是它跟它的视图容器并不是在同一个视图层上,它的 UI 显示也可以不在一个独立的线程中完成,所以对 SurfaceView 的绘制并不会影响到主线程的运行。综合这些特点,SurfaceView 一般用来实现动态的或者比较复杂的图像还有动画的显示。

SurfaceView 的 MVC 框架

要使用 SurfaceView ,就必须了解它的另外两个组件:Surface 和 SurfaceHolder. Surface、SurfaceHolder 和 SurfaceView 三者之间的关系实质上就是广为人知的 MVC,即 Model-View-Controller。Model就是模型的意思,或者说是数据模型,或者更简单地说就是数据,也就是这里的Surface;View即视图,代表用户交互界面,也就是这里的SurfaceView;SurfaceHolder很明显可以理解为MVC中的Controller(控制器)。

应用场景

区别于普通的控件,SurfaceView 可以运行于主线程之外,不需要及时响应用户的输入,也不会造成响应的 ANR 问题。SurfaceView 一般用在游戏、视频、摄影等一些复杂 UI 且高效的图像的显示,这类的图像处理都需要开单独的线程来处理。

分析源码

分析 Surface,SurfaceHolder,SurfaceView 三个类

Surface

android.view.SurfaceView

/**
 * Handle onto a raw buffer that is being managed by the screen compositor.
 */
public class Surface implements Parcelable {
    // code......
}

首先来看 Surface 这个类,它实现了 Parcelable 接口进行序列化(这里主要用来在进程间传递 surface 对象),用来处理屏幕显示缓冲区的数据,源码中对它的注释为: Handle onto a raw buffer that is being managed by the screen compositor. Surface是原始图像缓冲区(raw buffer)的一个句柄,而原始图像缓冲区是由屏幕图像合成器(screen compositor)管理的。

- 由屏幕显示内容合成器(screen compositor)所管理的原生缓冲器的句柄(类似句柄) - 名词解释:句柄,英文:HANDLE,数据对象进入内存之后获取到内存地址,但是所在的内存地址并不是固定的,需要用句柄来存储内容所在的内存地址。从数据类型上来看它只是一个32位(或64位)的无符号整数。 - Surface 充当句柄的角色,用来获取源生缓冲区以及其中的内容 - 源生缓冲区(raw buffer)用来保存当前窗口的像素数据 - 于是可知 Surface 就是 Android 中用来绘图的的地方,具体来说应该是 Surface 中的 Canvas Surface 中定义了画布相关的 Canvas 对象

private final Canvas mCanvas = new CompatibleCanvas();

Java中,绘图通常在一个 Canvas 对象上进行的,Surface 中也包含了一个 Canvas 对象,这里的 CompatibleCanvas 是Surface.java 中的一个内部类,其中包含一个矩阵对象Matrix(变量名mOrigMatrix)。矩阵Matrix就是一块内存区域,针对View的各种绘画操作都保存在此内存中。

Surface 内部有一个 CompatibleCanvas 的内部类,这个内部类的作用是为了能够兼容 Android 各个分辨率的屏幕,根据不同屏幕的分辨率处理不同的图像数据。

private final class CompatibleCanvas extends Canvas {
        // A temp matrix to remember what an application obtained via {@link getMatrix}
        private Matrix mOrigMatrix = null;
        @Override
        public void setMatrix(Matrix matrix) {
            if (mCompatibleMatrix == null || mOrigMatrix == null || mOrigMatrix.equals(matrix)) {
                // don't scale the matrix if it's not compatibility mode, or
                // the matrix was obtained from getMatrix.
                super.setMatrix(matrix);
            } else {
                Matrix m = new Matrix(mCompatibleMatrix);
                m.preConcat(matrix);
                super.setMatrix(m);
            }
        }
        @SuppressWarnings("deprecation")
        @Override
        public void getMatrix(Matrix m) {
            super.getMatrix(m);
            if (mOrigMatrix == null) {
                mOrigMatrix = new Matrix();
            }
            mOrigMatrix.set(m);
        }
    }

两个重要的方法

需要说明的是,这里的 lockCanvas 并不是实际使用 SurfaceView 来进行绘图时 SurfaceHolder 对象调用的 lockCanvas 以及 unlockCanvasAndPost 方法。实际例子中使用的方法是在 SurfaceView 内部封装过对这两个方法封装之后的。 - lockCanvas(...) + Gets a Canvas for drawing into this surface. 获取进行绘画的 Canvas 对象 + After drawing into the provided Canvas, the caller must invoke unlockCanvasAndPost to post the new contents to the surface. 绘制完一帧的数据之后需要调用 unlockCanvasAndPost 方法把画布解锁,然后把画好的图像 Post 到当前屏幕上去显示 + 当一个 Canvas 在被绘制的时候,它是出于被锁定的状态,就是说必须等待正在绘制的这一帧绘制完成之后并解锁画布之后才能进行别的操作 + 实际锁住 Canvas 的过程是在 jni 层完成的 - unlockCanvasAndPost(...) + Posts the new contents of the Canvas to the surface and releases the Canvas. 将新绘制的图像内容传给 surface 之后这个 Canvas 对象会被释放掉(实际释放的过程是在 jni 层完成的)

Surface 的 lockCanvas 和 unlockCanvasAndPost 两个方法最终都是调用 jni 层的方法来处理,有兴趣可以看下相关的源码:

/frameworks/native/libs/gui/Surface.cpp /frameworks/base/core/jni/android_view_Surface.cpp

SurfaceHolder

android.view.SurfaceHolderSurfaceHolder 实际上是一个接口,它充当的是 Controller 的角色。

public interface SurfaceHolder {
}

来看下注释是怎么说的

  • Abstract interface to someone holding a display surface. 一个针对 Surface 的抽象接口
  • Allows you to control the surface size and format, edit the pixels in the surface, and monitor changes to the surface. 赤裸裸的 Controller 角色,可以控制 Surface 的大小和格式,监控 Surface 的变化(在回调函数中对 Surface 的变化做相应的处理)
  • When using this interface from a thread other than the one running its SurfaceView, you will want to carefully read the methods Callback.surfaceCreated() 如果用子线程来处理 SurfaceView 的绘制,需要用到接下来要介绍的关键接口 Callback 中的 surfaceCreated 方法。可以看到之前给的例子中就是在 surfaceCreated 方法中开启的绘制动画的线程

关键接口 Callback

Callback 是 SurfaceHolder 内部的一个接口,例子中就实现了这个接口来控制绘制动画的线程。

接口中有以下三个方法 - public void surfaceCreated(SurfaceHolder holder); + Surface 第一次被创建时被调用,例如 SurfaceView 从不可见状态到可见状态时 + 在这个方法被调用到 surfaceDestroyed 方法被调用之前的这段时间,Surface 对象是可以被操作的,拿 SurfaceView 来说就是如果 SurfaceView 只要是在界面上可见的情况下,就可以对它进行绘图和绘制动画 + 这里还有一点需要注意,Surface 在一个线程中处理需要渲染的图像数据,如果你已经在另一个线程里面处理了数据渲染,就不需要在这里开启线程对 Surface 进行绘制了 - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height); + Surface 大小和格式改变时会被调用,例如横竖屏切换时如果需要对 Sufface 的图像和动画进行处理,就需要在这里实现 + 这个方法在 surfaceCreated 之后至少会被调用一次 - public void surfaceDestroyed(SurfaceHolder holder); + Surface 被销毁时被调用,例如 SurfaceView 从可见到不可见状态时 + 在这个方法被调用过之后,就不能够再对 Surface 对象进行任何操作,所以需要保证绘图的线程在这个方法调用之后不再对 Surface 进行操作,否则会报错

SurfaceView

android.view.SurfaceView

SurfaceView,就是用来显示 Surface 数据的 View,通过 SurfaceView 来看到 Surface 的数据。

public class SurfaceView extends View {
    // code.....
}

分析一下源码中对 SurfaceView 的注释

  • Provides a dedicated drawing surface embedded inside of a view hierarchy. 在屏幕显示的视图层中嵌入了一块用做图像绘制的 Surface 视图
  • the SurfaceView punches a hole in its window to allow its surface to be displayed. SurfaceView 在屏幕上挖了个洞来来世它所绘制的图像
  • 挖洞是什么鬼?
    • 这里引入一个Z轴的概念,SurfaceView 视图所在层级的Z轴位置是小于用来其宿主 Activity 窗口的 Layer 的 Z 轴的,就是说其实 SurfaceView 实际是显示在 Activity 所在的视图层下方的
    • 那么问题就来了,为什么还是能看到 SurfaceView?形象一点的说法就是你在墙上凿了一个方形的洞,然后在洞上装了块玻璃,你就能看到墙后面的东西了。SurfaceView 就做了这样的事情,它把 Activity 所在的层当作了墙
  • The Surface will be created for you while the SurfaceView's window is visible. 这里说明了动画是什么时候开始的,当 SurfaceView 可见时,就可以开始在 Canvas 上绘制图像,并把图像数据传递给 Surface 用来显示在 SurfaceView 上
  • you should implement SurfaceHolder.Callback#surfaceCreated and SurfaceHolder.Callback#surfaceDestroyed to discover when the Surface is created and destroyed as the window is shown and hidden. 在使用 SurfaceView 的地方需要实现 SurfaceHolder.CallBack 回调,来对 Surface 的创建和销毁进行监听以及做响应的处理,这里的处理指的是开始对 Canvas 进行绘制并把数据传递给 Surface 来做显示

使用 SurfaceView

例子中展示了如何使用 SurfaceView

  1. 需要实现 SurfaceHolder.Callback 接口
  2. 需要在 SurfaceHolder.Callback 的 surfaceCreated 方法中开启一个线程进行动画的逐帧的绘制
  3. 需要在 SufaceHolder.Callback 的 surfaceDestroyed 方法中结束绘画的线程并调用 SurfaceHolder 的 removeCallbck 方法
  4. 绘画线程每一帧开始之前需要调用 lockCanvas 方法锁住画布进行绘图
  5. 绘制完一帧的数据之后需要调用 unlockCanvasAndPost 方法提交数据来显示图像

例子

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.drawable.BitmapDrawable;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder.Callback;
public class MySurfaceView extends SurfaceView implements Runnable, SurfaceHolder.Callback {
    private SurfaceHolder mHolder; // 用于控制SurfaceView
    private Thread t; // 声明一条线程
    private volatile boolean flag; // 线程运行的标识,用于控制线程
    private Canvas mCanvas; // 声明一张画布
    private Paint p; // 声明一支画笔
    float m_circle_r = 10;
    public MySurfaceView(Context context) {
        super(context);
        mHolder = getHolder(); // 获得SurfaceHolder对象
        mHolder.addCallback(this); // 为SurfaceView添加状态监听
        p = new Paint(); // 创建一个画笔对象
        p.setColor(Color.WHITE); // 设置画笔的颜色为白色
        setFocusable(true); // 设置焦点
    }
    /**
     * 当SurfaceView创建的时候,调用此函数
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        t = new Thread(this); // 创建一个线程对象
        flag = true; // 把线程运行的标识设置成true
        t.start(); // 启动线程
    }
    /**
     * 当SurfaceView的视图发生改变的时候,调用此函数
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
    }
    /**
     * 当SurfaceView销毁的时候,调用此函数
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        flag = false; // 把线程运行的标识设置成false
        mHolder.removeCallback(this);
    }
    /**
     * 当屏幕被触摸时调用
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return true;
    }
    /**
     * 当用户按键时调用
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
        }
        return super.onKeyDown(keyCode, event);
    }
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        surfaceDestroyed(mHolder);
        return super.onKeyDown(keyCode, event);
    }
    @Override
    public void run() {
        while (flag) {
            try {
                synchronized (mHolder) {
                    Thread.sleep(100); // 让线程休息100毫秒
                    Draw(); // 调用自定义画画方法
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (mCanvas != null) {
                    // mHolder.unlockCanvasAndPost(mCanvas);//结束锁定画图,并提交改变。
                }
            }
        }
    }
    /**
     * 自定义一个方法,在画布上画一个圆
     */
    protected void Draw() {
        mCanvas = mHolder.lockCanvas(); // 获得画布对象,开始对画布画画
        if (mCanvas != null) {
            Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
            paint.setColor(Color.BLUE);
            paint.setStrokeWidth(10);
            paint.setStyle(Style.FILL);
            if (m_circle_r >= (getWidth() / 10)) {
                m_circle_r = 0;
            } else {
                m_circle_r++;
            }
            Bitmap pic = ((BitmapDrawable) getResources().getDrawable(
                    R.drawable.qq)).getBitmap();
            mCanvas.drawBitmap(pic, 0, 0, paint);
            for (int i = 0; i < 5; i++)
                for (int j = 0; j < 8; j++)
                    mCanvas.drawCircle(
                            (getWidth() / 5) * i + (getWidth() / 10),
                            (getHeight() / 8) * j + (getHeight() / 16),
                            m_circle_r, paint);
            mHolder.unlockCanvasAndPost(mCanvas); // 完成画画,把画布显示在屏幕上
        }
    }
}

可能碰到的一些问题

为什么把一个 SurfaceView 放在布局文件中不做任务图像的绘制,它会显示一个黑色的区域?

造成这个现象的原因可以在 SurfaceView 的 draw 和 dispatchDraw 方法中看到,SurfaceView 中,windownType 变量被初始化为 WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA ,所以在创建绘制这个 View 的过程中整个 Canvas 会被涂成黑色

// windowType 的默认值
int mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
@Override
    public void draw(Canvas canvas) {
        if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
            // draw() is not called when SKIP_DRAW is set
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
                // punch a whole in the view-hierarchy below us
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
            }
        }
        super.draw(canvas);
    }

相关文章
  • Android SurfaceView 源码分析及使用

    概述 SurfaceView 是 Android 中一种比较特殊的视图(View),它跟平时时候的 TextView.Button 最大的区别是它跟它的视图容器并不是在同一个视图层上,它的 UI 显示也可以不在一个独立的线程中完成,所以对 SurfaceView 的绘制并不会影响到主线程的运行.综合这些特点,SurfaceView 一般用来实现动态的或者比较复杂的图像还有动画的显示. SurfaceView 的 MVC 框架 要使用 SurfaceView ,就必须了解它的另外两个组件:Surf

  • [Android]Volley源码分析(2)Cache

    [Android]Volley源码分析(二)Cache Cache作为Volley最为核心的一部分,Volley花了重彩来实现它.本章我们顺着Volley的源码思路往下,来看下Volley对Cache的处理逻辑. 我们回想一下昨天的简单代码,我们的入口是从构造一个Request队列开始的,而我们并不直接调用new来构造,而是将控制权反转给Volley这个静态工厂来构造. com.android.volley.toolbox.Volley: public static RequestQueue n

  • [Android] Volley源码分析(1)体系结构

    [Android] Volley源码分析(一)体系结构 Volley:google出的一个用于异步处理的框架.由于本身的易用性和良好的api,使得它能得以广泛的应用.我还是一如既往从源码的方向上来把控它.我们先通过一段简单的代码来了解Volley RequestQueue queue = Volley.newRequestQueue(this); ImageRequest imagerequest = new ImageRequest(url, new Response.Listener<Bit

  • [Android] Volley源码分析(5)答疑

    [Android] Volley源码分析(五)答疑 Volley源码分析系列出了有一段日子了,有不少看官私底下给我留言,同时抛出了一些问题.对于一些比较简单的问题我们跳过去,[email protected]�这位看官看源码时候的细腻程度,我引出这个问题供大家一块思考一下. Q:在写入文件头数据的时候为何不直接写入Int而是通过移位的方式来完成? 我们来看一下对应的源码: writeInt(os, CACHE_MAGIC); static void writeInt(OutputStream o

  • android smack源码分析——接收消息以及怎么解析消息 android smack源码分析——接收消息以及怎么解析消息

    android smack源码分析--接收消息以及如何解析消息 在android里面用的smack包其实叫做asmack,该包提供了两种不同的连接方式:socket和httpclient.该并且提供了很多操作xmpp协议的API,也方便各种不同自定义协议的扩展.我们不需要自己重新去定义一套接收机制来扩展新的协议,只需继承然后在类里处理自己的协议就可以了.而本文今天主要说两点,一点就是消息是如何接收的,另一点就是消息是如何通知事件的. 总的思路 1.使用socket连接服务器 2.将XmlPull

  • android camera 源码分析(基于使用)

    android camera 源码分析(基于应用) 这里主要是针对Ophone进行介绍的,当然 结合了android的源码(以下出现均为android2.2源码). 首先在Ophone中也是通过android.hardware.Camera类来控制摄像头设备的,要使用只有通过android.hardware.Camera.open()来打开. try { mCameraDevice = android.hardware.Camera.open(); } catch (RuntimeExcepti

  • [Android]Fragment源码分析(2) 状态

    [Android]Fragment源码分析(二) 状态 我们上一讲,抛出来一个问题,就是当Activity的onCreateView的时候,是如何构造Fragment中的View参数.要回答这个问题我们先要了解Fragment的状态,这是Fragment管理中非常重要的一环.我们先来看一下FragmentActivity提供的一些核心回调: @Override protected void onCreate(Bundle savedInstanceState) { mFragments.atta

  • Android Calculator2源码分析与批改 Android Calculator2源码分析与批改

    Android Calculator2源码分析与修改 将Android 4.4.4的计算器Calculator移植出来,可以独立的在Android Studio中使用.完整的代码已经推到我的GitHub,链接在文末. 下面看一下效果图: 这是在三星手机上的效果,和我之前在Nexus上用的计算器UI一样,原生的.当然有了源码,我们就可以定制自己想要的效果了. 代码可以去我的GitHub查看. 在Dialer和Calculator中加入暗码启动指定应用 比如在拨号面板中输入##55555##启动没有

  • 人们网官方Android客户端源码分析 人们网官方Android客户端源码分析

    人人网官方Android客户端源码分析 ContentProvider是不同应用程序之间进行数据交换的标准API,ContentProvider以某种Uri的形式对外提供数据,允许其他应用访问或修改数据;其他应用程序使用ContentResolver根据Uri去访问操作指定数据. 人人网Android客户端也是使用ContentProvider对需要保存于Android客户端的数据进行管理. 1. renren.db SQLLiteOpenHelper是Android提供的一个管理数据库的工具类

  • Android 消息处理源码分析(一)

    Android 消息处理源码分析(1) Android 消息处理源码分析(1) 在Android中,通常被使用的消息队列的代码在目录\sources\android-22\android\os下,涉及到以下几个类文件 Handler.java Looper.java Message.java MessageQueue.java Message.java public final class Message implements Parcelable { public int what; //消息

  • Android 消息处理源码分析(二)

    Android 消息处理源码分析(2) Android 消息处理源码分析(1)点击打开链接 继续接着分析剩下的类文件 Looper.java public final class Looper { final MessageQueue mQueue; //消息队列 final Thread mThread; //Looper联系的线程 public static void prepare() { prepare(true); } private static void prepare(boole

  • [Android]Fragment源码分析(1) 构造

    [Android]Fragment源码分析(一) 构造 Fragment是Android3.0之后提供的api,被大家广泛所熟知的主要原因还是因为随即附带的ViewPager控件.虽然我并不喜欢用它,但是它确实是一个相对不错的控件.还是我的一贯作风,我将从源码上向大家展示什么是Fragment.我们先写一个简单的代码对Fragment有个直观的认识:(为了保证我们方便调试,我们可以直接使用V4提供的源码包) FragmentTransaction t = getSupportFragmentMa

  • [Android]Fragment源码分析(3) 事务

    [Android]Fragment源码分析(三) 事务 Fragment管理中,不得不谈到的就是它的事务管理,它的事务管理写的非常的出彩.我们先引入一个简单常用的Fragment事务管理代码片段: FragmentTransaction ft = this.getSupportFragmentManager().beginTransaction(); ft.add(R.id.fragmentContainer, fragment, "tag"); ft.addToBackStack(&

  • android 从源码分析为啥Listview初次显示时没滚动却自动调用onScroll方法的原因

    android 从源码分析为什么Listview初次显示时没滚动却自动调用onScroll方法的原因 我们做Listview的分批加载时,需要为Listview调用setOnScrollListener(具体代码可见我上一篇博客) 可是,我们会发现,当运行程序时,listview明明没有滚动,那为什么系统会调用onScroll方法呢?(补充:此时onScrollStateChanged并不会调用) 我们先看setOnScrollListener源码: public void setOnScrol

  • Android   Launcher2源码分析 Android Launcher2源码分析

    Android Launcher2源码分析 Android源码程序程序中有一个应用程序入口,官方给出的中文翻译为"启动器".我们一下统称Launcher. Launcher源码分析,我们还是从AndroidManifest.xml开始: ... 其他,我们姑且也不管,有三点我们必须说一下: 一. android:hardwareAccelerated="@bool/config_hardwareAccelerated" 指定了整个应用程序是启用硬件加速的,这样整个应

  • Android   IntentService 源码分析 Android IntentService 源码分析

    IntentService简介: IntentService是一个通过Context.startService(Intent)启动可以处理异步请求的Service,使用时你只需要继承IntentService和重写其中的onHandleIntent(Intent)方法接收一个Intent对象,该服务会在异步任务完成时自动停止服务. 所有的请求的处理都在IntentService内部工作线程中完成,它们会顺序执行任务(但不会阻塞主线程的执行),某一时刻只能执行一个异步请求. IntnetServi

  • Android HandlerThread 源码分析

    HandlerThread 简介: 我们知道Thread线程是一次性消费品,当Thread线程执行完一个耗时的任务之后,线程就会被自动销毁了.如果此时我又有一 个耗时任务需要执行,我们不得不重新创建线程去执行该耗时任务.然而,这样就存在一个性能问题:多次创建和销毁线程是很耗 系统资源的.为了解这种问题,我们可以自己构建一个循环线程Looper Thread,当有耗时任务投放到该循环线程中时,线程执行耗 时任务,执行完之后循环线程处于等待状态,直到下一个新的耗时任务被投放进来.这样一来就避免了多次

  • Android:Otto源码分析

    源码分析">Otto源码分析 Otto是一个轻量级的EventBus,它的使用非常简单,我们使用一个Bus的单例,所有需要产生事件(@Produce bus.post(new YourEvent(-)))或者处理事件(@Subscribe)的对象,在create时register,销毁destroy时unregister即可. 使用 @Subscribe 订阅事件,也就是事件的处理者,它有且仅有一个参数YourEvent,每一个Subscribe对应处理一个YourEvent.Event用

  • Android debuggerd 源码分析 Android debuggerd 源码分析

    debuggerd 简介 Android系统自带一个实用的程序异常退出的诊断daemon debuggerd.此进程可以侦测到程序崩溃,并将崩溃时的进程状态信息输出到文件和串口中,以供开发人员分析调试使用.Debuggerd的数据被保存在/data/tombstone/目录下,共可保存10个文件,当超过10个时,会覆盖重写最早生产的文件.串口中,则直接用DEBUG的tag,输出logcat信息. Linux kernel有自己的一套signal机制,在应用程序崩溃时,通常系统内核都会发送sign

  • Android Toast源码分析

    前言 这周去杭州参加了百阿培训,见到了传说中的牛人多隆大神.从多隆大神身上看到了做技术人的纯粹,单纯.除了见到多隆大神,这次培训并没有太多的收获,反而培训过程中遇到了好多产品上的Bug,远程办公快累到死.总结一下跟Toast相关的问题,首先从深入学习Toast的源码实现开始. Toast源码实现 Toast入口 我们在应用中使用Toast提示的时候,一般都是一行简单的代码调用,如下所示: Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(

最新文章
  • [北京朝外 SOHO]互联网金融公司“理财范”,诚邀各种工程师入伙。10k-20k

    互联网金融是最近几年非常火的行业,"理财范"是一个非常有志向的团队. 虽然梦想还是要有的,但是我不想用梦想来忽悠任何人入伙.所以,我们来认认真真地实实在在地谈谈--作为技术工程师的你,加入这样的公司会给你带来些什么. 1,有钱么?这是一家很有钱途的公司.这也是一个离钱最近的行业.公司的业务今年3月份上线,到目前已经有可观的收入,基本上实现收支平衡. 2,有什么样的人?在这样的公司里面,可以多接触一些金融领域的人和事,接触各种时尚的帅哥美女,给你提供一个从宅男程序员华丽转身为"

  • 你们这些Geek都用什么相机?

    调查下 --cut-- bloodyxu在2011-12-29 23:20:2回答到: GRD III 和一台已经老掉牙的30D caniggia3在2011-12-30 10:03:0回答到: 我之前用佳能50D,但是旅行带着大机器好麻烦-- 现在用佳能的Canonet GIII QL17和徕卡x1. Echoldman在2011-12-30 10:05:1回答到: 刚订了gxr+10 Echoldman在2011-12-30 10:06:5回答到: 补充下是GXR+S10 printf37在

  • 情人节创意祝福短信大全

    1.鱼对水说:你看不见我的眼泪,因为我在水里.水说:我能感觉到你的眼泪,因为你在我心里. 2.知道我在做什么吗?给你五个选择:A:想你B:很想你C:非常想你D:不想你不行E:以上皆是. 3.老公啊,记得明天是什么日子吗?猜错了!不是第一次牵手的日子,也不是第一次kiss的日子,是去年情人节你送我玫瑰花的周年纪念日啦! 4.五百年前,你是我们家的长工,那天在我在窗口偷看你砍柴的姿势,我就喜欢上你,你可别怪我当时没有告诉你!因为那时没有短消息! 5.上帝给了我3个愿望,第一个我去了天堂,发现天使不是

  • 一份夫妻生活协议

    1.吵架不当着父母,亲戚,邻居的面吵,在公共场所给对方面子. 2.不管谁对谁错,只要一吵架,男方必须先轻声轻气哄女方一次,女方才能马上冷静下来,否则女方一看到男方哇啦哇啦女方也忍不住哇啦哇啦,一旦造成严重后果,全部由男方负责. 3.在家里吵架不准一走了之,实在要走不得走出小区,不许不带手机和关机. 4.尊敬对方的父母长辈,吵架不开心不能对父母无礼. 5.有错一方要主动道歉,无错一方在有错方道歉并补偿后要尽快原谅对方. 6.双方都有错时要互相检讨,认识到错误并道歉后由男方主动提出带女方出去散心.

  • Windows系统属性造假原理 Windows系统属性造假原理

    先来看个图: 该图是在我C4 2.4/256机子上的系统属性,决非图片修改. 修改方法:(本文以修改XP+SP2为例,其它系统方法类似.) 1,系统属性对应的系统文件为system32sysdm.cpl,我们先把它备份一个之后用exescope打开. 2,在Resource>Dialog中101对应的窗体就是我们要改的.我的方法是将显示系统的几行信息隐藏再用OEM信息在相同位置代替. 3,具体为改几个Link Window属性:将第4和第5个的Visible去掉,从第1个到3个和第6个到第9个的

  • 天天酷跑南瓜法师和神气牛牛属性对比评点 天天酷跑南瓜法师和神气牛牛属性对比评点

    <天天酷跑>里南瓜法师和神气牛牛哪个好?下面就是高玩贡献出来的南瓜法师和神气牛牛属性对比评点-- 天天酷跑南瓜法师属性 1.游戏金币加25% 2.表现分数加30% 3.踩怪得分加50% 技能:对超能少年得分加成3% 天天酷跑神奇牛牛属性 表现分数+20% 冲刺时间+2.0秒 飞星得分+300% 技能:每天可以完成5个每日任务 每小时产生999个金币 总结 从属性上来看,南瓜法师的表现分数加成比神气牛牛高10个百分点,以及对超能少年得分加成3%,除此之外,没有没有亮点可言了. 南瓜法师和神气牛牛

  • 教你如何巧妙使用香水

    怎样才能使一瓶昂贵的香水物尽其用呢?今天就要教大家最省钱的香水使用办法哦,让你心爱的香水每一滴都发挥最大作用!下面就一起来学习下吧! 如果想要省着点用香水,那么可以把香水擦衣服上好,可是不大建议这样做,因为这样是有点伤衣服的.冬天的时候你把香水喷衣服上,可以保持香味更持久,因为冬天香水味到不容易散开来. 一般来说,你在喷香水的时候,要距离你的皮肤20厘米,这样喷的范围大而且均匀,香水如果喷得不均匀的话,很容易挥发造成浪费. 如果要隐蔽一点,那一些动态部位就不要喷了. 擦香水通常最普遍的地方就是耳

  • 《全民打怪兽》紫装怎么得?获取方法攻略 《全民打怪兽》紫装怎么得?获取方法攻略

    各位全民打怪兽的玩家们,下面为大家带来的是游戏中的紫装获取方法,紫装是现在游戏版本中最强力的装备,那么紫装的获取方法是什么呢?紫装要怎么得呢?下文就为大家带来紫装获取的全攻略,不要错过哦. 全民打怪兽紫装怎么得? 第一.非常非常小概率开玉匣子直接得到成品. 第二.靠玉匣子开出的碎片合成.概率也不是很高.主要是不会重复开出一样的. 第三.精英副本也是有非常非常小概率爆成品. 第四.精英副本爆碎片合成. 第五.在炼化里面有个乾坤商人里面可以购买碎片合成.总之想要紫装.要嘛人品逆天.要嘛你是土豪. 以

  • 375光年外发现迄今最古老行星  诞于宇宙大爆炸之后 375光年外发现迄今最古老行星 诞于宇宙大爆炸之后

    据报道,科学家最新研究发现,围绕一颗距离地球375光年的恒星运行的两颗巨大的行星,是迄今为止发现的最古老的外星世界.据估计,已经有128亿岁的主星及其两颗行星可能是在宇宙刚刚诞生时形成的,发生在宇宙大爆炸之后不超过10亿年. 研究负责人约翰-塞蒂亚旺在德国海德尔堡马普天文研究所进行了这项研究,他表示,银河自己迄今还未完全形成.在最近的一项研究期间,塞蒂亚旺及其同事发现有两颗行星围绕编号是HIP 11952的恒星运行的迹象.根据该科研组的计算结果可知,其中一颗行星几乎与木星一样大,它围绕轨道运行一

  • 百将行竞技场输出伤害最高搭配攻略分享 百将行竞技场输出伤害最高搭配攻略分享

    在百将行的这一款游戏里面,玩家们在竞技场的阵容搭配好坏,砼能够影响最后的解决,那么到底怎么样搭配阵容才好呢?对于这个问题,今天小编就来给各位玩家们来说说搭配方法. 给各位百将行的玩家们分享一下在竞技场里面最高伤害输出阵容搭配的具体方法. 搭配方法: 成吨输出伤害组合:周仓+孙尚香+张宝+诸葛亮 推荐点评: 这套组合也是目前比较流行的一套输出组合了,不同于关羽组合的单秒,在占据先手的情况下,完全可以实现对敌方进行群秒. 推荐理由: 1.在百将行中周仓的定位是法T,但是他也具有很强大法伤能力,大招的

热门推荐
  • 替人出一台九成新 iPhone 5C,蓝色,32GB,支持移动 4G 替人出一台九成新 iPhone 5C,蓝色,32GB,支持移动 4G 购买时间五个月,一直都带保护壳,后背很新,有购买发票 <img src="" /> <img src="" /> <img src="" /> <img src="" /> --cut-- austinluo在2014-06-07 13:46:1回答到: 价格 DrWeb在2014-06-07 13:52:3回答到: @austinluo ¥3000 bullettrain143
  • 怎样让孩子大脑动起来的方法推荐 怎样让孩子大脑动起来的方法推荐 如何才能让孩子的大脑更加的聪明呢?动脑时候让大脑越来越聪明的一个好方法,如果经常不用大脑,那么大脑会变得更加的迟钝.所以想要孩子越来越聪明,我们就要让孩子的大脑动起来.下面就为大家介绍让孩子大脑动起来的方法. 本站育儿知识配图 让孩子的大脑动起来的方案 培养孩子动脑筋的兴趣."兴趣是最好的老师",孩子若对某件事有浓厚的兴趣,就会集中思想和注意力,就会想方设法克服种种困难来达到自己的目的.怎样培养孩子动脑筋的兴趣呢?父母是孩子的启蒙老师,对孩子的影响是相当大的,因此,父母要以自己的情绪和
  • 与我长跑十年的女朋友就要嫁人了 与我长跑十年的女朋友就要嫁人了 昨天下午凌一尧给我发来一张照片,是一件婚纱,她问好不好看,我说还行. 她说"初五举办婚礼,和我们以前想象得一样,有鲜花拱门,有红地毯,有白婚纱黑礼服,就是没有你." 我说"要不要我去凑个份子?" 她半天之后才回复说:"不用了. ----------------------------------------------------------------------------------------------- 2001年的夏天,我十六岁,正在读高中.
  • 哪种女人容易出轨 哪种女人容易出轨 哪种女人容易出轨 一.太穷的女人 现在的社会就是一个笑贫不笑娼的年代,穷人才会让别人看不起,所以有的女人为了钱,做人家第三者啊.出轨啊,因为这些来钱快. 二.破罐子破摔的女人 有些女人出轨是被逼的,比如曾经被男人伤过,不相信爱情了,所以自己放弃自己,管它什么道德观念,只有报复的快感才重要. 三.性报复欲强的女人 老公先对不起她,或许女人会原谅,不过时机了,那么她就以牙还牙的报复.但这样的女人终究没有什么好下场. 四.寂寞的女人 因为各种原因不得不和老公分居两地,对双方来说那日子真难熬,如果这时候
  • 十万个冷笑话体力最佳使用方法分享 十万个冷笑话体力最佳使用方法分享 在十万个冷笑话的这一款游戏里面,玩家们在体力使用方面到底有什么样的小技巧呢?对于这个问题,今天小编就为各位玩家们带来了一些使用技巧,下面就和小编一起去看看吧. 给各位玩家们分享一下十万个冷笑话的里面在体力使用方面的一些小技巧. 使用技巧: 1.自动恢复,120点体力槽,每6分钟恢复一次,每12小时蓄满120. 2.鸡腿午餐,中午12点可领取60点体力;晚上6点可以领取60点体力. 3.元宝购买,正常非R玩家可以购买1次,VIP等级越高可购买次数越多. 体力用于刷本.普通6点,噩梦为双倍. 刷本分
  • 辐射4异形彩蛋详解及位置分享 辐射4异形彩蛋详解及位置分享 辐射4的这一款游戏相信各位玩家们都已经不会陌生了吧,今天小编在这里要给大家说的就是这款游戏异形彩蛋的情况以及位置,下面各位玩家们就来跟着小编看下吧. 给各位辐射4的玩家们来详细的说上一说异形彩蛋的情况以及位置. 分享一览: 这就是1Times发现的那个探测器 事情是这样的,imgur上有一个叫做1Times的用户上传了一堆图片,他说自己在<辐射4>里找到了一个奇怪的传感器道具,这个传感器道具并没有什么其他的用途,但是角色可以拿起来观察它的模型,他拿这这个传感器看了半天,发现在传感器上面有一个有