android应用中用TextView实现跑马灯效果实例教程

 更新时间:2016年9月20日 19:54  点击:2068
在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应用中要做一个有几百张图片的帧动画(恶心),这样很容易出现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开发中,用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);
        
    }

}

友盟是一款移动应用统计分析平台。它可以帮助移动应用运营人员统计和分析流量来源、产品留存数据、用户属性和行为数据等,以便产品开发者和运营人员利用数据进行产品、运营、推广策略的决策。

软硬件环境
    Macbook Pro MGX 72
    Android Studio 1.3.2
    Genymotion模拟器


集成友盟SDK

首先到友盟官网http://www.umeng.com/注册个帐户,完成后到管理后台添加需要集成友盟统计的应用,如下

umneg_01

提交后,分给你的应用分配key,如下

umneg_02

接下来到http://dev.umeng.com/analytics/android-doc/sdk-download下载SDK,将下载下来的文件夹中的libs里的jar文件copy到工程中的libs中,在Android Studio中右键单击jar文件,选择Add as library完成导入。


配置AndroidManifest.xml

添加相应权限,如下

<uses-sdk android:minSdkVersion="4"></uses-sdk>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>

然后填写key和渠道id,一个包中只能添加一个渠道,如下

<meta-data android:value="这里填写友盟分配的key值" android:name="UMENG_APPKEY" />
<meta-data android:value="这里填写渠道名称,如Wandoujia或者360" android:name="UMENG_CHANNEL" />

集成代码

在每个Activity的onResume方法中调用MobclickAgent.onResume(Context),在onPause方法中调用MobclickAgent.onPause(Context)。如果Activity之间有继承关系,不要重复添加onResume和onPause方法,否则会出现重复统计,影响统计结果。如果App中有调用Process.kill或者System.exit之类的方法杀死进程,请务必在此之前调用MobclickAgent.onKillProcess(Context)方法,用来保存统计数据。

至此,基本功能已经集成完毕,还是很简单的。后续的一些高级功能,可以根据自己的需求另行添加,官方的文档已经写得很详细了,这里就不再写了,感兴趣的去友盟的官网查看。



友盟统计怎么算新增用户 android

Android 统计分析 SDK使用指南


1. 建立App,下载SDK


登录你的帐号后,看到友盟的管理后台,点击"+添加新应用",进入新应用信息填写的页面。

App建立成功后,可以获得该App的AppKey,以及最新的开发指南和SDK文件。

可在 这里 下载SDK,包含开发文档,demo程序和jar包。

说明:在新应用信息填写中,请尽量填写真实的信息。您可以通过友盟统计分析平台的特性节省重复建立App的时间。
如果您要对App不同的发布渠道进行统计,不需要创建新App,请使用分发渠道分析,通过分发渠道分析,您可以更方便的对比数据。

2. 实现基本的使用基本统计实现本的页面跳转,机型,分辨率,地理位置 …的统计1. 导入umeng-sdk*.jar(简称SDK)下载最新版sdk的zip包,解压将其中的umeng-sdk.jar释放到本地目录,Eclipse用户右键您的工程根目录,选择Properties -> Java Build Path -> Libraries, 然后点击 Add External JARs... 选择指向 Analytics_Android_SDK_*.jar的路径,点击OK,即导入成功。


2. 配置 AndroidManifest.xml

<manifest……><application ……> ……<activity ……/><meta-dataandroid:value="YOUR_APP_KEY"android:name="UMENG_APPKEY"></meta-data><meta-dataandroid:value="Channel ID"android:name="UMENG_CHANNEL"/></application><uses-sdkandroid:minSdkVersion="4"></uses-sdk><uses-permissionandroid:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission><uses-permissionandroid:name="android.permission.INTERNET"></uses-permission><uses-permissionandroid:name="android.permission.READ_PHONE_STATE"></uses-permission><uses-permissionandroid:name="android.permission.READ_LOGS"></uses-permission></manifest>

说明:


META-DATA 用途
UMENG_APPKEY 用来定位该应用程序的唯一性。
UMENG_CHANNEL 用来标注应用推广渠道,区分新用户的来源来查看统计,您可以使用20位以内的英文和数字为渠道定名,替换value中的"Channel ID"。详见渠道统计。
权限 用途
INTERNET(必须) 允许应用程序联网,以便向我们的服务器端发送数据。
READ_PHONE_STATE(必须) 获取用户手机的IMEI,用来唯一的标识用户。(如果您的应用会运行在无法读取IMEI的平板上,我们会将mac地址作为用户的唯一标识,请添加权限: android.permission.ACCESS_WIFI_STATE )
ACCESS_NETWORK_STATE 检测网络状态,友盟SDK 1.6版本新增权限。
READ_LOGS 如果您想获得客户端crash的报告, 需要添加这个权限。具体见【使用错误报告】。
WRITE_EXTERNAL_STORAGE 如果您使用了友盟自动更新提醒功能,需添加这个权限,为了将更新的APK临时存在SD卡里。


