Android加载上百张图片做动画出现OOM(内存溢出)解决方案

 更新时间:2016年9月20日 19:54  点击:1835
有一个Android应用中要做一个有几百张图片的帧动画(恶心),这样很容易出现android内存溢出,本文我们来谈谈防止OOM的解决方案。

帧动画

一开始我的想法是直接用帧动画来做,可是我太天真了,当帧数放到 50 几张的时候,已经在有些机器上奔溃了!所以这个方案否决!

GIF动图

虽然可以显示,但是已经卡的我,已经不想看了,直接放弃

视频

在这里,我突然想到我可以直接把他做成一个小视频啊,而且可以极限压缩视频。最终,视频大小被压缩到 500K 左右。此时已经基本可以满足需求了,但是我们有好多类似的动画,要求在每个动画切换的时候要有衔接感,不能有突兀的感觉,所有在这里视频就不能很好的完成任务了,所有再次放弃,已经泪牛满面了!!!!

SurfaceView + BitmapRegionDecoder +缓存

首先回答一下:为什么会想到这个解决方案?

    首先在做帧动画的时候,大约每帧之间的时间差值是 40ms 可以说速度非常快了,在如此快速的图片切换上,自然而然的想到来了使用SurfaceView。
    现在再来说说为什么想到要使用这个类 BitmapRegionDecoder .这个也是从我司游戏开发人员那儿得到的经验?他们在做游戏的时候,游戏中的切图都是放在一张大图上的,然后在根据对应的 xml,json 文件,获取相应的图片,接着再来切图。对此,我想能不能把所有的动图都放到同一张的图片上呢,之后在根据对应的描述文件,裁剪出我想要的图片呢!所以就用到了 BitmapRegionDecoder. 它的作用是:于显示图片的某一块矩形区域!之后,我在找设计人员商量一一下,把图片在尽量的压缩。之后从美工那儿获取的信息是这样的:


    json格式的描述文件:


