Android 触摸事件处理机制

  Android 触摸事件的处理主要涉及到几个方法:onInterceptTouchEvent(), dipatchTouchEvent(), onTouchEvent(), onTouch()。

  onInterceptTouchEvent() 用于拦截事件并改变事件传递方向。解释一下事件传递。比如一个Activity中展示给用户可能是ViewGroup和View的多层嵌套,默认情况下触摸事件产生之后从最外层一次传递到最里面一层,然后在从最里面一层开始响应。从最里面一层开始依次调用各层次的dispatchTouchEvent()进行分发,dispatchTouchEvent()中在调用onTouch / onTouchEvent进行响应触摸事件。

  onInterceptTouchEvent() 方法可以将触摸事件的传递截断,让触摸事件在某一层就不往下面传递,就开始调用这一层的dispatchTouchEvent(),开始向上层返回。如果需要在某一层拦截,需要复写该层的onInterceptTouchEvent()方法,并让该方法返回 true。通过一张图来解释一下。

  Android 触摸事件处理机制

  假设一个Activity展示的界面有A->B->C->D四层,当事件发生之后,首先经过A的onInterceptTouchEvent(), 如果A的onInterceptTouchEvent()返回false,则会传递到B的onInterceptTouchEvent(),如果返回false则一次向下(内层)传递。如果某一层的onInterceptTouchEvent()返回true,然后就会调用该层的disatchTouchEvent()分发事件,事件不再向下传递。如果各层都没有拦截事件则从最内层开始调用dispatchTouchEvent(),如果某一各层的dispatchTouchEvent()返回true,则表明该层消费了该事件,则上面层的dispatchTouEvent()不会被调用。举一个例子:

  Android 触摸事件处理机制

  上图中B层的onInterceptTouchEvent()返回true,则事件被拦截,开始调用B层的dispatchTouchEvent()向上返回一次响应触摸事件。

  知道这个机制有什么卵用吗?

  一个简单例子,我们在scrollView中放置了图片,图片允许缩放拖动,但是你对图片进行拖动的时候会发现scrollView也跟着动,这样体验就会很不好,怎么办呢?

  结合上面的分析,我们可以知道,如果让触摸事件传递到内层的图片,然后在在图片的disPatchTouchEvent()中把这个触摸事件消费了就不就可以了吗?

  具体做法在图片的onTouch() 方法中,ACTION_DOWN中设置scrollView不拦截事件,通过scrollView.requestDisallowInterceptTouchEvent(true)来完成,完成想要的处理之后在图片的onTouch()方法最后返回true就可以实现了。

  问题又来了 onTouch(View v, MotionEvent event) 和 onTouchEvent(MotionEvent event) 有什么区别呢? 看起来那么像,不会是完成相同的功能吧?这样做不是多此一举吗,为什么不用一个就好了。

  为了搞清楚这个问题,首先需要来看View中disPatchTouchEvent()方法:

public boolean dispatchTouchEvent(MotionEvent event) {
    // If the event should be handled by accessibility focus first.
    if (event.isTargetAccessibilityFocus()) {
        // We don't have focus or no virtual descendant has it, do not handle the event.
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        // We have focus and got the event, then use normal event dispatch.
        event.setTargetAccessibilityFocus(false);
    }

    boolean result = false;

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        stopNestedScroll();
    }

    if (onFilterTouchEventForSecurity(event)) {
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    // Clean up after nested scrolls if this is the end of a gesture;
    // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    // of the gesture.
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}

  从上面可以看出onTouch() 先于 onTouchEvent()执行。通过查看View的源码还发现,或者简单推理一下我们设置setOnTouchListener()设置的是谁就知道,我们什么时候需要调用onTouch()方法让其发挥作用而不是让onTouchEvent()来响应了。View源码中可以看出onTouch是一个Interface的方式实现,将处理逻辑的实现交给开发者自定义,因此可以得出Android系统自带的控件我们使用onTouch处理事件,如果我们需要扩展View,则需要复写onTouchEvent()来实现事件的处理。其实onTouch(View v, MotionEvent event) 和 onTouchEvent(MotionEvent event)可以从这两个方法接受的参数就可以看出不同来,onTouch包含一个View类型的参数,因此是可以设置给某个View的,onTouchEvent()则是给某个View自己用的。

  既然提到了View的事件响应,那onClick事件又是怎么响应的呢? 通过产看源码可以发现onClick是在onTouchEvent中执行的,而且是在onTouchEvent的ACTION_UP事件中执行的。因此如果View 的onTouch()返回true则会导致onClick得不到执行,因为onTouchEvent()得不到执行。

  此外Activity中也有onTouchEvent()成员方法,如果Activity中的View都不处理Event则Activity的onTouchEvent()会调用。

 

Android OpenGL 编写简单滤镜

  Android 上使用Opengl进行滤镜渲染效率较高,比起单纯的使用CPU给用户带来的体验会好很多。滤镜的对象是图片,图片是以Bitmap的形式表示,Opengl不能直接处理Bitmap,在Android上一般是通过GLSurfaceView来进行渲染的,也可以说成Android需要借助GLSurfaceView来完成对图片的渲染。

  GlSurfaceView 的图片来源依然是Bitmap,但是Bitmap需要以纹理(Texture)的形式载入到Opengl中。因此我首先来看一下载入纹理的步骤:

  1. GLES20.glGenTextures() : 生成纹理资源的句柄

  2. GLES20.glBindTexture(): 绑定句柄

  3. GLUtils.texImage2D() :将bitmap传递到已经绑定的纹理中

  4. GLES20.glTexParameteri() :设置纹理属性,过滤方式,拉伸方式等

  这里做滤镜使用Android4.x以后提供的 Effect 类来完成,Effect类实现也是通过Shader的方式来完成的,这些Shader程序内置在Android中,我们只需要按照一定的方式来调用就行了。在Android上使用GLSurfaceView来显示并完成图片的渲染,实现渲染需要实现GLSurfaceView.Render接口,该接口有三个方法:onDrawFrame(GL10 gl) ,该方法按照一定的刷新频率反复执行;onSurfaceChanged(GL10 gl, int width, int height),该方法在窗口重绘的时候执行;onSurfaceCreated(GL10 gl, EGLConfig config) 在创建SurfaceView的时候执行。

  使用Effect类会用到EffectFactory 和 EffectContex,在下面的例子中看看具体的使用方式。

  首先定义一个Activity:EffectivefilterActivity

package com.example.effectsfilterdemo;

import java.nio.IntBuffer;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

public class EffectsFilterActivity extends Activity {

    private GLSurfaceView mEffectView;

    private TextureRenderer renderer;


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        renderer = new TextureRenderer();
        renderer.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.puppy));
        renderer.setCurrentEffect(R.id.none);
        
        mEffectView = (GLSurfaceView) findViewById(R.id.effectsview);
        //mEffectView = new GLSurfaceView(this);
        mEffectView.setEGLContextClientVersion(2);
        //mEffectView.setRenderer(this);
        mEffectView.setRenderer(renderer);
        mEffectView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
        
        //setContentView(mEffectView);
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        Log.i("info", "menu create");
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        renderer.setCurrentEffect(item.getItemId());
        mEffectView.requestRender();
        return true;
    }
}

  EffectivefilterActivity 中使用了两个布局文件,一个用于Activity的布局,另一个用于菜单的布局。

  R.layout.main:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <android.opengl.GLSurfaceView
        android:id="@+id/effectsview"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
         />