3. 添加代码


添加引用: import com.umeng.analytics.MobclickAgent

注册 Activity: 在每个Activity的onResume方法中调用 MobclickAgent.onResume(Context), onPause方法中调用
MobclickAgent.onPause(Context)

publicvoid onResume() { super.onResume(); MobclickAgent.onResume(this);}publicvoid onPause() { super.onPause(); MobclickAgent.onPause(this);}API:public void onResume(Context context)
context 当前Activity的引用,这里请不要将全局的application context传入。
public void onPause(Context context)
context 当前Activity的引用,这里请不要将全局的application context传入。
API说明:
void onResume(Context context)
context 当前Activity引用

void onPause(Context context)
context 当前Activity引用

说明:
方法将会自动地从AndroidManifest.xml文件里读取Appkey。

确保在所有的activity中都调用 MobclickAgent.onResume() 和MobclickAgent.onPause()方法,这两个调用将不会阻塞应用程序的主线程,也不会影响应用程序的性能。

注意如果您的Activity之间有继承或者控制关系请不要同时在父和子Activity中重复添加onPause和onResume方法,否则会造成重复统计(eg.使用TabHost、TabActivity、ActivityGroup时)。

一个应用程序在多个activity之间连续切换时,将会被视为同一个session(启动)。

当用户两次使用之间间隔超过30秒时,将被认为是两个的独立的session(启动),例如用户回到home,或进入其他程序,经过一段时间后再返回之前的应用。

4. 测试


确认所需的权限都已经添加:INTERNET, READ_PHONE_STATE, (READ_LOGS, WRITE_EXTERNAL_STORAGE)

确认APPKEY已经正确的写入Androidmanifest.xml

确认所有的Activity中都调用了onResume和onPause方法

确认测试手机(或者模拟器)已成功连入网络

启动应用程序,几分钟之后您应该已经可以看到相应的报表.

3. 使用错误报告友盟统计分析工具,还可以帮助您捕捉用户在使用应用程序过程中出现的异常退出(FC), 并在应用程序下次启动时将错误报告发送给服务器。
1. 自动捕获异常退出(FC)
在AndroidManifest.xml里面添加权限android.permission.READ_LOGS

在程序的Main Activity(应用程序入口)的onCreate方法里调用MobclickAgent.onError(Context).

publicvoid onCreate(Bundle savedinstanceState) { super.onCreate(savedInstanceState); MobclickAgent.onError(this); ...}API:public void onError(Context context)
context 当前Activity的引用
说明:错误报告包含应用程序版本,操作系统版本和设备型号以及程序出现异常时的Stacktrace,这些数据将帮助您修正应用程序的Bug。
2. 手动发送错误报告如果您自己捕获了程序中的异常,但是依然希望,将这次异常信息发送到友盟的服务器,您可以调用下面的函数。
MobclickAgent.reportError(Context context,String error) API:public void reportError(Context context, String error)
context 当前Activity的引用

error 开发者手动捕获的错误信息
说明:手动发送的异常信息和自动捕获的异常信息一样,都会展示在错误报告面板。

4. 使用自定义事件除了基本统计分析功能外,我们还支持您自定义的事件分析,例如您可以统计游戏中通过不同关卡的人数,广告的点击次数或者视频被播放的次数等等。 使用自定义事件功能请先在网站应用管理后台(设置->编辑自定义事件)中添加相应的自定义事件后,服务器才会对相应的自定义事件请求进行处理。
1. 事件数量统计1. 在您希望跟踪的代码部分,调用如下方法:MobclickAgent.onEvent(Context context, String event_id);API:public void onEvent(Context context, String event_id)
context 当前Activity的引用

event_id 为当前统计的事件ID,注意要先在友盟网站上注册此事件ID。
示例:统计微博应用中“转发"事件发生的次数,那么在“转发"的函数里调用
MobclickAgent.onEvent(this, "Forward") 2. 记录事件的不同属性及取值,调用如下方法:MobclickAgent.onEvent(Context context, String event_id, Map<String,String>; map);API:public voidonEvent(Context context, String event_id, Map map)
context 当前Activity的引用

event_id 为当前统计的事件ID,注意要先在友盟网站上注册此事件ID。