{"frames": [

{
    "filename": "kidbot-正常闭眼0000",
    "frame": {"x":0,"y":0,"w":360,"h":300},
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": {"x":0,"y":0,"w":360,"h":300},
    "sourceSize": {"w":360,"h":300}
}
.....
}

png图片:

图片

接下来就好做了,解析 json 格式的文件,裁剪图片。

    最后说一下为什么使用缓存,其实很简单,因为切换的频率实在太高了,没有必要每次都从图片中裁剪,这里就把裁剪出来的 bitmap 缓存起来在用。从而介绍内存开销!

最后给出代码:

public class AnimView extends SurfaceView implements SurfaceHolder.Callback {
    private BitmapRegionDecoder bitmapRegionDecoder;
    private SurfaceHolder mHolder;
    private boolean isrunning = true;
    private AnimThread thread;
    private Paint mPaint;
    private int WIDTH = 0;
    private int HEIGHT = 0;
    private int state = -1;
    private boolean isstart = false;
    private boolean isblinkfirst = false;
    private int rate = 40;
    private int index = 0;
    private Matrix matrix;
    private Random rand;
    private Handler handler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            isblinkfirst = true;
        };
    };
    private SparseArray<WeakReference<Bitmap>> weakBitmaps;
    private SparseArray<WeakReference<Bitmap>> cweakBitmaps;
    private BitmapFactory.Options options;
    public AnimView(Context context) {
        super(context);
        init();
    }
    public AnimView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    public AnimView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    @SuppressLint("NewApi")
    private void init() {
        weakBitmaps = new SparseArray<WeakReference<Bitmap>>();
        cweakBitmaps = new SparseArray<WeakReference<Bitmap>>();
        mHolder = getHolder();
        mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        setState(FaceBean.BLINK);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        matrix = new Matrix();
        float[] values = { -1f, 0.0f, 0.0f, 0.0f, 1f, 0.0f, 0.0f, 0.0f, 1.0f };
        matrix.setValues(values);
        WindowManager manger = (WindowManager) getContext().getSystemService(
                Context.WINDOW_SERVICE);
        DisplayMetrics displayMetrics = new DisplayMetrics();
        manger.getDefaultDisplay().getMetrics(displayMetrics);
        WIDTH = displayMetrics.widthPixels / 2;
        HEIGHT = displayMetrics.heightPixels / 2;
        rand = new Random();
        options = new Options();
        options.inPreferredConfig = Bitmap.Config.RGB_565;
    
    }
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        handler.sendEmptyMessageDelayed(0, 1000 * (4 + rand.nextInt(4)));
        thread = new AnimThread();
        thread.start();
    }
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
    }
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (thread != null) {
            thread.stopThread();
        }
    }
    public class AnimThread extends Thread {
        @Override
        public void run() {
            super.run();
            SurfaceHolder holder = mHolder;
            while (isrunning) {
                Canvas canvas = holder.lockCanvas();
                if (canvas == null)
                    continue;
                synchronized (AnimThread.class) {
                    AnimBean.Frames frames;
                    switch (state) {
                    case FaceBean.BLINK:
                        frames = KidbotRobotApplication.animBlink.getFrames()
                                .get(index);
                        if (frames.getFrame().getW() <= 0) {
                        } else {
                            Rect rect = new Rect(frames.getFrame().getX(),
                                    frames.getFrame().getY(), frames.getFrame()
                                            .getX()
                                            + frames.getSourceSize().getW(),
                                    frames.getFrame().getY()
                                            + frames.getSourceSize().getH());
                            WeakReference<Bitmap> weakBitmap = weakBitmaps
                                    .get(index);
                            Bitmap map = null;
                            if (weakBitmap == null) {
                                map = bitmapRegionDecoder.decodeRegion(rect,
                                        options);
                                weakBitmaps.put(index,
                                        new WeakReference<Bitmap>(map));
                            } else {
                                map=weakBitmap.get();
                                if (map == null) {
                                    map = bitmapRegionDecoder.decodeRegion(
                                            rect, options);
                                    weakBitmaps.put(index,
                                            new WeakReference<Bitmap>(map));
                                }
                            }
                            if (map == null) {
                                holder.unlockCanvasAndPost(canvas);
                                continue;
                            }
                            mPaint.setXfermode(new PorterDuffXfermode(
                                    Mode.CLEAR));
                            canvas.drawPaint(mPaint);
                            mPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
                            canvas.drawBitmap(map,
                                    (int) (WIDTH - (map.getWidth() * 1) - 150),
                                    (int) (HEIGHT - (map.getHeight() / 2)),
                                    mPaint);
                            canvas.drawBitmap(map, (int) (WIDTH + 150),
                                    (int) (HEIGHT - (map.getHeight() / 2)),
                                    mPaint);
                            if (index == 0) {
                            }
                            if (map.isRecycled()) {
                                map.recycle();
                            }
                        }
                        if (!isstart) {
                            if (index < KidbotRobotApplication.animBlink
                                    .getFrames().size()) {
                                index++;
                                if (index == KidbotRobotApplication.animBlink
                                        .getFrames().size()) {
                                    index--;
                                    isstart = true;
                                    if (rand.nextInt(10) <= 2) {
                                        index = 1;
                                    }
                                }
                            } else {
                                index--;
                                isstart = true;
                            }
                        } else {
                            if (index > 0) {
                                index--;
                                if (index == 0) {
                                    isstart = false;
                                }
                            } else {
                                index++;
                                isstart = false;
                            }
                        }
                        if (!isblinkfirst) {
                            index = 0;
                        } else {
                            if (index == KidbotRobotApplication.animBlink
                                    .getFrames().size() - 1) {
                                isblinkfirst = false;
                                index = 0;
                                handler.sendEmptyMessageDelayed(0,
                                        1000 * (4 + rand.nextInt(4)));
                            }
                        }
                        break;
                    case FaceBean.ANGRY:
                        frames = KidbotRobotApplication.animAngry.getFrames()
                                .get(index);
                        if (frames.getFrame().getW() <= 0) {
                        } else {
                            Rect rect = new Rect(frames.getFrame().getX(),
                                    frames.getFrame().getY(), frames.getFrame()
                                            .getX() + frames.getFrame().getW(),
                                    frames.getFrame().getH()
                                            + frames.getFrame().getX());
                            WeakReference<Bitmap> weakBitmap = weakBitmaps
                                    .get(index);
                            Bitmap map = null;
                            if (weakBitmap == null) {
                                map = bitmapRegionDecoder.decodeRegion(rect,
                                        options);
                                weakBitmaps.put(index,
                                        new WeakReference<Bitmap>(map));
                            } else {
                                map=weakBitmap.get();
                                if (map == null) {
                                    map = bitmapRegionDecoder.decodeRegion(
                                            rect, options);
                                    weakBitmaps.put(index,
                                            new WeakReference<Bitmap>(map));
                                }
                            }
                            if (map == null) {
                                holder.unlockCanvasAndPost(canvas);
                                continue;
                            }
                            mPaint.setXfermode(new PorterDuffXfermode(
                                    Mode.CLEAR));
                            canvas.drawPaint(mPaint);
                            mPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
                            Bitmap dstbmp =null;
                            weakBitmap=cweakBitmaps.get(index);
                            if(weakBitmap==null){
                                dstbmp = Bitmap.createBitmap(map, 0, 0,
                                        map.getWidth(), map.getHeight(),
                                        matrix, true);
                                cweakBitmaps.put(index,
                                        new WeakReference<Bitmap>(dstbmp));
                            }else{
                                dstbmp=weakBitmap.get();
                                if(dstbmp==null){
                                    dstbmp = Bitmap.createBitmap(map, 0, 0,
                                            map.getWidth(), map.getHeight(),
                                            matrix, true);
                                    cweakBitmaps.put(index,
                                            new WeakReference<Bitmap>(dstbmp));
                                }
                            }
                            canvas.drawBitmap(
                                    map,
                                    frames.getSpriteSourceSize().getX()
                                            + (int) (WIDTH
                                                    - (map.getWidth() * 1) - 150),
                                    frames.getSpriteSourceSize().getY()
                                            + (int) (HEIGHT - (map.getHeight() / 2)),
                                    mPaint);
                            canvas.drawBitmap(dstbmp, frames
                                    .getSpriteSourceSize().getX()
                                    + (int) (WIDTH + 150), frames
                                    .getSpriteSourceSize().getY()
                                    + (int) (HEIGHT - (map.getHeight() / 2)),
                                    mPaint);
                            if (dstbmp.isRecycled()) {
                                dstbmp.recycle();
                            }
                            if (map.isRecycled()) {
                                map.recycle();
                            }
                        }
                        if (!isstart) {
                            if (index < KidbotRobotApplication.animAngry
                                    .getFrames().size()) {
                                index++;
                                if (index == KidbotRobotApplication.animAngry
                                        .getFrames().size()) {
                                    index--;
                                    isstart = true;
                                }
                            } else {
                                index--;
                                isstart = true;
                            }
                        } else {
                            if (index > 0) {
                                index--;
                                if (index == 0) {
                                    isstart = false;
                                }
                            } else {
                                index++;
                                isstart = false;
                            }
                        }
                        break;
                    case FaceBean.HAPPY:
                        frames = KidbotRobotApplication.animHappy.getFrames()
                                .get(index);
                        if (frames.getFrame().getW() <= 0) {
                        } else {
                            Rect rect = new Rect(frames.getFrame().getX(),
                                    frames.getFrame().getY(), frames.getFrame()
                                            .getX()
                                            + frames.getSourceSize().getW(),
                                    frames.getFrame().getY()
                                            + frames.getSourceSize().getH());
                            WeakReference<Bitmap> weakBitmap = weakBitmaps
                                    .get(index);
                            Bitmap map = null;
                            if (weakBitmap == null) {
                                map = bitmapRegionDecoder.decodeRegion(rect,
                                        options);
                                weakBitmaps.put(index,
                                        new WeakReference<Bitmap>(map));
                            } else {
                                map=weakBitmap.get();
                                if (map == null) {
                                    map = bitmapRegionDecoder.decodeRegion(
                                            rect, options);
                                    weakBitmaps.put(index,
                                            new WeakReference<Bitmap>(map));
                                }
                            }
                            if (map == null) {
                                holder.unlockCanvasAndPost(canvas);
                                continue;
                            }
                            mPaint.setXfermode(new PorterDuffXfermode(
                                    Mode.CLEAR));
                            canvas.drawPaint(mPaint);
                            mPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
                            Bitmap dstbmp =null;
                            weakBitmap=cweakBitmaps.get(index);
                            if(weakBitmap==null){
                                dstbmp = Bitmap.createBitmap(map, 0, 0,
                                        map.getWidth(), map.getHeight(),
                                        matrix, true);
                                cweakBitmaps.put(index,
                                        new WeakReference<Bitmap>(dstbmp));
                            }else{
                                dstbmp=weakBitmap.get();
                                if(dstbmp==null){
                                    dstbmp = Bitmap.createBitmap(map, 0, 0,
                                            map.getWidth(), map.getHeight(),
                                            matrix, true);
                                    cweakBitmaps.put(index,
                                            new WeakReference<Bitmap>(dstbmp));
                                }
                            }
                            canvas.drawBitmap(
                                    map,
                                    frames.getSpriteSourceSize().getX()
                                            + (int) (WIDTH
                                                    - (map.getWidth() * 1) - 150),
                                    frames.getSpriteSourceSize().getY()
                                            + (int) (HEIGHT - (map.getHeight() / 2)),
                                    mPaint);
                            canvas.drawBitmap(dstbmp, frames
                                    .getSpriteSourceSize().getX()
                                    + (int) (WIDTH + 150), frames
                                    .getSpriteSourceSize().getY()
                                    + (int) (HEIGHT - (map.getHeight() / 2)),
                                    mPaint);
                            // if (dstbmp.isRecycled()) {
                            // dstbmp.recycle();
                            // }
                            // if (map.isRecycled()) {
                            // map.recycle();
                            // }
                        }
                        if (!isstart) {
                            if (index < KidbotRobotApplication.animHappy
                                    .getFrames().size()) {
                                index++;
                                if (index == KidbotRobotApplication.animHappy
                                        .getFrames().size()) {
                                    index--;
                                    isstart = true;
                                }
                            } else {
                                index--;
                                isstart = true;
                            }
                        } else {
                            if (index > 0) {
                                index--;
                                if (index == 0) {
                                    isstart = false;
                                }
                            } else {
                                index++;
                                isstart = false;
                            }
                        }
                        break;
                    case FaceBean.RESOLVE:
                        break;
                    case FaceBean.RISUS:
                        break;
                    case FaceBean.SEERIGHT:
                        break;
                    case FaceBean.SAD:
                        frames = KidbotRobotApplication.animSad.getFrames()
                                .get(index);
                        if (frames.getFrame().getW() <= 0) {
                        } else {
                            Rect rect = new Rect(frames.getFrame().getX(),
                                    frames.getFrame().getY(), frames.getFrame()
                                            .getX()
                                            + frames.getSourceSize().getW(),
                                    frames.getFrame().getY()
                                            + frames.getSourceSize().getH());
                            WeakReference<Bitmap> weakBitmap = weakBitmaps
                                    .get(index);
                            Bitmap map = null;
                            if (weakBitmap == null) {
                                map = bitmapRegionDecoder.decodeRegion(rect,
                                        options);
                                weakBitmaps.put(index,
                                        new WeakReference<Bitmap>(map));
                            } else {
                                map=weakBitmap.get();
                                if (map == null) {
                                    map = bitmapRegionDecoder.decodeRegion(
                                            rect, options);
                                    weakBitmaps.put(index,
                                            new WeakReference<Bitmap>(map));
                                }
                            }
                            if (map == null) {
                                holder.unlockCanvasAndPost(canvas);
                                continue;
                            }
                            mPaint.setXfermode(new PorterDuffXfermode(
                                    Mode.CLEAR));
                            canvas.drawPaint(mPaint);
                            mPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
                            Bitmap dstbmp =null;
                            weakBitmap=cweakBitmaps.get(index);
                            if(weakBitmap==null){
                                dstbmp = Bitmap.createBitmap(map, 0, 0,
                                        map.getWidth(), map.getHeight(),
                                        matrix, true);
                                cweakBitmaps.put(index,
                                        new WeakReference<Bitmap>(dstbmp));
                            }else{
                                dstbmp=weakBitmap.get();
                                if(dstbmp==null){
                                    dstbmp = Bitmap.createBitmap(map, 0, 0,
                                            map.getWidth(), map.getHeight(),
                                            matrix, true);
                                    cweakBitmaps.put(index,
                                            new WeakReference<Bitmap>(dstbmp));
                                }
                            }
                            canvas.drawBitmap(
                                    map,
                                    frames.getSpriteSourceSize().getX()
                                            + (int) (WIDTH
                                                    - (map.getWidth() * 1) - 150),
                                    frames.getSpriteSourceSize().getY()
                                            + (int) (HEIGHT - (map.getHeight() / 2)),
                                    mPaint);
                            canvas.drawBitmap(dstbmp, frames
                                    .getSpriteSourceSize().getX()
                                    + (int) (WIDTH + 150), frames
                                    .getSpriteSourceSize().getY()
                                    + (int) (HEIGHT - (map.getHeight() / 2)),
                                    mPaint);
                            if (dstbmp.isRecycled()) {
                                dstbmp.recycle();
                            }
                            if (map.isRecycled()) {
                                map.recycle();
                            }
                        }
                        if (!isstart) {
                            if (index < KidbotRobotApplication.animSad
                                    .getFrames().size()) {
                                index++;
                                if (index == KidbotRobotApplication.animSad
                                        .getFrames().size()) {
                                    index--;
                                    isstart = true;
                                }
                            } else {
                                index--;
                                isstart = true;
                            }
                        } else {
                            if (index > 0) {
                                index--;
                                if (index == 0) {
                                    isstart = false;
                                }
                            } else {
                                index++;
                                isstart = false;
                            }
                        }
                        break;
                    default:
                        break;
                    }
                }
                holder.unlockCanvasAndPost(canvas);
                try {
                    Thread.sleep(rate);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        public void stopThread() {
            isrunning = false;
            try {
                join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public synchronized void setRate(int rate) {
        this.rate = rate;
    }
    public int getState() {
        return this.state;
    }
    public synchronized void setState(int state) {
        // if (FaceBean.BLINK == this.state) {
        // while ((index != KidbotRobotApplication.animBlink.getFrames()
        // .size() - 1)) {
        // continue;
        // }
        // }
        cweakBitmaps.clear();
        weakBitmaps.clear();
        this.state = state;
        this.index = 0;
        switch (state) {
        case FaceBean.BLINK:
            try {
                bitmapRegionDecoder = BitmapRegionDecoder.newInstance(
                        getContext().getAssets().open("kidbot_blink.png"),
                        false);
            } catch (IOException e) {
                e.printStackTrace();
            }
            break;
        case FaceBean.ANGRY:
            try {
                bitmapRegionDecoder = BitmapRegionDecoder.newInstance(
                        getContext().getAssets().open("kidbot_angry.png"),
                        false);
            } catch (IOException e) {
                e.printStackTrace();
            }
            break;
        case FaceBean.HAPPY:
            try {
                bitmapRegionDecoder = BitmapRegionDecoder.newInstance(
                        getContext().getAssets().open("kidbot_happy.png"),
                        false);
            } catch (IOException e) {
                e.printStackTrace();
            }
            break;
        case FaceBean.RESOLVE:
            try {
                bitmapRegionDecoder = BitmapRegionDecoder.newInstance(
                        getContext().getAssets().open("kidbot_blink.png"),
                        false);
            } catch (IOException e) {
                e.printStackTrace();
            }
            break;
        case FaceBean.RISUS:
            try {
                bitmapRegionDecoder = BitmapRegionDecoder.newInstance(
                        getContext().getAssets().open("kidbot_blink.png"),
                        false);
            } catch (IOException e) {
                e.printStackTrace();
            }
            break;
        case FaceBean.SEERIGHT:
            break;
        case FaceBean.SAD:
            try {
                bitmapRegionDecoder = BitmapRegionDecoder.newInstance(
                        getContext().getAssets().open("kidbot_sad.png"), false);
            } catch (IOException e) {
                e.printStackTrace();
            }
            break;
        }
    }
    public synchronized void setRunning(boolean isrunning) {
        this.isrunning = isrunning;
    }
    public synchronized void addIndex() {
        this.index++;
    }
}



Android高效加载大图、多图解决方案,有效避免程序OOM

比如说系统图片库里展示的图片大都是用手机摄像头拍出来的,这些图片的分辨率会比我们手机屏幕的分辨率高得多。大家应该知道,我们编写的应用程序都是有一定内存限制的,程序占用了过高的内存就容易出现OOM(OutOfMemory)异常。

我们可以通过下面的代码看出每个应用程序最高可用内存是多少。

    int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);   
    Log.d("TAG", "Max memory is " + maxMemory + "KB");
      

因此在展示高分辨率图片的时候,最好先将图片进行压缩。压缩后的图片大小应该和用来展示它的控件大小相近,在一个很小的ImageView上显示一张超大的图片不会带来任何视觉上的好处,但却会占用我们相当多宝贵的内存,而且在性能上还可能会带来负面影响。下面我们就来看一看,如何对一张大图片进行适当的压缩,让它能够以最佳大小显示的同时,还能防止OOM的出现。

BitmapFactory这个类提供了多个解析方法(decodeByteArray, decodeFile, decodeResource等)用于创建Bitmap对象,我们应该根据图片的来源选择合适的方法。比如SD卡中的图片可以使用decodeFile方法,网络上的图片可以使用decodeStream方法,资源文件中的图片可以使用decodeResource方法。这些方法会尝试为已经构建的bitmap分配内存,这时就会很容易导致OOM出现。为此每一种解析方法都提供了一个可选的BitmapFactory.Options参数,将这个参数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是null。虽然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。这个技巧让我们可以在加载图片之前就获取到图片的长宽值和MIME类型,从而根据情况对图片进行压缩。如下代码所示:

    BitmapFactory.Options options = new BitmapFactory.Options();   
    options.inJustDecodeBounds = true;   
    BitmapFactory.decodeResource(getResources(), R.id.myimage, options);   
    int imageHeight = options.outHeight;   
    int imageWidth = options.outWidth;   
    String imageType = options.outMimeType;
      

为了避免OOM异常,最好在解析每张图片的时候都先检查一下图片的大小,除非你非常信任图片的来源,保证这些图片都不会超出你程序的可用内存。

现在图片的大小已经知道了,我们就可以决定是把整张图片加载到内存中还是加载一个压缩版的图片到内存中。以下几个因素是我们需要考虑的:

预估一下加载整张图片所需占用的内存。

为了加载这一张图片你所愿意提供多少内存。

用于展示这张图片的控件的实际大小。

当前设备的屏幕尺寸和分辨率。

比如,你的ImageView只有128*96像素的大小,只是为了显示一张缩略图,这时候把一张1024*768像素的图片完全加载到内存中显然是不值得的。

那我们怎样才能对图片进行压缩呢?

通过设置BitmapFactory.Options中inSampleSize的值就可以实现。比如我们有一张2048*1536像素的图片,将inSampleSize的值设置为4,就可以把这张图片压缩成512*384像素。原本加载这张图片需要占用13M的内存,压缩后就只需要占用0.75M了(假设图片是ARGB_8888类型,即每个像素点占用4个字节)。下面的方法可以根据传入的宽和高,计算出合适的inSampleSize值:

 

   public static int calculateInSampleSize(BitmapFactory.Options options,   
            int reqWidth, int reqHeight) {   
        // 源图片的高度和宽度   
        final int height = options.outHeight;   
        final int width = options.outWidth;   
        int inSampleSize = 1;   
        if (height > reqHeight || width > reqWidth) {   
            // 计算出实际宽高和目标宽高的比率   
            final int heightRatio = Math.round((float) height / (float) reqHeight);   
            final int widthRatio = Math.round((float) width / (float) reqWidth);   
            // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高   
            // 一定都会大于等于目标的宽和高。   
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;   
        }   
        return inSampleSize;   
    }

      

使用这个方法,首先你要将BitmapFactory.Options的inJustDecodeBounds属性设置为true,解析一次图片。然后将BitmapFactory.Options连同期望的宽度和高度一起传递到到calculateInSampleSize方法中,就可以得到合适的inSampleSize值了。之后再解析一次图片,使用新获取到的inSampleSize值,并把inJustDecodeBounds设置为false,就可以得到压缩后的图片了。

    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,   
            int reqWidth, int reqHeight) {   
        // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小   
        final BitmapFactory.Options options = new BitmapFactory.Options();   
        options.inJustDecodeBounds = true;   
        BitmapFactory.decodeResource(res, resId, options);   
        // 调用上面定义的方法计算inSampleSize值   
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);   
        // 使用获取到的inSampleSize值再次解析图片   
        options.inJustDecodeBounds = false;   
        return BitmapFactory.decodeResource(res, resId, options);   
    }


    下面的代码非常简单地将任意一张图片压缩成100*100的缩略图,并在ImageView上展示。

    mImageView.setImageBitmap(   
        decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

      

使用图片缓存技术

在你应用程序的UI界面加载一张图片是一件很简单的事情,但是当你需要在界面上加载一大堆图片的时候,情况就变得复杂起来。在很多情况下,(比如使用ListView, GridView 或者 ViewPager 这样的组件),屏幕上显示的图片可以通过滑动屏幕等事件不断地增加,最终导致OOM。

为了保证内存的使用始终维持在一个合理的范围,通常会把被移除屏幕的图片进行回收处理。此时垃圾回收器也会认为你不再持有这些图片的引用,从而对这些图片进行GC操作。用这种思路来解决问题是非常好的,可是为了能让程序快速运行,在界面上迅速地加载图片,你又必须要考虑到某些图片被回收之后,用户又将它重新滑入屏幕这种情况。这时重新去加载一遍刚刚加载过的图片无疑是性能的瓶颈,你需要想办法去避免这个情况的发生。

这个时候,使用内存缓存技术可以很好的解决这个问题,它可以让组件快速地重新加载和处理图片。下面我们就来看一看如何使用内存缓存技术来对图片进行缓存,从而让你的应用程序在加载很多图片的时候可以提高响应速度和流畅性。

内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。

在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。

为了能够选择一个合适的缓存大小给LruCache, 有以下多个因素应该放入考虑范围内,例如:

你的设备可以为每个应用程序分配多大的内存?

设备屏幕上一次最多能显示多少张图片?有多少图片需要进行预加载,因为有可能很快也会显示在屏幕上?

你的设备的屏幕大小和分辨率分别是多少?一个超高分辨率的设备(例如 Galaxy Nexus) 比起一个较低分辨率的设备(例如 Nexus S),在持有相同数量图片的时候,需要更大的缓存空间。

图片的尺寸和大小,还有每张图片会占据多少内存空间。

图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?如果有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不同组的图片。

你能维持好数量和质量之间的平衡吗?有些时候,存储多个低像素的图片,而在后台去开线程加载高像素的图片会更加的有效。

并没有一个指定的缓存大小可以满足所有的应用程序,这是由你决定的。你应该去分析程序内存的使用情况,然后制定出一个合适的解决方案。一个太小的缓存空间,有可能造成图片频繁地被释放和重新加载,这并没有好处。而一个太大的缓存空间,则有可能还是会引起 java.lang.OutOfMemory 的异常。

下面是一个使用 LruCache 来缓存图片的例子:

    private LruCache<String, Bitmap> mMemoryCache;   
     
    @Override   
    protected void onCreate(Bundle savedInstanceState) {   
        // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。   
        // LruCache通过构造函数传入缓存值,以KB为单位。   
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);   
        // 使用最大可用内存值的1/8作为缓存的大小。   
        int cacheSize = maxMemory / 8;   
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {   
            @Override   
            protected int sizeOf(String key, Bitmap bitmap) {   
                // 重写此方法来衡量每张图片的大小,默认返回图片数量。   
                return bitmap.getByteCount() / 1024;   
            }   
        };   
    }   
     
    public void addBitmapToMemoryCache(String key, Bitmap bitmap) {   
        if (getBitmapFromMemCache(key) == null) {   
            mMemoryCache.put(key, bitmap);   
        }   
    }   
     
    public Bitmap getBitmapFromMemCache(String key) {   
        return mMemoryCache.get(key);   
    }


在这个例子当中,使用了系统分配给应用程序的八分之一内存来作为缓存大小。在中高配置的手机当中,这大概会有4兆(32/8)的缓存空间。一个全屏幕的 GridView 使用4张 800x480分辨率的图片来填充,则大概会占用1.5兆的空间(800*480*4)。因此,这个缓存大小可以存储2.5页的图片。

当向 ImageView 中加载一张图片时,首先会在 LruCache 的缓存中进行检查。如果找到了相应的键值,则会立刻更新ImageView ,否则开启一个后台线程来加载这张图片。

    public void loadBitmap(int resId, ImageView imageView) {   
        final String imageKey = String.valueOf(resId);   
        final Bitmap bitmap = getBitmapFromMemCache(imageKey);   
        if (bitmap != null) {   
            imageView.setImageBitmap(bitmap);   
        } else {   
            imageView.setImageResource(R.drawable.image_placeholder);   
            BitmapWorkerTask task = new BitmapWorkerTask(imageView);   
            task.execute(resId);   
        }   
    }


    BitmapWorkerTask 还要把新加载的图片的键值对放到缓存中。
      

    class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {   
        // 在后台加载图片。   
        @Override   
        protected Bitmap doInBackground(Integer... params) {   
            final Bitmap bitmap = decodeSampledBitmapFromResource(   
                    getResources(), params[0], 100, 100);   
            addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);   
            return bitmap;   
        }   
    }


掌握了以上两种方法,不管是要在程序中加载超大图片,还是要加载大量图片,都不用担心OOM的问题了!不过仅仅是理论地介绍不知道大家能不能完全理解,在后面的文章中我会演示如何在实际程序中灵活运用上述技巧来避免程序OOM,敬请期待。

很多android应用都有用相机拍照,然后再把图片裁剪上传,本文我们来分享实现这个功能的实现代码,需要做这个功能的朋友可以参考一下。

其实Android提供Intent让我们打开系统的相机,但是系统相机跟自己app风格不搭,而且用起来体验不好。所以我使用了SDK提供的camera API自定义了一个相机,并且在相机界面上面添加了参考线,有助于用户将题目拍正,提高ocr的识别率。

1、绘制参考线的代码

public class ReferenceLine extends View {

    private Paint mLinePaint;

    public ReferenceLine(Context context) {
        super(context);
        init();
    }

    public ReferenceLine(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ReferenceLine(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mLinePaint = new Paint();
        mLinePaint.setAntiAlias(true);
        mLinePaint.setColor(Color.parseColor("#45e0e0e0"));
        mLinePaint.setStrokeWidth(1);
    }



    @Override
    protected void onDraw(Canvas canvas) {
        int screenWidth = Utils.getScreenWH(getContext()).widthPixels;
        int screenHeight = Utils.getScreenWH(getContext()).heightPixels;

        int width = screenWidth/3;
        int height = screenHeight/3;

        for (int i = width, j = 0;i < screenWidth && j<2;i += width, j++) {
            canvas.drawLine(i, 0, i, screenHeight, mLinePaint);
        }
        for (int j = height,i = 0;j < screenHeight && i < 2;j += height,i++) {
            canvas.drawLine(0, j, screenWidth, j, mLinePaint);
        }
    }


}

2、自定义相机代码

  这里主要是要创建一个SurfaceView,将摄像头的预览界面放到SurfaceView中显示。

package com.bbk.lling.camerademo.camare;

import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.PictureCallback;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.Toast;

import com.bbk.lling.camerademo.utils.Utils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @Class: CameraPreview
 * @Description: 自定义相机
 * @author: lling(www.cnblogs.com/liuling)
 * @Date: 2015/10/25
 */
public class CameraPreview extends SurfaceView implements
        SurfaceHolder.Callback, AutoFocusCallback {
    private static final String TAG = "CameraPreview";

    private int viewWidth = 0;
    private int viewHeight = 0;

    /** 监听接口 */
    private OnCameraStatusListener listener;

    private SurfaceHolder holder;
    private Camera camera;
    private FocusView mFocusView;

    //创建一个PictureCallback对象,并实现其中的onPictureTaken方法
    private PictureCallback pictureCallback = new PictureCallback() {

        // 该方法用于处理拍摄后的照片数据
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            // 停止照片拍摄
            try {
                camera.stopPreview();
            } catch (Exception e) {
            }
            // 调用结束事件
            if (null != listener) {
                listener.onCameraStopped(data);
            }
        }
    };

    // Preview类的构造方法
    public CameraPreview(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 获得SurfaceHolder对象
        holder = getHolder();
        // 指定用于捕捉拍照事件的SurfaceHolder.Callback对象
        holder.addCallback(this);
        // 设置SurfaceHolder对象的类型
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        setOnTouchListener(onTouchListener);
    }

    // 在surface创建时激发
    public void surfaceCreated(SurfaceHolder holder) {
        Log.e(TAG, "==surfaceCreated==");
        if(!Utils.checkCameraHardware(getContext())) {
            Toast.makeText(getContext(), "摄像头打开失败!", Toast.LENGTH_SHORT).show();
            return;
        }
        // 获得Camera对象
        camera = getCameraInstance();
        try {
            // 设置用于显示拍照摄像的SurfaceHolder对象
            camera.setPreviewDisplay(holder);
        } catch (IOException e) {
            e.printStackTrace();
            // 释放手机摄像头
            camera.release();
            camera = null;
        }
        updateCameraParameters();
        if (camera != null) {
            camera.startPreview();
        }
        setFocus();
    }

    // 在surface销毁时激发
    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.e(TAG, "==surfaceDestroyed==");
        // 释放手机摄像头
        camera.release();
        camera = null;
    }

    // 在surface的大小发生改变时激发
    public void surfaceChanged(final SurfaceHolder holder, int format, int w,
            int h) {
        // stop preview before making changes
        try {
            camera.stopPreview();
        } catch (Exception e){
            // ignore: tried to stop a non-existent preview
        }
        // set preview size and make any resize, rotate or
        // reformatting changes here
        updateCameraParameters();
        // start preview with new settings
        try {
            camera.setPreviewDisplay(holder);
            camera.startPreview();

        } catch (Exception e){
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
        setFocus();
    }

    /**
     * 点击显示焦点区域
     */
    OnTouchListener onTouchListener = new OnTouchListener() {
        @SuppressWarnings("deprecation")
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                int width = mFocusView.getWidth();
                int height = mFocusView.getHeight();
                mFocusView.setX(event.getX() - (width / 2));
                mFocusView.setY(event.getY() - (height / 2));
                mFocusView.beginFocus();
            } else if (event.getAction() == MotionEvent.ACTION_UP) {
                focusOnTouch(event);
            }
            return true;
        }
    };

    /**
     * 获取摄像头实例
     * @return
     */
    private Camera getCameraInstance() {
        Camera c = null;
        try {
            int cameraCount = 0;
            Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
            cameraCount = Camera.getNumberOfCameras(); // get cameras number

            for (int camIdx = 0; camIdx < cameraCount; camIdx++) {
                Camera.getCameraInfo(camIdx, cameraInfo); // get camerainfo
                // 代表摄像头的方位,目前有定义值两个分别为CAMERA_FACING_FRONT前置和CAMERA_FACING_BACK后置
                if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                    try {
                        c = Camera.open(camIdx);   //打开后置摄像头
                    } catch (RuntimeException e) {
                        Toast.makeText(getContext(), "摄像头打开失败!", Toast.LENGTH_SHORT).show();
                    }
                }
            }
            if (c == null) {
                c = Camera.open(0); // attempt to get a Camera instance
            }
        } catch (Exception e) {
            Toast.makeText(getContext(), "摄像头打开失败!", Toast.LENGTH_SHORT).show();
        }
        return c;
    }

    private void updateCameraParameters() {
        if (camera != null) {
            Camera.Parameters p = camera.getParameters();

            setParameters(p);

            try {
                camera.setParameters(p);
            } catch (Exception e) {
                Camera.Size previewSize = findBestPreviewSize(p);
                p.setPreviewSize(previewSize.width, previewSize.height);
                p.setPictureSize(previewSize.width, previewSize.height);
                camera.setParameters(p);
            }
        }
    }

    /**
     * @param p
     */
    private void setParameters(Camera.Parameters p) {
        List<String> focusModes = p.getSupportedFocusModes();
        if (focusModes
                .contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
            p.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
        }

        long time = new Date().getTime();
        p.setGpsTimestamp(time);
        // 设置照片格式
        p.setPictureFormat(PixelFormat.JPEG);
        Camera.Size previewSize = findPreviewSizeByScreen(p);
        p.setPreviewSize(previewSize.width, previewSize.height);
        p.setPictureSize(previewSize.width, previewSize.height);
        p.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        if (getContext().getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
            camera.setDisplayOrientation(90);
            p.setRotation(90);
        }
    }

    // 进行拍照,并将拍摄的照片传入PictureCallback接口的onPictureTaken方法
    public void takePicture() {
        if (camera != null) {
            try {
                camera.takePicture(null, null, pictureCallback);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // 设置监听事件
    public void setOnCameraStatusListener(OnCameraStatusListener listener) {
        this.listener = listener;
    }

    @Override
    public void onAutoFocus(boolean success, Camera camera) {

    }

    public void start() {
        if (camera != null) {
            camera.startPreview();
        }
    }

    public void stop() {
        if (camera != null) {
            camera.stopPreview();
        }
    }

    /**
     * 相机拍照监听接口
     */
    public interface OnCameraStatusListener {
        // 相机拍照结束事件
        void onCameraStopped(byte[] data);
    }

    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        viewWidth = MeasureSpec.getSize(widthSpec);
        viewHeight = MeasureSpec.getSize(heightSpec);
        super.onMeasure(
                MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY));
    }

    /**
     * 将预览大小设置为屏幕大小
     * @param parameters
     * @return
     */
    private Camera.Size findPreviewSizeByScreen(Camera.Parameters parameters) {
        if (viewWidth != 0 && viewHeight != 0) {
            return camera.new Size(Math.max(viewWidth, viewHeight),
                    Math.min(viewWidth, viewHeight));
        } else {
            return camera.new Size(Utils.getScreenWH(getContext()).heightPixels,
                    Utils.getScreenWH(getContext()).widthPixels);
        }
    }

    /**
     * 找到最合适的显示分辨率 (防止预览图像变形)
     * @param parameters
     * @return
     */
    private Camera.Size findBestPreviewSize(Camera.Parameters parameters) {

        // 系统支持的所有预览分辨率
        String previewSizeValueString = null;
        previewSizeValueString = parameters.get("preview-size-values");

        if (previewSizeValueString == null) {
            previewSizeValueString = parameters.get("preview-size-value");
        }

        if (previewSizeValueString == null) { // 有些手机例如m9获取不到支持的预览大小 就直接返回屏幕大小
            return camera.new Size(Utils.getScreenWH(getContext()).widthPixels,
                    Utils.getScreenWH(getContext()).heightPixels);
        }
        float bestX = 0;
        float bestY = 0;

        float tmpRadio = 0;
        float viewRadio = 0;

        if (viewWidth != 0 && viewHeight != 0) {
            viewRadio = Math.min((float) viewWidth, (float) viewHeight)
                    / Math.max((float) viewWidth, (float) viewHeight);
        }

        String[] COMMA_PATTERN = previewSizeValueString.split(",");
        for (String prewsizeString : COMMA_PATTERN) {
            prewsizeString = prewsizeString.trim();

            int dimPosition = prewsizeString.indexOf('x');
            if (dimPosition == -1) {
                continue;
            }

            float newX = 0;
            float newY = 0;

            try {
                newX = Float.parseFloat(prewsizeString.substring(0, dimPosition));
                newY = Float.parseFloat(prewsizeString.substring(dimPosition + 1));
            } catch (NumberFormatException e) {
                continue;
            }

            float radio = Math.min(newX, newY) / Math.max(newX, newY);
            if (tmpRadio == 0) {
                tmpRadio = radio;
                bestX = newX;
                bestY = newY;
            } else if (tmpRadio != 0 && (Math.abs(radio - viewRadio)) < (Math.abs(tmpRadio - viewRadio))) {
                tmpRadio = radio;
                bestX = newX;
                bestY = newY;
            }
        }

        if (bestX > 0 && bestY > 0) {
            return camera.new Size((int) bestX, (int) bestY);
        }
        return null;
    }

    /**
     * 设置焦点和测光区域
     *
     * @param event
     */
    public void focusOnTouch(MotionEvent event) {

        int[] location = new int[2];
        RelativeLayout relativeLayout = (RelativeLayout)getParent();
        relativeLayout.getLocationOnScreen(location);

        Rect focusRect = Utils.calculateTapArea(mFocusView.getWidth(),
                mFocusView.getHeight(), 1f, event.getRawX(), event.getRawY(),
                location[0], location[0] + relativeLayout.getWidth(), location[1],
                location[1] + relativeLayout.getHeight());
        Rect meteringRect = Utils.calculateTapArea(mFocusView.getWidth(),
                mFocusView.getHeight(), 1.5f, event.getRawX(), event.getRawY(),
                location[0], location[0] + relativeLayout.getWidth(), location[1],
                location[1] + relativeLayout.getHeight());

        Camera.Parameters parameters = camera.getParameters();
        parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);

        if (parameters.getMaxNumFocusAreas() > 0) {
            List<Camera.Area> focusAreas = new ArrayList<Camera.Area>();
            focusAreas.add(new Camera.Area(focusRect, 1000));

            parameters.setFocusAreas(focusAreas);
        }

        if (parameters.getMaxNumMeteringAreas() > 0) {
            List<Camera.Area> meteringAreas = new ArrayList<Camera.Area>();
            meteringAreas.add(new Camera.Area(meteringRect, 1000));

            parameters.setMeteringAreas(meteringAreas);
        }

        try {
            camera.setParameters(parameters);
        } catch (Exception e) {
        }
        camera.autoFocus(this);
    }

    /**
     * 设置聚焦的图片
     * @param focusView
     */
    public void setFocusView(FocusView focusView) {
        this.mFocusView = focusView;
    }

    /**
     * 设置自动聚焦,并且聚焦的圈圈显示在屏幕中间位置
     */
    public void setFocus() {
        if(!mFocusView.isFocusing()) {
            try {
                camera.autoFocus(this);
                mFocusView.setX((Utils.getWidthInPx(getContext())-mFocusView.getWidth()) / 2);
                mFocusView.setY((Utils.getHeightInPx(getContext())-mFocusView.getHeight()) / 2);
                mFocusView.beginFocus();
            } catch (Exception e) {
            }
        }
    }

}