</LinearLayout>

  R.menu.main:

<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:id="@+id/none"
        android:showAsAction="never"
        android:title="none"/>
    <item
        android:id="@+id/autofix"
        android:showAsAction="never"
        android:title="autofix"/>
    <item
        android:id="@+id/bw"
        android:showAsAction="never"
        android:title="bw"/>
    <item
        android:id="@+id/brightness"
        android:showAsAction="never"
        android:title="brightness"/>
    <item
        android:id="@+id/contrast"
        android:showAsAction="never"
        android:title="contrast"/>
    <item
        android:id="@+id/crossprocess"
        android:showAsAction="never"
        android:title="crossprocess"/>
    <item
        android:id="@+id/documentary"
        android:showAsAction="never"
        android:title="documentary"/>
    <item
        android:id="@+id/duotone"
        android:showAsAction="never"
        android:title="duotone"/>
    <item
        android:id="@+id/filllight"
        android:showAsAction="never"
        android:title="filllight"/>
    <item
        android:id="@+id/fisheye"
        android:showAsAction="never"
        android:title="fisheye"/>
    <item
        android:id="@+id/flipvert"
        android:showAsAction="never"
        android:title="flipvert"/>
    <item
        android:id="@+id/fliphor"
        android:showAsAction="never"
        android:title="fliphor"/>
    <item
        android:id="@+id/grain"
        android:showAsAction="never"
        android:title="grain"/>
    <item
        android:id="@+id/grayscale"
        android:showAsAction="never"
        android:title="grayscale"/>
    <item
        android:id="@+id/lomoish"
        android:showAsAction="never"
        android:title="lomoish"/>
    <item
        android:id="@+id/negative"
        android:showAsAction="never"
        android:title="negative"/>
    <item
        android:id="@+id/posterize"
        android:showAsAction="never"
        android:title="posterize"/>
    <item
        android:id="@+id/rotate"
        android:showAsAction="never"
        android:title="rotate"/>
    <item
        android:id="@+id/saturate"
        android:showAsAction="never"
        android:title="saturate"/>
    <item
        android:id="@+id/sepia"
        android:showAsAction="never"
        android:title="sepia"/>
    <item
        android:id="@+id/sharpen"
        android:showAsAction="never"
        android:title="sharpen"/>
    <item
        android:id="@+id/temperature"
        android:showAsAction="never"
        android:title="temperature"/>
    <item
        android:id="@+id/tint"
        android:showAsAction="never"
        android:title="tint"/>
    <item
        android:id="@+id/vignette"
        android:showAsAction="never"
        android:title="vignette"/>

</menu>

  在R.layout.main中只定义了一个GLSurfaceView用于显示图片,R.menu.main用于显示多个菜单项,通过点击菜单来完成调用不同滤镜实现对图片的处理。

  接下来看比较关键的Renderer接口的实现。