map 为当前事件的属性和取值集合(key-value)
示例:MobclickAgent.onEvent(LoginActivity.this, "sinaLogin");
新浪用户登陆海知笔记

3. 考虑事件在一个属性上的取值,可以调用如下方法:MobclickAgent.onEvent(Context context, String event_id, String label);API:public void onEvent(Context context, String event_id, String label)
context 当前Activity的引用

event_id 为当前统计的事件ID,注意要先在友盟网站上注册此事件ID.

label 事件的一个属性描述
示例:统计游戏中“死亡"事件发生的关卡数,那么可以在死亡的函数里调用
MobclickAgent.onEvent(this, "player_dead","level");2. 事件时长统计有的事件是持续发生的,需要记录其持续的时间,这里提供两种解决方法。
1. 在事件开始和结束时分别调用onEventBegin和 onEventEnd两个函数。MobclickAgent.onEventBegin(Context context, String event_id);...MobclickAgent.onEventEnd(Context context, String event_id);API:public voidonEventBegin(Context context, String event_id)

public void onEventEnd(Context context, String event_id)
context 当前Activity引用

event_id 为当前统计的事件ID,注意要先在友盟网站上注册此事件ID.
public void onEventBegin(Context context, String event_id, String label)

public void onEventEnd(Context context, String event_id, String label)
context 当前Activity引用

event_id 为当前统计的事件ID,注意要先在友盟网站上注册此事件ID.

label 事件的一个属性描述
示例:跟踪播放音乐事件发生的总时间,在音乐播放开始时调用:
MobclickAgent.onEventBegin(this, "music_play");在音乐播放结束时调用:
MobclickAgent.onEventEnd(this, "music_play");2. 跟踪时长的事件包含多个属性,在事件开始和结束时分别调用onKVEventBegin和 onKVEventEnd两个函数MobclickAgent.onKVEventBegin(Context context, String event_id, Map<String,String> map, String ekvFlag);...MobclickAgent.onKVEventEnd(Context context, String event_id, String ekvFlag);API:public void onKVEventBegin(Context context, String event_id, Map map, String ekvFlag)
context 当前Activity引用

event_id 为当前统计的事件ID,注意要先在友盟网站上注册此事件ID.

map 为当前事件的属性和取值集合(key-value)

ekvFlag 事件标示符
public void onKVEventEnd(Context context, String event_id, String ekvFlag)
context 当前Activity引用

event_id 为当前统计的事件ID,注意要先在友盟网站上注册此事件ID

ekvFlag 事件标示符,ekvFlag 和 event_id 一起标示一个唯一事件,并不会被统计;对于同一个事件,在onKVEventBegin和onKVEventEnd 中要传递相同的event_id 和 flag
示例跟踪每种类型的音乐播放了多久,在音乐播放开始时调用
Map<String,String> music = new HashMap<String,String>(); music.put("type", "popular"); music.put("artist", "JJLin"); music.put("User_status", "registered");MobclickAgent.onKVEventBegin(this, "music",music,"m7");在音乐播放结束时调用:
MobclickAgent.onKVEventEnd(this, "music",music,"m7");3. 自己计算并上传event时长,在您想跟踪时长的代码部分,调用如下方法:MobclickAgent.onEventDuration(Context context, String event_id, long duration);orMobclickAgent.onEventDuration(Context context, String event_id,String label, long duration)orMobclickAgent.onEventDuration(Context context, String event_id, Map<String, String> map, long duration)API:public void onEventDuration(Context context, String event_id, long duration)

public void onEventDuration(Context context, String event_id,String label, long duration)
context 当前Activity引用

event_id 为当前统计的事件ID,注意要先在友盟网站上注册此事件ID

label 事件的一个属性描述

duration 事件持续时长,单位毫秒,您需要手动计算并传入时长,作为事件的时长参数
public void onEventDuration(Context context, String event_id, Map map, long duration)
context 当前Activity引用

event_id 为当前统计的事件ID,注意要先在友盟网站上注册此事件ID

map 为当前事件的属性和取值集合(key-value)

duration 事件持续时长,单位毫秒,您需要手动计算并传入时长,作为事件的时长参数
说明
时长是友盟统计的一个新功能,使用过程中可能会出现一些常见的错误,开发者应该尽量的避免,这里有一些可能出错的案例。

每个event的key不能超过10个,event ID、map中key和value都不能使用特殊字符,且长度不能超过255个字符(否则将截取前255个字符),“id", “ts", “du"是保留字段,不能作为event ID及key的名称