3、Activity中使用自定义相机

public class TakePhoteActivity extends Activity implements CameraPreview.OnCameraStatusListener,
        SensorEventListener {
    private static final String TAG = "TakePhoteActivity";
    public static final Uri IMAGE_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
    public static final String PATH = Environment.getExternalStorageDirectory()
            .toString() + "/AndroidMedia/";
    CameraPreview mCameraPreview;
    CropImageView mCropImageView;
    RelativeLayout mTakePhotoLayout;
    LinearLayout mCropperLayout;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 设置横屏
//        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        // 设置全屏
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setContentView(R.layout.activity_take_phote);
        // Initialize components of the app
        mCropImageView = (CropImageView) findViewById(R.id.CropImageView);
        mCameraPreview = (CameraPreview) findViewById(R.id.cameraPreview);
        FocusView focusView = (FocusView) findViewById(R.id.view_focus);
        mTakePhotoLayout = (RelativeLayout) findViewById(R.id.take_photo_layout);
        mCropperLayout = (LinearLayout) findViewById(R.id.cropper_layout);

        mCameraPreview.setFocusView(focusView);
        mCameraPreview.setOnCameraStatusListener(this);
        mCropImageView.setGuidelines(2);

        mSensorManager = (SensorManager) getSystemService(Context.
                SENSOR_SERVICE);
        mAccel = mSensorManager.getDefaultSensor(Sensor.
                TYPE_ACCELEROMETER);

    }

    boolean isRotated = false;

    @Override
    protected void onResume() {
        super.onResume();
        if(!isRotated) {
            TextView hint_tv = (TextView) findViewById(R.id.hint);
            ObjectAnimator animator = ObjectAnimator.ofFloat(hint_tv, "rotation", 0f, 90f);
            animator.setStartDelay(800);
            animator.setDuration(1000);
            animator.setInterpolator(new LinearInterpolator());
            animator.start();
            View view =  findViewById(R.id.crop_hint);
            AnimatorSet animSet = new AnimatorSet();
            ObjectAnimator animator1 = ObjectAnimator.ofFloat(view, "rotation", 0f, 90f);
            ObjectAnimator moveIn = ObjectAnimator.ofFloat(view, "translationX", 0f, -50f);
            animSet.play(animator1).before(moveIn);
            animSet.setDuration(10);
            animSet.start();
            isRotated = true;
        }
        mSensorManager.registerListener(this, mAccel, SensorManager.SENSOR_DELAY_UI);
    }

    @Override
    protected void onPause() {
        super.onPause();
        mSensorManager.unregisterListener(this);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        Log.e(TAG, "onConfigurationChanged");
        super.onConfigurationChanged(newConfig);
    }

    public void takePhoto(View view) {
        if(mCameraPreview != null) {
            mCameraPreview.takePicture();
        }
    }

    public void close(View view) {
        finish();
    }

    /**
     * 关闭截图界面
     * @param view
     */
    public void closeCropper(View view) {
        showTakePhotoLayout();
    }

    /**
     * 开始截图,并保存图片
     * @param view
     */
    public void startCropper(View view) {
        //获取截图并旋转90度
        CropperImage cropperImage = mCropImageView.getCroppedImage();
        Log.e(TAG, cropperImage.getX() + "," + cropperImage.getY());
        Log.e(TAG, cropperImage.getWidth() + "," + cropperImage.getHeight());
        Bitmap bitmap = Utils.rotate(cropperImage.getBitmap(), -90);
//        Bitmap bitmap = mCropImageView.getCroppedImage();
        // 系统时间
        long dateTaken = System.currentTimeMillis();
        // 图像名称
        String filename = DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken)
                .toString() + ".jpg";
        Uri uri = insertImage(getContentResolver(), filename, dateTaken, PATH,
                filename, bitmap, null);
        cropperImage.getBitmap().recycle();
        cropperImage.setBitmap(null);
        Intent intent = new Intent(this, ShowCropperedActivity.class);
        intent.setData(uri);
        intent.putExtra("path", PATH + filename);
        intent.putExtra("width", bitmap.getWidth());
        intent.putExtra("height", bitmap.getHeight());
        intent.putExtra("cropperImage", cropperImage);
        startActivity(intent);
        bitmap.recycle();
        finish();
        super.overridePendingTransition(R.anim.fade_in,
                R.anim.fade_out);