package com.example.effectsfilterdemo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.media.effect.Effect;
import android.media.effect.EffectContext;
import android.media.effect.EffectFactory;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.GLUtils;
import android.util.Log;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.LinkedList;
import java.util.Queue;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class TextureRenderer implements GLSurfaceView.Renderer{

    private int mProgram;
    private int mTexSamplerHandle;
    private int mTexCoordHandle;
    private int mPosCoordHandle;

    private FloatBuffer mTexVertices;
    private FloatBuffer mPosVertices;

    private int mViewWidth;
    private int mViewHeight;

    private int mTexWidth;
    private int mTexHeight;
    
    private Context mContext;
    private final Queue<Runnable> mRunOnDraw;
    private int[] mTextures = new int[2];
    int mCurrentEffect;
    private EffectContext mEffectContext;
    private Effect mEffect;
    private int mImageWidth;
    private int mImageHeight;
    private boolean initialized = false;

    private static final String VERTEX_SHADER =
        "attribute vec4 a_position;/n" +
        "attribute vec2 a_texcoord;/n" +
        "varying vec2 v_texcoord;/n" +
        "void main() {/n" +
        "  gl_Position = a_position;/n" +
        "  v_texcoord = a_texcoord;/n" +
        "}/n";

    private static final String FRAGMENT_SHADER =
        "precision mediump float;/n" +
        "uniform sampler2D tex_sampler;/n" +
        "varying vec2 v_texcoord;/n" +
        "void main() {/n" +
        "  gl_FragColor = texture2D(tex_sampler, v_texcoord);/n" +
        "}/n";

    private static final float[] TEX_VERTICES = {
        0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f
    };

    private static final float[] POS_VERTICES = {
        -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f
    };

    private static final int FLOAT_SIZE_BYTES = 4;
    
    public TextureRenderer() {
        // TODO Auto-generated constructor stub
        mRunOnDraw = new LinkedList<>();

    }

    public void init() {
        // Create program
        mProgram = GLToolbox.createProgram(VERTEX_SHADER, FRAGMENT_SHADER);

        // Bind attributes and uniforms
        mTexSamplerHandle = GLES20.glGetUniformLocation(mProgram,
                "tex_sampler");
        mTexCoordHandle = GLES20.glGetAttribLocation(mProgram, "a_texcoord");
        mPosCoordHandle = GLES20.glGetAttribLocation(mProgram, "a_position");

        // Setup coordinate buffers
        mTexVertices = ByteBuffer.allocateDirect(
                TEX_VERTICES.length * FLOAT_SIZE_BYTES)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mTexVertices.put(TEX_VERTICES).position(0);
        mPosVertices = ByteBuffer.allocateDirect(
                POS_VERTICES.length * FLOAT_SIZE_BYTES)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mPosVertices.put(POS_VERTICES).position(0);
    }

    public void tearDown() {
        GLES20.glDeleteProgram(mProgram);
    }

    public void updateTextureSize(int texWidth, int texHeight) {
        mTexWidth = texWidth;
        mTexHeight = texHeight;
        computeOutputVertices();
    }

    public void updateViewSize(int viewWidth, int viewHeight) {
        mViewWidth = viewWidth;
        mViewHeight = viewHeight;
        computeOutputVertices();
    }

    public void renderTexture(int texId) {
        GLES20.glUseProgram(mProgram);
        GLToolbox.checkGlError("glUseProgram");

        GLES20.glViewport(0, 0, mViewWidth, mViewHeight);
        GLToolbox.checkGlError("glViewport");

        GLES20.glDisable(GLES20.GL_BLEND);

        GLES20.glVertexAttribPointer(mTexCoordHandle, 2, GLES20.GL_FLOAT, false,
                0, mTexVertices);
        GLES20.glEnableVertexAttribArray(mTexCoordHandle);
        GLES20.glVertexAttribPointer(mPosCoordHandle, 2, GLES20.GL_FLOAT, false,
                0, mPosVertices);
        GLES20.glEnableVertexAttribArray(mPosCoordHandle);
        GLToolbox.checkGlError("vertex attribute setup");

        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLToolbox.checkGlError("glActiveTexture");
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);//把已经处理好的Texture传到GL上面
        GLToolbox.checkGlError("glBindTexture");
        GLES20.glUniform1i(mTexSamplerHandle, 0);

        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
    }

    private void computeOutputVertices() { //调整AspectRatio 保证landscape和portrait的时候显示比例相同,图片不会被拉伸
        if (mPosVertices != null) {
            float imgAspectRatio = mTexWidth / (float)mTexHeight;
            float viewAspectRatio = mViewWidth / (float)mViewHeight;
            float relativeAspectRatio = viewAspectRatio / imgAspectRatio;
            float x0, y0, x1, y1;
            if (relativeAspectRatio > 1.0f) {
                x0 = -1.0f / relativeAspectRatio;
                y0 = -1.0f;
                x1 = 1.0f / relativeAspectRatio;
                y1 = 1.0f;
            } else {
                x0 = -1.0f;
                y0 = -relativeAspectRatio;
                x1 = 1.0f;
                y1 = relativeAspectRatio;
            }
            float[] coords = new float[] { x0, y0, x1, y0, x0, y1, x1, y1 };
            mPosVertices.put(coords).position(0);
        }
    }
    
    private void initEffect() {
        EffectFactory effectFactory = mEffectContext.getFactory();
        if (mEffect != null) {
            mEffect.release();
        }
        /**
         * Initialize the correct effect based on the selected menu/action item
         */
        switch (mCurrentEffect) {

        case R.id.none:
            break;

        case R.id.autofix:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_AUTOFIX);
            mEffect.setParameter("scale", 0.5f);
            break;

        case R.id.bw:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_BLACKWHITE);
            mEffect.setParameter("black", .1f);
            mEffect.setParameter("white", .7f);
            break;

        case R.id.brightness:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_BRIGHTNESS);
            mEffect.setParameter("brightness", 2.0f);
            break;

        case R.id.contrast:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_CONTRAST);
            mEffect.setParameter("contrast", 1.4f);
            break;

        case R.id.crossprocess:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_CROSSPROCESS);
            break;

        case R.id.documentary:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_DOCUMENTARY);
            break;

        case R.id.duotone:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_DUOTONE);
            mEffect.setParameter("first_color", Color.YELLOW);
            mEffect.setParameter("second_color", Color.DKGRAY);
            break;

        case R.id.filllight:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_FILLLIGHT);
            mEffect.setParameter("strength", .8f);
            break;

        case R.id.fisheye:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_FISHEYE);
            mEffect.setParameter("scale", .5f);
            break;

        case R.id.flipvert:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_FLIP);
            mEffect.setParameter("vertical", true);
            break;

        case R.id.fliphor:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_FLIP);
            mEffect.setParameter("horizontal", true);
            break;

        case R.id.grain:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_GRAIN);
            mEffect.setParameter("strength", 1.0f);
            break;

        case R.id.grayscale:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_GRAYSCALE);
            break;

        case R.id.lomoish:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_LOMOISH);
            break;

        case R.id.negative:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_NEGATIVE);
            break;

        case R.id.posterize:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_POSTERIZE);
            break;

        case R.id.rotate:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_ROTATE);
            mEffect.setParameter("angle", 180);
            break;

        case R.id.saturate:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_SATURATE);
            mEffect.setParameter("scale", .5f);
            break;

        case R.id.sepia:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_SEPIA);
            break;

        case R.id.sharpen:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_SHARPEN);
            break;

        case R.id.temperature:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_TEMPERATURE);
            mEffect.setParameter("scale", .9f);
            break;

        case R.id.tint:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_TINT);
            mEffect.setParameter("tint", Color.MAGENTA);
            break;

        case R.id.vignette:
            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_VIGNETTE);
            mEffect.setParameter("scale", .5f);
            break;

        default:
            break;

        }
    }
    
    public void setCurrentEffect(int effect) {
        mCurrentEffect = effect;
    }

    
    public void setImageBitmap(final Bitmap bmp){
        runOnDraw(new Runnable() {
            
            @Override
            public void run() {
                // TODO Auto-generated method stub
                loadTexture(bmp);
            }
        });
    }
    
    private void loadTexture(Bitmap bmp){
        GLES20.glGenTextures(2, mTextures , 0);

        updateTextureSize(bmp.getWidth(), bmp.getHeight());
        
        mImageWidth = bmp.getWidth();
        mImageHeight = bmp.getHeight();

        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[0]);
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bmp, 0);

        GLToolbox.initTexParams();
    }
    
    private void applyEffect() {
        if(mEffect == null){
            Log.i("info","apply Effect null mEffect");
        }
        
        mEffect.apply(mTextures[0], mImageWidth, mImageHeight, mTextures[1]);
    }

    private void renderResult() {
        if (mCurrentEffect != R.id.none) {
            renderTexture(mTextures[1]);
        } else {
            renderTexture(mTextures[0]);
        }
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        // TODO Auto-generated method stub
        if(!initialized){
            init();
            mEffectContext = EffectContext.createWithCurrentGlContext();
            initialized = true;
        }
        
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        synchronized (mRunOnDraw) {
            while (!mRunOnDraw.isEmpty()) {
                mRunOnDraw.poll().run();
            }
        }
        
        if (mCurrentEffect != R.id.none) {
            initEffect();
            applyEffect();
        }
        renderResult();
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        // TODO Auto-generated method stub
        updateViewSize(width, height);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // TODO Auto-generated method stub
        
    }
    
    protected void runOnDraw(final Runnable runnable) {
        synchronized (mRunOnDraw) {
            mRunOnDraw.add(runnable);
        }
    }
}

  这里有一个地方需要注意,任何使用Opengl接口的方法调用需要在Opengl Context中进行,否则会出现:call to OpenGL ES API with no current context (logged once per thread) 报错信息。所谓的Opengl Context 其实就是需要在onDrawFrame(GL10 gl),onSurfaceChanged(GL10 gl, int width, int height),onSurfaceCreated(GL10 gl, EGLConfig config)中调用,注意到这三个方法都有一个参数GL10。这里还有一个地方就是在载入纹理之前需要载入位图,使用了runOnDraw()方法将loadTexure的步骤放在onDrawFrame() 中来完成,巧妙的为外界提供了一个接口并使得操作在具有Opengl Context的黄金中完成。

  最后来看看辅助的工具类(GLToolbox),该类完成Shader程序的创建,应用程序提供Shader 源码给该工具类编译:

package com.example.effectsfilterdemo;
import android.opengl.GLES20;

public class GLToolbox {

    public static int loadShader(int shaderType, String source) {
        int shader = GLES20.glCreateShader(shaderType);
        if (shader != 0) {
            GLES20.glShaderSource(shader, source);
            GLES20.glCompileShader(shader);
            int[] compiled = new int[1];
            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
            if (compiled[0] == 0) {
                String info = GLES20.glGetShaderInfoLog(shader);
                GLES20.glDeleteShader(shader);
                shader = 0;
                throw new RuntimeException("Could not compile shader " +
                shaderType + ":" + info);
            }
        }
        return shader;
    }

    public static int createProgram(String vertexSource,
            String fragmentSource) {
        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
        if (vertexShader == 0) {
            return 0;
        }
        int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
        if (pixelShader == 0) {
            return 0;
        }

        int program = GLES20.glCreateProgram();
        if (program != 0) {
            GLES20.glAttachShader(program, vertexShader);
            checkGlError("glAttachShader");
            GLES20.glAttachShader(program, pixelShader);
            checkGlError("glAttachShader");
            GLES20.glLinkProgram(program);
            int[] linkStatus = new int[1];
            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus,
                    0);
            if (linkStatus[0] != GLES20.GL_TRUE) {
                String info = GLES20.glGetProgramInfoLog(program);
                GLES20.glDeleteProgram(program);
                program = 0;
                throw new RuntimeException("Could not link program: " + info);
            }
        }
        return program;
    }

    public static void checkGlError(String op) {
        int error;
        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
            throw new RuntimeException(op + ": glError " + error);
        }
    }

    public static void initTexParams() {
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
                GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
                GLES20.GL_CLAMP_TO_EDGE);
   }

}

  这里就不提供整个工程了,结合上面的代码,自己在资源文件中提供一个图片载入就可以看到效果了。