5. 使用分发渠道分析有时需要统计应用程序的分发渠道,例如有多少用户来从联想乐园下载了您的应用,又有多少用户通过Google android market下载到您的应用程序。您只需要在AndroidManifest.xml里添加meta-data,并将 value属性修改为对应的发布渠道名。
配置AndroidManifest.XML添加下面代码
<application ……><activity ……/><meta-dataandroid:value="Channel ID"android:name="UMENG_CHANNEL"/></application>当然,这需要您在不同渠道发布应用程序时,重新编译打包。
说明
不要改变'UMENG_CHANNEL',修改'Channel ID'为您的渠道名称,注意不能是纯数字(eg.value="AndroidMarket")。

每台设备只记录第一次统计到的渠道,您如果在测试的时候发现渠道统计到的设备数量不增加,很可能是因为您用同一个设备修改过渠道号,您换一台设备测试即可。

6. 使用在线配置功能这个功能目前可以帮你在网站上动态配置两种类型的参数:

自定义key-value型的键值对

数据发送策略

在程序的入口Activity的OnCreate()方法中调用
publicvoid onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MobclickAgent.updateOnlineConfig(this);}API:public voidupdateOnlineConfig(Context context)

[!--infotagslink--]

相关文章

  • Android子控件超出父控件的范围显示出来方法

    下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
  • Android开发中findViewById()函数用法与简化

    findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20
  • Android模拟器上模拟来电和短信配置

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

    夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
  • android自定义动态设置Button样式【很常用】

    为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
  • Android WebView加载html5页面实例教程

    如果我们要在Android应用APP中加载html5页面,我们可以使用WebView,本文我们分享两个WebView加载html5页面实例应用。 实例一:WebView加载html5实现炫酷引导页面大多...2016-09-20
  • 深入理解Android中View和ViewGroup

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

    下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
  • Android用MemoryFile文件类读写进行性能优化

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

    TextView默认是横着显示了,今天我们一起来看看Android设置TextView竖着显示如何来实现吧,今天我们就一起来看看操作细节,具体的如下所示。 在开发Android程序的时候,...2016-10-02
  • android.os.BinderProxy cannot be cast to com解决办法

    本文章来给大家介绍关于android.os.BinderProxy cannot be cast to com解决办法,希望此文章对各位有帮助呀。 Android在绑定服务的时候出现java.lang.ClassCastExc...2016-09-20
  • Android 实现钉钉自动打卡功能

    这篇文章主要介绍了Android 实现钉钉自动打卡功能的步骤,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下...2021-03-15
  • Android 开发之布局细节对比:RTL模式

    下面我们来看一篇关于Android 开发之布局细节对比:RTL模式 ,希望这篇文章对各位同学会带来帮助,具体的细节如下介绍。 前言 讲真,好久没写博客了,2016都过了一半了,赶紧...2016-10-02
  • Android中使用SDcard进行文件的读取方法

    首先如果要在程序中使用sdcard进行存储,我们必须要在AndroidManifset.xml文件进行下面的权限设置: 在AndroidManifest.xml中加入访问SDCard的权限如下: <!--...2016-09-20
  • Android开发之PhoneGap打包及错误解决办法

    下面来给各位简单的介绍一下关于Android开发之PhoneGap打包及错误解决办法,希望碰到此类问题的同学可进入参考一下哦。 在我安装、配置好PhoneGap项目的所有依赖...2016-09-20
  • 用Intel HAXM给Android模拟器Emulator加速

    Android 模拟器 Emulator 速度真心不给力,, 现在我们来介绍使用 Intel HAXM 技术为 Android 模拟器加速,使模拟器运行度与真机比肩。 周末试玩了一下在Eclipse中使...2016-09-20
  • Android判断当前屏幕是全屏还是非全屏

    在安卓开发时我碰到一个问题就是需要实现全屏,但又需要我们来判断出用户是使用了全屏或非全屏了,下面我分别找了两段代码,大家可参考。 先来看一个android屏幕全屏实...2016-09-20
  • Android开发中布局中的onClick简单完成多控件时的监听的利与弊

    本文章来为各位介绍一篇关于Android开发中布局中的onClick简单完成多控件时的监听的利与弊的例子,希望这个例子能够帮助到各位朋友. 首先在一个控件加上这么一句:and...2016-09-20
  • Ubuntu 系统下安装Android开发环境 Android Studio 1.0 步骤

    Android Studio 是一个Android开发环境,基于IntelliJ IDEA. 类似 Eclipse ADT,Android Studio 提供了集成的 Android 开发工具用于开发和调试,可以在Linux,Mac OS X,Window...2016-09-20
  • Android实现简单用户注册案例

    这篇文章主要为大家详细介绍了Android实现简单用户注册案例,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-05-26