//        doAnimation(cropperImage);
    }

    private void doAnimation(CropperImage cropperImage) {
        ImageView imageView = new ImageView(this);
        View view = LayoutInflater.from(this).inflate(
                R.layout.image_view_layout, null);
        ((RelativeLayout) view.findViewById(R.id.root_layout)).addView(imageView);
        RelativeLayout relativeLayout = ((RelativeLayout) findViewById(R.id.root_layout));
//        relativeLayout.addView(imageView);
        imageView.setX(cropperImage.getX());
        imageView.setY(cropperImage.getY());
        ViewGroup.LayoutParams lp = imageView.getLayoutParams();
        lp.width = (int)cropperImage.getWidth();
        lp.height = (int) cropperImage.getHeight();
        imageView.setLayoutParams(lp);
        imageView.setImageBitmap(cropperImage.getBitmap());
        try {
            getWindow().addContentView(view, lp);
        } catch (Exception e) {
            e.printStackTrace();
        }
        /*AnimatorSet animSet = new AnimatorSet();
        ObjectAnimator translationX = ObjectAnimator.ofFloat(this, "translationX", cropperImage.getX(), 0);
        ObjectAnimator translationY = ObjectAnimator.ofFloat(this, "translationY", cropperImage.getY(), 0);*/

        TranslateAnimation translateAnimation = new TranslateAnimation(
                0, -cropperImage.getX(), 0, -(Math.abs(cropperImage.getHeight() - cropperImage.getY())));// 当前位置移动到指定位置
        RotateAnimation rotateAnimation = new RotateAnimation(0, -90,
                Animation.ABSOLUTE, cropperImage.getX() ,Animation.ABSOLUTE, cropperImage.getY());
        AnimationSet animationSet = new AnimationSet(true);
        animationSet.addAnimation(translateAnimation);
        animationSet.addAnimation(rotateAnimation);
        animationSet.setFillAfter(true);
        animationSet.setDuration(2000L);
        imageView.startAnimation(animationSet);
//        finish();
    }

    /**
     * 拍照成功后回调
     * 存储图片并显示截图界面
     * @param data
     */
    @Override
    public void onCameraStopped(byte[] data) {
        Log.i("TAG", "==onCameraStopped==");
        // 创建图像
        Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
        // 系统时间
        long dateTaken = System.currentTimeMillis();
        // 图像名称
        String filename = DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken)
                .toString() + ".jpg";
        // 存储图像(PATH目录)
        Uri source = insertImage(getContentResolver(), filename, dateTaken, PATH,
                filename, bitmap, data);
        //准备截图
        try {
            mCropImageView.setImageBitmap(MediaStore.Images.Media.getBitmap(this.getContentResolver(), source));
//            mCropImageView.rotateImage(90);
        } catch (IOException e) {
            Log.e(TAG, e.getMessage());
        }
        showCropperLayout();
    }

    /**
     * 存储图像并将信息添加入媒体数据库
     */
    private Uri insertImage(ContentResolver cr, String name, long dateTaken,
                            String directory, String filename, Bitmap source, byte[] jpegData) {
        OutputStream outputStream = null;
        String filePath = directory + filename;
        try {
            File dir = new File(directory);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            File file = new File(directory, filename);
            if (file.createNewFile()) {
                outputStream = new FileOutputStream(file);
                if (source != null) {
                    source.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
                } else {
                    outputStream.write(jpegData);
                }
            }
        } catch (FileNotFoundException e) {
            Log.e(TAG, e.getMessage());
            return null;
        } catch (IOException e) {
            Log.e(TAG, e.getMessage());
            return null;
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (Throwable t) {
                }
            }
        }
        ContentValues values = new ContentValues(7);
        values.put(MediaStore.Images.Media.TITLE, name);
        values.put(MediaStore.Images.Media.DISPLAY_NAME, filename);
        values.put(MediaStore.Images.Media.DATE_TAKEN, dateTaken);
        values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
        values.put(MediaStore.Images.Media.DATA, filePath);
        return cr.insert(IMAGE_URI, values);
    }

    private void showTakePhotoLayout() {
        mTakePhotoLayout.setVisibility(View.VISIBLE);
        mCropperLayout.setVisibility(View.GONE);
    }

    private void showCropperLayout() {
        mTakePhotoLayout.setVisibility(View.GONE);
        mCropperLayout.setVisibility(View.VISIBLE);
        mCameraPreview.start();   //继续启动摄像头
    }


    private float mLastX = 0;
    private float mLastY = 0;
    private float mLastZ = 0;
    private boolean mInitialized = false;
    private SensorManager mSensorManager;
    private Sensor mAccel;
    @Override
    public void onSensorChanged(SensorEvent event) {

        float x = event.values[0];
        float y = event.values[1];
        float z = event.values[2];
        if (!mInitialized){
            mLastX = x;
            mLastY = y;
            mLastZ = z;
            mInitialized = true;
        }
        float deltaX  = Math.abs(mLastX - x);
        float deltaY = Math.abs(mLastY - y);
        float deltaZ = Math.abs(mLastZ - z);

        if(deltaX > 0.8 || deltaY > 0.8 || deltaZ > 0.8){
            mCameraPreview.setFocus();
        }
        mLastX = x;
        mLastY = y;
        mLastZ = z;
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }
}


  actiity中注册了SensorEventListener,也就是使用传感器监听用户手机的移动,如果有一定距离的移动,则自动聚焦,这样体验好一点。

  我对比了一下小猿搜题和学霸君两款app的拍照功能,个人感觉小猿搜题的体验要好一些,因为从主界面进入拍照界面,连个界面没有一个旋转的过渡,而学霸君就有一个过渡,有一丝丝的影响体验。也就是说学霸君的拍照界面是横屏的,在activity的onCreate方法里面调用了setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)来设置全屏,而切换界面的时候又从竖屏切换为横屏,就会有个过渡的效果,影响了体验。

  个人猜测小猿搜题是将拍照界面的activity设置为竖屏,而将摄像头直接旋转90度,这样就强制用户横屏拍摄,当然,拍完之后还要将图片旋转回来。所以我参考小猿搜题来实现的,毕竟体验为王嘛。


  如上图(其实是竖屏),红色圈起来的其实是放到底部,然后将屏幕中间的文字旋转90度(带有动画,起了提示用户横屏拍照的作用),就给人的感觉是横屏的。了。

  还有一点就是小猿搜题拍完照到截图过渡的很自然,感觉很流畅,估计是拍照和截图放在同一个activity中的,如果是两个activty,涉及到界面切换,肯定不会那么自然。所以我也将拍照和截图放在一个界面,拍照完就将自定义相机隐藏,将截图界面显示出来,这样切换就很流畅了。

  项目中截图的功能我是从github上面找的一个开源库cropper:https://github.com/edmodo/cropper

    因为ocr图片识别的代码是公司的,所以识别的功能没有添加到demo里面去。