Android OpenGL 基础入门

  Android 自从2.2 版本之后之后开始支持OpenGL,在没有支持OpenGL 的 GPU的情况下,也可以使用(通过软件来模拟)。在Android上使用Opengl操作的对象是GLSurfaceView,这是一个继承自View的扩展。

  在Android上Opengl是通过Vertex Shader 和 Fragment Shader 这两种定点着色器程序来实现图片的加载和渲染的,中文称为定点着色器和片段着色器。一个完整的Opengl程序需要创建定点着色器和片段着色器并将他们Link起来组成一个完整的OpenGL程序。

  顶点着色器的作用是为每一个顶点生成坐标,因此每个顶点都要运行一遍顶点着色器程序,一旦顶点坐标计算出来之后,OpenGL就能够使用这些顶点来组成点,线,和三角形。所有任意的图形都是由这三种基本元素来描述的。下图是顶点着色器进行坐标转换的过程(稍微有点复杂):

  Android OpenGL 基础入门

  这个过程包含了从原始的对象坐标经过模型视图转换生成眼坐标,再经过投影转换生成裁剪坐标,再通过w坐标的归一化转换成为NDC(顶点坐标由(x,y,z,w)构成,在shader程序中一般用一个四维向量vec4来描述),最终通过视口变换生成屏幕坐标显示在屏幕上。这一系列的转换都是通过矩阵来完成的,转换过程和原理是Opengl的精华内容,对于需要进入3D世界的同学需要掌握。在2D世界中我们只需要了解NDC就行了,这里就把NDC叫做OpenGL 坐标,加上Normalized 的修饰是因为这些坐标的值都在(-1,1)之间,OpenGL坐标表示见下图:

Android OpenGL 基础入门

  OpenGL坐标原点在屏幕中央,左右坐标范围为[-1,1],在2d环境中坐标值只存在(x,y),并且坐标范围都只能在-1 到 1之间,屏幕中心坐标为(0,0)。因此如果需要指定一张图片的显示位置,指定坐标需要根据这个坐标系来,此外为了保证图片显示比例(长宽比例),在portrait 到 landscape 之间变换的时候一般需要乘以一个aspectRatio (width / height) 来重新设定坐标值。

  了解了Opengl坐标,在来了解一下屏幕坐标(屏幕坐标的坐标原点在左上角)和纹理坐标(纹理坐标的坐标原点在左下角):

Android OpenGL 基础入门

屏幕坐标系                Android OpenGL 基础入门

  纹理坐标系

  片段着色器的作用是为点,线或者三角形的每一个顶点的片段(Fragment)生成渲染后的最终颜色。片段就是一个小的单色矩形区域,可以简单的认为是屏幕上的一个像素点。

  以上基本知识基本上可以处理Opengl实现2D图像的绘制和处理。下面来简单看一下Shader的写法,在Android平台上Shader程序一般以字符串的形式出现,或者在res/raw/目录下以*.glsl的格式出现,如果是以单独的文件出现,需要定义专门的文件读入接口来加载Shader程序。这里就介绍一下字符串的形式的Shader程序,来看下面简单的Vertex Shader 和 Fragment Shader:

    private static final String VERTEX_SHADER =
        "attribute vec4 a_position;/n" +
        "attribute vec2 a_texcoord;/n" +
        "varying vec2 v_texcoord;/n" +
        "void main() {/n" +
        "  gl_Position = a_position;/n" +
        "  v_texcoord = a_texcoord;/n" +
        "}/n";

    private static final String FRAGMENT_SHADER =
        "precision mediump float;/n" +
        "uniform sampler2D tex_sampler;/n" +
        "varying vec2 v_texcoord;/n" +
        "void main() {/n" +
        "  gl_FragColor = texture2D(tex_sampler, v_texcoord);/n" +
        "}/n";

  先看一下VERTEX_SHADER,定义了两种类型的变量 atrribute 和 varying。

  其中 attribute变量是只能在vertex shader中使用的变量。(它不能在fragment shader中声明attribute变量,也不能被fragment shader中使用),一般用attribute变量来表示一些顶点的数据,这些顶点数据包含了顶点坐标,法线,纹理坐标,顶点颜色等。应用中一般用函数glBindAttribLocation()来绑定每个attribute变量的位置,然后用函数glVertexAttribPointer()为每个attribute变量赋值。

  varying被称为易变量,一般用于从Vertex Shader 向 Fragment Shader传递数据,上面例子中在VertexShader中定义了attribute 类型的二维向量a_texcoord,并将该值赋值给varying类型的二维向量 v_texcoord。此外对于Vertex Shader 在main() 中必须将顶点坐标赋值给系统变量gl_Position。

  看一下FRAGMENT_SHADER,定义了两种类型的变量,uniform 和 varying。此外还多了一句 precision mediump float,这句话用于定义数据精度,Opengl中可以设置三种类型的精度(lowp,medium 和 highp),对于Vertex Shader来说,Opengl使用的是默认最高精度级别(highp),因此没有定义。

  uniform变量是APP序传递给(vertex和fragment)shader的变量。通过函数glUniform**()函数赋值的。 在(vertex和fragment)shader程序内部,uniform变量就像是C语言里面的常量(const ),它不能被shader程序修改(shader只能用,不能改)。如果uniform变量在vertex和fragment两者之间声明方式完全一样,则它可以在vertex和fragment共享使用。 (相当于一个被vertex和fragment shader共享的全局变量)uniform变量一般用来表示:变换矩阵,材质,光照参数和颜色等信息。

  对于Fragment Shader也需对gl_FragColor赋值。

  现在对基本的Shader有了了解,来看一下Android怎么使用Shader程序。先说明一下Opengl中的资源一般都是用一个句柄(handle)来引用,句柄一般由gl***接口返回,代表一个特定的资源。

  在Android上使用gl,需要用到一些列接口,这里按照一般的调用顺序来列一下基本的接口(暂不包含错误处理)

  1. 和创建Shader相关的:glCreateShader -> glShaderSource -> glCompileShader , 通过这几个接口的调用最终返回一个Shader的句柄

  2. 和创建Shader程序相关的(每个shader程序都必须包含Vertex Shader 和 Fragment Shader两部分):glCreateProgram -> glAttachShader -> glLinkProgram,通过这些接口的调用最终返回个Program的句柄。

  3. 获取Shader内部变量赋值和传递数据到gl,对于不同类型的数据使用不同的接口:GLES20.glGetUniformLocation(mProgram, “tex_sampler”),GLES20.glGetAttribLocation(mProgram, “a_texcoord”),以上两个接口中mProgram 代表glCreateProgram返回的 Shader程序句柄,“a_texcoord”和”tex_sampler”是 Vertex Shader和 Fragment Shader中定义的变量,通过这两个接口获取了变量的句柄,便于向这些变量中传入值。获得变量的句柄之后就要向其中传值,一般通过glVertexAttribPointer()来完成,详细参数这里没有列出,在实例代码中可以学习一下,传值之后需要使glEnableVertexAttribArray才能将数据传入到gl中,传入数据使用glDrawArrays() 。

  这里介绍了Opengl的基本知识,下一篇结合实例介绍一下Opengl在Android上的使用。

使用Codeblock搭建Windows下Objec-c学习环境

  学习Object-c如果使用的是Windows,一般推荐使用虚拟机,但是太重量级了,先要下载OS-X,又要下载x-code。这里推荐一种比较简便的方式,使用code-block来搭建简易的Object-c学习环境,下载地址是:http://www.codblocks.org/。

  Objective-C的编译器有很多,这里使用GnuStep,网址是http://www.gnustep.org/experience/Windows.html,从这里可以下载Windows版本的gcc编译器,共有四个软件包,其中GNUstep System和GNUstep Core是必装的,GNUstep Devel和Cairo Backend是选装的(用迅雷拖要快点)。

  下载并安装好Code-block 和 GnuStep 之后,对Code-block进行配置。

  打开Code::Blocks,点击菜单Settings>Compiler and debugger>Global compiler settings在Selected compiler下拉框下面点击Copy, 在弹出窗口中填入: GNUstep MinGW Compiler
之后,点击Toolchain executables选项卡,将Compiler’s installation directory选择为C:/GNUstep,如下图:
使用Codeblock搭建Windows下Objec-c学习环境
  

  接下来找到Other Options 选项卡,写入-fconstant-string-class=NSConstantString -std=c99 :

 

使用Codeblock搭建Windows下Objec-c学习环境

    接下来设置静态链接库,在Link settings 中进行添加,需要添加的.a文件在:C:/GNUstep/GNUstep/System/Library/Libraries: libgnustep-base.dll.a 和 libobjc.dll.a,

    使用Codeblock搭建Windows下Objec-c学习环境

 

     接下来在Search irectories 中的Compiler选项卡中增加:C:/GNUstep/GNUstep/System/Library/Headers;在Linker选项卡中增加:C:/GNUstep/GNUstep/System/Library/Libraries。这里就不截图了。

       进入Settings->Environment…,选择Files extension handling 添加*.m;进入 Project->Projecttree->Edit file types & categories… ,在Sources, 下面添加*.m到文件类型列表中。

       进入Settings->Editor…,选择 Syntaxhighlighting,点击“Filemasks….”按钮,在弹出框尾部添加*.m 到文件类型;点击“Keywords…”按钮 (紧靠Filemasks…按钮) 添加下面Object-C的关键字到EditKeywords列表中:@interface @implementation @end  @class @selector @protocol @public @protected @private id BOOL YES NO SEL nil  NULL self。

     

     最后进行测试,选择File->New->Project…,会出现一个工程类型窗口,选择Console Application,然后按照工程建立指引,建立一个mTest的工程,并将main.c的文件更名为main.m,写入代码:

#import <Foundation/Foundation.h>
int main (int argc, const char * argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSDate *now = [NSDate date];
    NSLog(@"This NSDate object lives at %p", now);
    NSLog(@"The date is %@", now);

    [pool drain];
    return 0;
}

  编译运行上述代码,结果如下:

  使用Codeblock搭建Windows下Objec-c学习环境

 

Muduo 多线程模型对比

  本文主要对比Muduo多线程模型方案8 和方案9 。

  方案8:reactor + thread pool ,有一个线程来充当reactor 接受连接分发事件,将要处理的事件分配给thread pool中的线程,由thread pool 来完成事件处理。实例代码见:examples/sudoku/server_threadpool.cc

  这里截取关键部分代码进行说明。

class SudokuServer
{
 public :
  SudokuServer(EventLoop* loop, const InetAddress& listenAddr, int numThreads)
    : loop_(loop),
      server_(loop, listenAddr, “SudokuServer”),
      numThreads_(numThreads),
      startTime_(Timestamp::now())
  {
    server_.setConnectionCallback(
        boost::bind(&SudokuServer::onConnection, this, _1));
    server_.setMessageCallback(
        boost::bind(&SudokuServer::onMessage, this, _1, _2, _3));
  }
 
  void start()
  {
    LOG_INFO << “starting “ << numThreads_ << ” threads.”;
    threadPool_.start(numThreads_); // 注意这里,threadPool 的类型是: ThreadPool,且位置在start 里面
    server_.start();
  }
 
 private :
  void onConnection(const TcpConnectionPtr& conn)
  {
    LOG_TRACE << conn->peerAddress().toIpPort() << ” -> “
        << conn->localAddress().toIpPort() << ” is “
        << (conn->connected() ? “UP” : “DOWN”);
  }
 
  void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp)
  {
        if (!processRequest(conn, request)) // 封装计算任务执行方法
        {
          conn->send( “Bad Request!/r/n”);
          conn->shutdown();
          break;
        }
      }
    }
  }
 
  bool processRequest(const TcpConnectionPtr& conn, const string& request)
  {
 
    if (puzzle.size() == implicit_cast<size_t>(kCells))
    {
      threadPool_.run(boost::bind(&solve, conn, puzzle, id));// 将计算任务转移到 threadPool 线程
    }
    else
    {
      goodRequest = false;
    }
    return goodRequest;
  }
 
  static void solve(const TcpConnectionPtr& conn,
                    const string& puzzle,
                    const string& id)
  {
    LOG_DEBUG << conn->name();
    string result = solveSudoku(puzzle); // solveSudou 是一个pure function, 是可重入的 
    if (id.empty())
    {
      conn->send(result+ “/r/n”);
    }
    else
    {
      conn->send(id+ “:”+result+ “/r/n”);
    }
  }
 
  EventLoop* loop_;
  TcpServer server_;
  ThreadPool threadPool_; // 注意类型,方案8, reactor + threadpool
  int numThreads_;
  Timestamp startTime_;
};
 
void ThreadPool::start( int numThreads)  // 创建 thread pool,具体thread 调度这里暂时不分析
{
  assert(threads_.empty());
  running_ = true;
  threads_.reserve(numThreads);
  for (int i = 0; i < numThreads; ++i)
  {
    char id[32];
    snprintf(id, sizeof id, “%d”, i);
    threads_.push_back( new muduo::Thread(
          boost::bind(&ThreadPool::runInThread, this), name_+id));
    threads_[i].start();
  }
}
 
方案9:main-reactor + subreactors, one loop per thread, 有一个主线程来扮演main-reactor 专门语句 accept 连接,其它线程负责读写文件描述符(socket)
 
class SudokuServer
{
 public :
  SudokuServer(EventLoop* loop, const InetAddress& listenAddr, int numThreads)
    : loop_(loop),
      server_(loop, listenAddr, “SudokuServer”),
      numThreads_(numThreads),
      startTime_(Timestamp::now())
  {
    server_.setConnectionCallback(
        boost::bind(&SudokuServer::onConnection, this, _1));
    server_.setMessageCallback(
        boost::bind(&SudokuServer::onMessage, this, _1, _2, _3));
    server_.setThreadNum(numThreads); // 设置 EventLoopThreadPool里面的thread数量
  }
 
  void start()
  {
    LOG_INFO << “starting “ << numThreads_ << ” threads.”;
    server_.start();
  }
 