在android应用中,有时我们在一些较小的页面显示长点的文字时,或许会用到跑马灯效果,特别是在添加广告效果的时候,本文我们就来看看android中用TextView实现跑马灯效果。

如何实现两个TextView的跑马灯效果,如果使用常规的做法,只能够使一个TextView起作用。 现在方法具体如下:

1.为TextView增加四个属性

android:ellipsize="marquee"
android:focusable="true"
android:focusableInTouchMode="true"
android:singleLine="true"


实现TextView类,实现三个构造函数并重载 isFocused方法。

public class MarqueeText extends TextView { public MarqueeText(Context context) { super(context); }
 public MarqueeText(Context context, AttributeSet attrs) {
     super(context, attrs);
 }
 public MarqueeText(Context context, AttributeSet attrs, int defStyle) {
     super(context, attrs, defStyle);
 }
 @Override
 public boolean isFocused() {
     return true;
 }  }



在main.xml文件中使用自己实现的TextView类。

这样就实现了两个跑马灯的效果了。 这里主要是重写了isFocuse方法,这样默认两个TextView都被Focuse了,所以这两个TextView都可以跑马灯了。



Android实现多个TextView同时显示跑马灯效果

如果在一个较小的android页面中,需要显示较长的文案时无法显示完全,于是很自然地想到了TextView中的marquee —— 跑马灯效果,可是Android执行跑马灯效果需要控件获取焦点,当某一控件requestFocus()时,会将其他控件的焦点抢去,这就导致了同一时间只能有一个控件获取焦点。那么,如果让多个控件同时获取并持有焦点呢?或者说,“欺骗”Android系统,让它以为多个控件都持有焦点,即每个控件都在焦点状态。