 private :
  void onConnection(const TcpConnectionPtr& conn)
  {
    LOG_TRACE << conn->peerAddress().toIpPort() << ” -> “
        << conn->localAddress().toIpPort() << ” is “
        << (conn->connected() ? “UP” : “DOWN”);
  }
 
  void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp)
  {
        if (!processRequest(conn, request)) //准备计算
        {
          conn->send( “Bad Request!/r/n”);
          conn->shutdown();
          break;
        }
    }
  }
 
  bool processRequest(const TcpConnectionPtr& conn, const string& request)
  {
    if (puzzle.size() == implicit_cast<size_t>(kCells))
    {
      LOG_DEBUG << conn->name();
      string result = solveSudoku(puzzle); // 计算在当前线程完成
      if (id.empty())
      {
        conn->send(result+ “/r/n”);
      }
  }
  // 注意这里没有类型为ThreadPool的 threadPool_成员,整个类使用Muduo默认线程模型的EventLoopThreadPool,TcpServer 聚合了EventLoopThreadPool
  EventLoop* loop_;
  TcpServer server_;
  int numThreads_;
  Timestamp startTime_;
};
 
 
void TcpServer::setThreadNum( int numThreads)
{
  assert(0 <= numThreads);
  threadPool_->setThreadNum(numThreads); // 设置了 EventLoopThreadPool 里面的线程个数,为后面的threadPool_->start()服务
}
 
void TcpServer::start()
{
  if (!started_)
  {
    started_ = true;
    threadPool_->start(threadInitCallback_); // TcpServer 中的 threadPool 类型是 EventLoopThreadPool
  }
 
  if (!acceptor_->listenning())
  {
    loop_->runInLoop(
        boost::bind(&Acceptor::listen, get_pointer(acceptor_)));
  }
}
 
void EventLoopThreadPool::start( const ThreadInitCallback& cb) // 开启线程的方式是使用EventLoopThread,这个类将EventLoop 和 Thread 封装在一起实现 one loop per thread
{
  assert(!started_);
  baseLoop_->assertInLoopThread();
 
  started_ = true;
 
  for (int i = 0; i < numThreads_; ++i)
  {
    EventLoopThread* t = new EventLoopThread(cb); // 设置线程的 callback
    threads_.push_back(t); 
    loops_.push_back(t->startLoop()); // 保存loop方便管理和分配任务,任务分配其实是通过EventLoop::runInLoop() 来进行的
  }
  if (numThreads_ == 0 && cb)
  {
    cb(baseLoop_);
  }
}
 
  总结一下,这里所谓的Reactor就是持有Poller的结构(稍微有点狭隘,这里先就这样理解),Poller负责事件监听和分发。持有EventLoop的结构就持有Poller。
  对于方案8只有一个类持有EventLoop,也就是只创建了一个EventLoop,这个Loop就是reactor,其它的Thread 是通过ThreadPool来实现的,因此只有reactor所在的线程来完成I/O,其它线程用于完成计算任务,所以说这个模型适合于计算密集型而不是I/O密集型。
  对于方案9,存在多个Reactor,其中main reactor 持有Acceptor,专门用于监听三个半事件中的连接建立,消息到达和连接断开以及消息发送事件都让sub reactor来完成。由于main reactor 只关心连接建立事件,能够适应高并发的IO请求,多个subreactor的存在也能兼顾I/O与计算,因此被认为是一个比较好的方案。
   后面还会深入学习Muduo网络库相关的内容,包括Reactor结构的简化,线程池的实现,现代C++的编写方式,使用C++11进行重写等。现在看来C++11 thread library 提供的接口基本可以替换 posix thread library,虽然底层也许是通过posix thread实现的,毕竟Linux内核针对NPTL进行过修改。C++11 提供了 thread_local 来描述 线程局部存储,但是没有pthread_key_create() 提供 destructor那样的功能,或者遇到需要使用TLS的地方转过来使用posix 提供的接口。
 
Muduo 多线程 线程池 reactor

NPTL 线程同步方式

  NPTL提供了互斥体 pthread_mutex_t 类型进行线程同步,防止由于多线程并发对全局变量造成的不正确操作。使用 pthread_mutext_t 对数据进行保护已经可以实现基本的数据同步,NPTL又提供了pthread_cond_t 条件变量与pthread_mutext_t一起使用实现高效的线程同步保护数据。有了互斥变量pthread_mutext_t为什么还要引入条件变量pthread_cond_t呢? 原因就是防止CPU空转,一个线程获得互斥量之后,另外一个线程如果想获取该互斥量,就会不断的去查询这个互斥量是否已经空闲可以被自己占用,于是浪费了CPU周期。引入条件变量pthread_cond_t之后,如果条件不满足,线程进入睡眠状态,不会浪费CPU周期。

  NPTL进行线程同步的一般结构如下:

thread 1:
    pthread_mutex_lock(&mutex);
    while (!condition)
        pthread_cond_wait(&cond, &mutex);
    /* 实际操作,修改condition为无效 */
    pthread_mutex_unlock(&mutex);

thread2:
    pthread_mutex_lock(&mutex);
    /* 实际操作,修改condition为有效 */
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);

  标准代码结构是像上面这样,针对上面的结构提几个问题?

  1. 为什么要将pthread_cond_wait 放在while(!condition)循环内呢,为什么要有while(!condition)的存在呢?

    2. pthread_cond_t 为什么要和 pthread_mutex_t 一起使用呢,使用pthread_cond_signal的线程不使用pthread_mutext 行不行? 

 

  在回答上面的问题之前先介绍一下最核心的pthread_cond_wait(&cond, &mutex)在不同情况下都会干些什么。

  1. 程序运行到pthread_cond_wait() 条件发生,代码继续向下执行。

  2. 程序运行到pthread_cond_wait() 条件未发生,函数调用首先会释放mutex(打开锁),并使当前线程进入睡眠状态。

  3. 睡眠在pthread_cond_wait()上的线程被signal唤醒,pthread_cond_wait()首先去获得锁(尝试重新获得该mutex直到获得)。

 

  pthread_cond_wait()的行为为下面的讨论做一个铺垫。现在来考虑回答上面的问题,我们可以从多线程乱序执行做为切入点,thread1 有可能比thread2 先执行,thread2 也有可能比thread1先执行。

  1.首先考虑,如果thread2先执行并且已经执行到 pthread_cond_signal() 但是thread1甚至都还没有运行,更别说进入到pthread_cond_wait()状态,这时候没有 while(!condition) 会怎么样?

    显然thread2已经发送了singal了,但是没有接收者,此时出现了丢信号的情况,即如果没有 while(!condition) 当thread1进入到pthread_cond_wait()的时候就会睡眠,唤醒信号丢失的情况发生,在这种情况下如果有 while(!condition) 的存在则不会执行pthread_cond_wait() 直接执行下面的代码。

    那么用 if(!condtion) 不是也可以解决上面的问题吗? 不错是可以解决上面的问题,但是会带来新的问题。考虑这种情况:如果signal同时唤醒了多个wait在该条件上的线程(pthread_cond_broadcast 或者出现传说中的“惊群”效应),那使用if(!condtion) 就是不行的。 这是因为,各个多个被唤醒的线程肯定会有一个会先进入被这个mutex保护的临界区(回忆上面介绍的pthread_cond_wait()函数在线程醒来之前会尝试去持有锁直到持有为止),Linux上规定是低优先级的线程先获得该mutex,然后进行了操作,并修改了condition变量,释放了mutex,此时另一个正在睡眠中但同时也在尝试获取该mutex的线程被唤醒,然后直接就向下执行,此时就会导致多线程同步失败。如果使用while(!condtion)再次进行检查则不会出现同步失败的问题。      

  2.第二个问题相对简单,如果cond不和mutex一起使用,那么任何可以访问cond的线程都可能唤醒睡眠在某个mutex上的线程,所以需要mutex对cond的保护,以确保有资格的线程才能对某个线程进行唤醒操作。

  理解条件变量的关键还是需要理解pthread_cond_wait()都干了些什么!

   

NPTL 多线程同步  条件变量 互斥变量 Linux

C++ 封装互斥对象

  多线程程序中为了防止线程并发造成的竞态,需要经常使用到Mutex进行数据保护。posix提供了phtread_mutex_t进行互斥保护数据。Mutex的使用需要初始化和释放对应(phtread_mutex_init() 和 phtread_mutex_destroy() 对应),上锁和解锁对应(phtread_mutex_lock 和 pthread_mutex_unlock对应)。lock和unlock的过程是设计逻辑的一部分一般都程序员都能正确的进行加锁和解锁对应,但是要防止lock之后程序出现异常或者提前return而没有unlock。初始化mutex之后不释放也会造成资源泄漏,也是很容易遗漏的地方。在实际开发中一般都需要自己封装一下Mutex。

class MutexLock 
{
 public:
  MutexLock() : holder_(0)
  {
    int ret = pthread_mutex_init(&mutex_, NULL);
    assert(ret == 0); 
  }

  ~MutexLock()
  {
    assert(holder_ == 0);
    int ret = pthread_mutex_destroy(&mutex_);
    assert(ret == 0); 
  }

  bool isLockedByThisThread() const
  {
    return holder_ == static_cast<pid_t>(::syscall(SYS_gettid));
  }

  void assertLocked() const
  {
    assert(isLockedByThisThread());
  }

  void lock()
  {
    pthread_mutex_lock(&mutex_);
    assignHolder();
  }

  void unlock()
  {
    unassignHolder();
    pthread_mutex_unlock(&mutex_);
  }

  pthread_mutex_t* getPthreadMutex()
  {
    return &mutex_;
  }

 private:
  
  MutexLock(const MutexLock &);
  MutexLock &operator=(const MutexLock &);

  void unassignHolder()
  {
    holder_ = 0;
  }

  void assignHolder()
  {
    holder_ = static_cast<pid_t>(::syscall(SYS_gettid));
  }

  pthread_mutex_t mutex_;
  pid_t holder_;
};


class MutexLockGuard 
{
 public:
  explicit MutexLockGuard(MutexLock& mutex)
    : mutex_(mutex)
  {
    mutex_.lock();
  }

  ~MutexLockGuard()
  {
    mutex_.unlock();
  }

 private:
  MutexLockGuard(const MutexLockGuard &);
  MutexLockGuard &operator=(const MutexLockGuard &);

  MutexLock& mutex_;
};

  为了提高MutextLock的易用性,增加了一个MutexLockGuard 类来封装MutextLock,实际使用的时候直接使用MutexLockGuard,这样就能防止忘记释放Mutex的情况出现(MutexLockGuard 超出作用域(一般是一个栈上变量)就会自动释放,调用析构函数,destroy掉mutex)。

  以上封装其实就是所谓的RAII的一个具体实践,C++中的智能指针shared_ptr,weak_ptr,unique_ptr 也是RAII的优秀实现。

  注:其实在C++11 线程库中已经有lock guard可以直接使用了(std::lock_guard ,只需要include<mutex>),不需要自己再写一遍,对于没有迁移到C++11上的项目可以使用自己封装的Mutex来提高易用性。

 

Java 常用字符串操作总结

1. String转ASCII码

    
public static String stringToAscii(String value) {
        StringBuffer sbu = new StringBuffer();
        char[] chars = value.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            if (i <= chars.length - 1) {
                int ii = chars[i];
                sbu.append(Integer.toHexString(ii));
            } else {
                sbu.append((int) chars[i]);
            }
        }
        return sbu.toString();
    } 

2. ASCII码转String

    
public static String asciiToString(String value) {
        if("".equals(value) || value == null){
            return "";
        }
        StringBuffer sBuffer = new StringBuffer();
        char[] chars = value.toCharArray();
        for (int i = 0; i < chars.length; i = i + 2) {
            if (i < chars.length - 1) {
                int ii = Integer.valueOf(
                    Character.toString(chars[i]) + Character.toString(chars[i + 1]), 16);
                sBuffer.append((char) (ii));
            }
        }
        return sBuffer.toString();
    }

 

3. 输入流转字节数组

    
public static String inputStream2Byte(InputStream inputStream) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = -1;
        while ((len = inputStream.read(buffer)) != -1) {
            bos.write(buffer, 0, len);
        }
        bos.close();
        return new String(bos.toByteArray(), "UTF-8");
    }

4. 输入流转字符串

 

    
public static String InputStreamTOString(InputStream in) throws Exception {
        
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        byte[] data = new byte[1024];
        int count = -1;
        while ((count = in.read(data, 0, 2048)) != -1)
            outStream.write(data, 0, count);
        
        data = null;
        return new String(outStream.toByteArray());
    }

 

5. 判断输入是不是包含中文

   
 public static boolean isChinese(char c) {
        Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
        if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
                || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B
                || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS
                || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION) {
            return true;
        }
        return false;
    }
 
    public static boolean isChinese(String strName) {
        char[] ch = strName.toCharArray();
        for (int i = 0; i < ch.length; i++) {
            char c = ch[i];
            if (isChinese(c)) {
                return true;
            }
        }
        return false;
    }

  

6. 检验子网掩码的合法性:子网掩码转化为二进制之后不能全是1,也不能全是0,必须是连续的1或者连续的0,也就是说1和0不能交替出现。

  实现方式,先将4个整数字段按照每八位拼接起来,软后转化为二进制显示方式,使用正则表达式进行匹配。

  
  public static boolean checkMask(String maskStr){
        String[] ips = maskStr.split("//.");  
        String binaryVal = "";  
        for (int i = 0; i < ips.length; i++)  
        {  
            String binaryStr = Integer.toBinaryString(Integer.parseInt(ips[i]));    
            Integer times = 8 - binaryStr.length();  
              
            for(int j = 0; j < times; j++)  
            {  
                binaryStr = "0" +  binaryStr;  //补齐八位,每次需要进行八位合并
            }  
            binaryVal += binaryStr;  
        }  
        Pattern regxPattern = Pattern.compile("^[1]*[0]*$");
        if(regxPattern.matcher(binaryVal).matches())  
        {  
            return true;  
        }else {
            return false;
        }
    }

 

7. 检查IP的合法性,并转化为整数表示

    
public static boolean validIP(String ip) {
        if (ip == null) {
            return false;
        }
     String IP_REGEX  = "//b((?!//d//d//d)//d+|1//d//d|2[0-4]//d|25[0-5])//."
                                                       + "((?!//d//d//d)//d+|1//d//d|2[0-4]//d|25[0-5])//."
                                                       + "((?!//d//d//d)//d+|1//d//d|2[0-4]//d|25[0-5])//."
                                                       + "((?!//d//d//d)//d+|1//d//d|2[0-4]//d|25[0-5])//b";
        Pattern     ipPattern           = Pattern.compile(IP_REGEX);
        Matcher matcher = ipPattern.matcher(ip);
        return matcher.matches();
    }

    public static Integer ipString2Int(String strIp){
        if(!validIP(strIp)) return null;

        int[] ip = new int[4];
        String[] ips = strIp.split("//.");

        ip[0] = Integer.parseInt(ips[0]);
        ip[1] = Integer.parseInt(ips[1]);
        ip[2] = Integer.parseInt(ips[2]);
        ip[3] = Integer.parseInt(ips[3]);
        return (ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3];
    }

  

  

 

  

 