通过上述分析,问题转化为如何让多个控件同时处于焦点状态,这就需要重写TextView的部分方法,达到“欺骗”Android系统的目的,这样每个控件requestFocus()之后,均让自己处于焦点状态,并且不可被剥夺焦点,就可以达到多个控件同时“持有”焦点了,代码如下:

public class MarqueeTextView extends TextView
{
    public MarqueeTextView(Context context)
    {
        this(context, null);
    }
    
    public MarqueeTextView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        
        setFocusable(true);
        setFocusableInTouchMode(true);
        
        setSingleLine();
        setEllipsize(TextUtils.TruncateAt.MARQUEE);
        setMarqueeRepeatLimit(-1);
    }
    
    public MarqueeTextView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        
        setFocusable(true);
        setFocusableInTouchMode(true);
        
        setSingleLine();
        setEllipsize(TextUtils.TruncateAt.MARQUEE);
        setMarqueeRepeatLimit(-1);
    }
    @Override
    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)
    {
        if (focused)
        {
            super.onFocusChanged(focused, direction, previouslyFocusedRect);
        }
    }
    
    @Override
    public void onWindowFocusChanged(boolean focused)
    {
        if (focused)
        {
            super.onWindowFocusChanged(focused);
        }
    }
    
    @Override
    public boolean isFocused()
    {
        return true;
    }
}




由于常规执行跑马灯的属性:

android:focusable="true"、android:focusableInTouchMode="true"、android:singleLine="true"、android:ellipsize="marquee"

在代码中均已设置,故引用此控件时,无需添加上述4个属性。

本文我们谈谈在 Android开发中,用Volley框架实现获取服务器的字符串响应的实例,做Android开发的朋友可以参考。

学习内容:

例一、使用StringRequest实现获取服务器的字符串响应...

 
Android的Volley中到底实现了哪些请求才是我们在开发中需要进行使用的...Volley实现的东西其实并不是很多,它的主要功能是实现异步进行网络请求和图片加载,其实就是异步加载解析Json数据,异步获取服务器的字符串数据,异步实现网络图片的动态加载,还有一个请求就是清空缓存的请求,不过使用的地方不是很多,主要还是前面三个方面,因此Volley相对于AndBase来说,其实还算是一个轻量级的框架,而AndBase涉及到的东西就更加的广泛,全面,但是网络请求这一部分使用Volley基本算是够用了...

1.StringRequest.java


package com.android.volley.toolbox;
import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;
import java.io.UnsupportedEncodingException;
public class StringRequest extends Request<String> {
    private final Listener<String> mListener; //请求成功的监听...
    //根据指定的请求方式和url创建一个StringRquest对象...
    public StringRequest(int method, String url, Listener<String> listener,
            ErrorListener errorListener) {
        super(method, url, errorListener); //设置请求方式,url,以及错误监听..
        mListener = listener; //设置成功监听...
    }
    //根据指定的url来创建一个StringRequest对象,请求方式默认为GET..
    public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
        this(Method.GET, url, listener, errorListener);
    }
    //这里涉及到发送响应的过程了...表示整个请求的响应已经返回...
    @Override
    protected void deliverResponse(String response) {
        mListener.onResponse(response);
    }
    //对响应的解析过程...
    @Override
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); //对响应数据封装,解析字符集...
        } catch (UnsupportedEncodingException e) {
            parsed = new String(response.data);
        }
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));//返回请求成功...
    }
}



  上面只是StringRequest的源码实现,非常的简单...那么我们来具体的看看怎么用...

  一般使用在简单的响应方式,返回一些基本的数据信息,比如说用户登录中,在发送Post请求发送用户的账号信息和密码的时候,需要服务器调取数据库进行相关查找...在完成这个响应之后需要为服务端返回响应信息,一般就是以字符串的形式进行发送的...


package com.example.oop;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.ImageLoader.ImageCache;
import com.android.volley.toolbox.NetworkImageView;
import com.android.volley.toolbox.Volley;
import android.os.Bundle;
import android.app.Activity;
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
public class MainActivity extends Activity implements OnClickListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }
    public void init(){
        RequestQueue queue=Volley.newRequestQueue(MainActivity.this); //首先创建一个请求队列...
     //然后需要向请求队列中添加相关请求...
     queue.add(new StringRequest("http://www.baidu.com/",new Listener    <StringRequest>(){
        //请求成功,接收请求方法的重写...
        @Override
        public void onResponse(String response){
            System.out.println(response.toString());
        }
    },new ErrorListener(){ //请求失败,对错误的获取...
        @Override
        public void onErrorResponse(VolleyError error){
            System.out.println(error.toString());
        }
    });  
        
}
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
}




  这里我们想百度页面发送了相关的请求,那么毫无疑问,请求成功是必然的,那么返回的东西就是百度页面的原生数据,其实就是Html页面代码...那么我们无法去解析这个页面,但是浏览器却是可以的,我们可以通过浏览器去加载这个页面...这只是一个简单的小例子而已,目的是我们需要清楚,服务器返回给我们的是什么数据...

  第二个例子:

  这是一个中间用于过程处理的JSP方法...用于处理账户和密码,只是一个简单的方式,我们当然也可以通过它去连接数据库,完善化这个函数...这里只是一个简单的小例子...


 <%
  String name=request.getParameter("name");
  String password=request.getParameter("password");
  if("darker".equals(name)&& "49681888".equals(password)){
      out.println("Receive name is:"+name);
    out.println("Receive password is:"+password);%>
    Your Message are right!
  <%}else{
      out.println("Receive name is:"+name);
    out.println("Receive password is:"+password);%>
    Your Message are wrong!
  <%}%>




  那么Activity中需要通过Post请求发送请求参数,才能够通过这个函数来进行下一步的判断...由于Post请求中没有传递参数的方法...但是我们可以通过重写getParam()方法...来指定相关参数,服务端会自动调用getParam()中的参数....