Android 开发有用代码积累

  Android开发需求变化快,开发周期要求尽量短,接下来一系列文章从实际使用出发总结一些常用的代码片段,便于查找,也为后来人提供一份参考。

1.获取Manifest的基本信息(升级页面和软件关于页面一般会使用到)


Context mContext = XXXApplication.getInstance().getApplicationContext(); //获取Application的Context ,当然也可以获取当前的Activity的Context, Application一般是单例 packageName = mContext.getPackageName(); //获取包名,也就是manifest中的package选项的值 PackageInfo info = mContext.getPackageManager().getPackageInfo( mContext.getPackageName(), 0); //

String versionName = info.versionName;
int versionCode = info.versionCode;

2.获取手机屏幕参数(对于屏幕适配很重要,毕竟Android手机的屏幕种类太多了)


Resources resources = XXXApplication.getInstance().getResources(); float scale = resources.getDisplayMetrics().density;//屏幕密度因子,用于在px与dp之间转化 float scaledDensity = resources.getDisplayMetrics().scaledDensity; float disPlayWidth = resources.getDisplayMetrics().widthPixels; float disPlayHeight = resources.getDisplayMetrics().heightPixels;

  public static int dip2px(float dp) {
    return (int) (dp * scale + 0.5f);
  }

  public static int px2dip(float px) {
  return (int) (px / scale + 0.5f);
  }

  后面专门用篇文章来介绍一下我对不同屏幕适配的心得,今天就先介绍上面的代码。

 

3. 隐藏软键盘

 
InputMethodManager imm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);


//在含有EditText的页面,有可能一进入该Activity软键盘就弹出来,可以通过以下方法来禁止

 getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);

  

4. 安装APK(比如说下载了升级软件的时候,需要安装替换旧版)


File apkfile = new File(apkFilePath); if (!apkfile.exists()) { return; } Intent i = new Intent(Intent.ACTION_VIEW); i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); i.setDataAndType(Uri.parse("file://" + apkfile.toString()), "application/vnd.android.package-archive"); mContext.startActivity(i);

 

5. 设置TextView的文字大小

  TextView通过getTextSize()返回的值是px,setTextSize()却是sp,所以一般不能直接用getTextSize()获取的值来设置。可以指定setTextSize()的单位:

  TypedValue.COMPLEX_UNIT_PX : PX

  TypedValue.COMPLEX_UNIT_SP : SP

  TypedValue.COMPLEX_UNIT_DIP : DIP

  可以这样使用:

 mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.text_size)); //使用getDimension 是为了屏幕适配,这里先点到为止

  

6. 手机震动功能

import android.app.Activity;  
import android.app.Service;  
import android.os.Vibrator;  
  
public class Myvibrator {   
    public static void Vibrate(final Activity activity, long milliseconds) {  
        Vibrator vib = (Vibrator) activity.getSystemService(Service.VIBRATOR_SERVICE);  
        vib.vibrate(milliseconds);  
    }  
     public static void Vibrate(final Activity activity, long[] pattern,boolean isRepeat) {  
         Vibrator vib = (Vibrator) activity.getSystemService(Service.VIBRATOR_SERVICE);  
         vib.vibrate(pattern, isRepeat ? 1 : -1);  
     }  
 }  

  在manifest文件中需要增加权限:<uses-permission android:name=”android.permission.VIBRATE” />  。通过上面操作,我们可以使用Myvibrator所定义的函数了。两个Vibrate函数的参数简单介绍如下:

  final Activity activity :调用该方法的Activity实例
  long milliseconds :震动的时长,单位是毫秒

  long[] pattern :自定义震动模式 。数组中数字的含义依次是[静止时长,震动时长,静止时长,震动时长。。。]时长的单位是毫秒

  boolean isRepeat : 是否反复震动,如果是true,反复震动,如果是false,只震动一次

7. 软键盘弹出不把整个布局顶起来

  在manifest文件对应的activity中设置:android:windowSoftInputMode=”adjustPan” 属性

8. 设置Activity为横屏或者竖屏

  在manifest文件对应的activity中设置:android:screenOrientation=”portrait” (竖屏,landscape 代表横屏) 

 

C++ async task

  最近在搞Android 开发,里面多线程的使用比较频繁,java多线程接口很方便。 Thread, AysncTask, Handler 这些接口比起posix提供的pthread_create()等一系列接口方便很多,想到C++11也支持方便的多线程编程,最近java中AsyncTask用的比较多,于是学习了一下C++中的async task。

  

  C++ std::async(),原型如下:  

  unspecified policy (1) 

        template <class Fn, class… Args>
          future<typename result_of<Fn(Args…)>::type> async(Fn&& fn, Args&&… args);

  specific policy (2)
        template <class Fn, class… Args>
          future<typename result_of<Fn(Args…)>::type> async(launch policy, Fn&& fn, Args&&… args);
  

      std::async() 的 fn 和 args 参数用来指定异步任务及其参数。另外,std::async() 返回一个 std::future 对象,通过该对象可以获取异步任务的值或异常(如果异步任务抛出了异常)。

  上面两组 std::async() 的不同之处是第一类 std::async 没有指定异步任务(即执行某一函数)的启动策略(launch policy),而第二类函数指定了启动策略,详见 std::launch 枚举类型,指定启动策略的函数的 policy 参数可以是 launch::async,launch::deferred,以及两者的按位或( | )。

  来一段代码学习一下:

 1 #include "stdafx.h"
 2 
 3 
 4 #include <stdio.h>
 5 #include <stdlib.h>
 6 
 7 #include <cmath>
 8 #include <chrono>
 9 #include <future>
10 #include <iostream>
11 
12 
13 int main(int argc, const char *argv[])
14 {
15     auto begin = std::chrono::steady_clock::now();
16 
17         //std::future<double> 
18     auto f(std::async(std::launch::async,[](int n){
19 std::cout << std::this_thread::get_id() 20 << " start computing..." << std::endl; 21 22 double ret = 0; 23 for (int i = 0; i <= n; i++) { 24 ret += std::sin(i); 25 } 26 27 std::cout << std::this_thread::get_id() 28 << " finished computing..." << std::endl; 29 return ret; 30 } 31 ,100000000)); 32 33 34 while(f.wait_for(std::chrono::seconds(1)) 35 != std::future_status::ready) { 36 std::cout << "task is running.../n"; 37 } 38 39 40 auto end = std::chrono::steady_clock::now(); 41 42 auto diff = end - begin; 43 44 std::cout << "async_task result: "<<f.get() << std::endl; 45 std::cout << std::chrono::duration <double, std::milli> (diff).count() << " ms" << std::endl; 46 47 return EXIT_SUCCESS; 48 }

运行结果:(VS2012,ubuntu14.04 使用的是gcc4.9.1 也可以毫无压力运行)

C++ async task