package com.example.oop;
//有一部分包没有引用,在编写的时候会自动引用的...
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
public class MainActivity extends Activity {
 
    TextView tv;
    String url="192.168.19.172:8080/JSP/post.jsp"
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv=(TextView)findViewById(R.id.tv_1);
        init();
    }
    public void init(){
        RequestQueue queue=Volley.newRequestQueue(MainActivity.this); //首先创建一个请求队列...
       queue.add(new StringRequest(Method.POST, url, new Listener<String>() {
    
    @Override
    public void onResponse(String response) {
        // TODO Auto-generated method stub
        System.out.println(response.toString());
        tv.setText(response.toString()); //对获取的数据进行显示...
    }
}, new ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        // TODO Auto-generated method stub
          System.out.println(error.toString());            
    }
}){
      //在这个方法里完成参数的相关传递....
    @Override
    protected Map<String, String>getParams() throws AuthFailureError{
        Map<String, String>map=new HashMap<String, String>();
        map.put("name", "darker");
        map.put("password", "49681888");
        return map;
    }
});
}
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
}




  使用Post请求来完成验证,毫无疑问,这里由于我们传递的参数时正确的,因此客户端会获取到Receive name is:darker,Receive password is:49681888,Your Message are right!这段信息...

  StringRequest请求非常的简单,涉及的东西也并不是很多,适合于发送网络请求来获取相应的字符串数据,呈递给客户端

Android为数据存储提供了如下几种方式:文件、SharedPreferences(偏好参数)、SQLite数据库、内容提供者(Content provider)、网络,本文我们重点讲讲文件存储。

第一步,改写AndroidManifest.xml,给手机的Scard卡授权

<!--添加Scard卡读写授权 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

然后加单元测试instrumentation

 <instrumentation
        android:name="android.test.InstrumentationTestRunner"
        android:targetPackage="com.example.androidsdcard" >
    </instrumentation>

并且在application下加 <uses-library android:name="android.test.runner"/>


 <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <uses-library android:name="android.test.runner"/>
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>


第二步,编写文件读写的类FileStream

package com.example.androidsdcard;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import android.content.Context;
import android.os.Environment;

public class FileStream {
    private Context context;

    public FileStream(Context context) {
        this.context = context;
    }

    public FileStream() {

    }
    //读取sd卡的文件内容
    public String readFile(String filename){
        FileInputStream inputStream=null;
        //字符缓冲流
        ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
     //                 通过Environment获得sd卡的路径
        File file=new File(Environment.getExternalStorageDirectory(),filename);
        //判断sdcard是否存在
        if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){
            try {
                inputStream=new FileInputStream(file);
                int len=0;
         //创建字节数组
                byte[]data=new byte[2048];
          //按字节数组的大小进行读取
                while((len=inputStream.read(data))!=-1){
                    outputStream.write(data, 0, len);    
                }
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }finally{
                if(inputStream!=null){
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        }
        return new String(outputStream.toByteArray());
        
    }

    public boolean savaFile(String filename, String content) {
        boolean flag = false;
        FileOutputStream outputStream = null;
        File file = new File(Environment.getExternalStorageDirectory(),
                filename);
        if (Environment.MEDIA_MOUNTED.equals(Environment
                .getExternalStorageState())) {
            try {
                outputStream = new FileOutputStream(file);
                outputStream.write(content.getBytes());
                flag = true;
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } finally {
                if (outputStream != null) {
                    try {
                        outputStream.close();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }

        }

        return flag;
    }

}


第三部,编写测试类进行单元测试,进行文件的读写

package com.example.androidsdcard;

import android.content.Context;
import android.test.AndroidTestCase;
import android.util.Log;

public class MyTest extends AndroidTestCase {
    private final String TAG="MyTest";
    public MyTest(){
        
    }
    public void saveFile(){//测试文件写入
        Context context=getContext();
        FileStream fileStream=new FileStream(context);
        boolean flag=fileStream.savaFile("hello.txt","你好吗?");
        Log.i(TAG,"--->"+flag);
        
    }
    public void readFile(){//测试文件内容读取
        Context context=getContext();
        FileStream fileStream=new FileStream(context);
        String filename="hello.txt";
        String flag=fileStream.readFile(filename);
        Log.i(TAG,"--->"+flag);
        
    }

}

[!--infotagslink--]

相关文章

  • 使用PHP+JavaScript将HTML页面转换为图片的实例分享

    这篇文章主要介绍了使用PHP+JavaScript将HTML元素转换为图片的实例分享,文后结果的截图只能体现出替换的字体,也不能说将静态页面转为图片可以加快加载,只是这种做法比较interesting XD需要的朋友可以参考下...2016-04-19
  • C#从数据库读取图片并保存的两种方法

    这篇文章主要介绍了C#从数据库读取图片并保存的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...2021-01-16
  • Python 图片转数组,二进制互转操作

    这篇文章主要介绍了Python 图片转数组,二进制互转操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-09
  • Photoshop古装美女图片转为工笔画效果制作教程

    今天小编在这里就来给各位Photoshop的这一款软件的使用者们来说说把古装美女图片转为细腻的工笔画效果的制作教程,各位想知道方法的使用者们,那么下面就快来跟着小编一...2016-09-14
  • php抓取网站图片并保存的实现方法

    php如何实现抓取网页图片,相较于手动的粘贴复制,使用小程序要方便快捷多了,喜欢编程的人总会喜欢制作一些简单有用的小软件,最近就参考了网上一个php抓取图片代码,封装了一个php远程抓取图片的类,测试了一下,效果还不错分享...2015-10-30
  • Android子控件超出父控件的范围显示出来方法

    下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
  • jquery左右滚动焦点图banner图片鼠标经过显示上下页按钮

    jquery左右滚动焦点图banner图片鼠标经过显示上下页按钮...2013-10-13
  • Android开发中findViewById()函数用法与简化

    findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20
  • 利用JS实现点击按钮后图片自动切换的简单方法

    下面小编就为大家带来一篇利用JS实现点击按钮后图片自动切换的简单方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2016-10-25
  • Android模拟器上模拟来电和短信配置

    如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
  • 夜神android模拟器设置代理的方法

    夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
  • 浅谈redis key值内存消耗以及性能影响

    这篇文章主要介绍了浅谈redis key值内存消耗以及性能影响,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-07
  • android自定义动态设置Button样式【很常用】

    为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
  • Photoshop枪战电影海报图片制作教程

    Photoshop的这一款软件小编相信很多的人都已经是使用过了吧,那么今天小编在这里就给大家带来了用Photoshop软件制作枪战电影海报的教程,想知道制作步骤的玩家们,那么下面...2016-09-14
  • Android WebView加载html5页面实例教程

    如果我们要在Android应用APP中加载html5页面,我们可以使用WebView,本文我们分享两个WebView加载html5页面实例应用。 实例一:WebView加载html5实现炫酷引导页面大多...2016-09-20
  • 详解分析MySQL8.0的内存消耗

    这篇文章主要介绍了详解分析MySQL8.0的内存消耗,帮助大家更好的理解和学习使用MySQL,感兴趣的朋友可以了解下...2021-03-23
  • 深入理解Android中View和ViewGroup

    深入理解Android中View和ViewGroup从组成架构上看,似乎ViewGroup在View之上,View需要继承ViewGroup,但实际上不是这样的。View是基类,ViewGroup是它的子类。本教程我们深...2016-09-20
  • Android自定义WebView网络视频播放控件例子

    下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
  • js实现上传图片及时预览

    这篇文章主要为大家详细介绍了js实现上传图片及时预览的相关资料,具有一定的参考价值,感兴趣的朋友可以参考一下...2016-05-09
  • Android用MemoryFile文件类读写进行性能优化

    java开发的Android应用,性能一直是一个大问题,,或许是Java语言本身比较消耗内存。本文我们来谈谈Android 性能优化之MemoryFile文件读写。 Android匿名共享内存对外A...2016-09-20