王学岗性能优化8——长图优化与电量优化
第一;长图优化
我们加载一张比屏幕还大的图片该如何处理呢?
解决方案一:沿对角线缩放,缩放到屏幕的宽度。然后上下滑动显示这张图片;
解决方案二:用手指按住图片,在屏幕上滑动显示。
以上两种方案都不会加载整张图片到内存,而是加载屏幕上显示的图片。
我们这里有一张大图片,我们把它放到assets目录里面.
这是那张大图片,够大的吧,超出屏幕长度几十倍。你要是不经处理直接加载到内存,估计直接OOM。
我们看下我们定义的BigView,专门加载上面那种大图使用我们自定义一个View,仿照腾讯的写法。读者可在项目中直接使用该View。
package com.example.administrator.lsn_8_demo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;
import java.io.IOException;
import java.io.InputStream;
public class BigView extends View implements GestureDetector.OnGestureListener,View.OnTouchListener{
private Rect mRect;
private BitmapFactory.Options mOptions;
private GestureDetector mGestureDetector;
private Scroller mScroller;
private int mImageWidth;
private int mImageHeight;
private BitmapRegionDecoder mDecoder;
private int mViewWidth;
private int mViewHeight;
private float mScale;
private Bitmap bitmap;
public BigView(Context context) {
this(context,null,0);
}
public BigView(Context context, @Nullable AttributeSet attrs) {
this(context,attrs,0);
}
public BigView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//指定要加载的区域
//mrect指的是图上缩放后的区域
mRect =new Rect();
//需要复用,如果这张图换了一张图,同样的内存块就不需要加载。
mOptions=new BitmapFactory.Options();
//手势识别类,在屏幕上上下左右的去滑动,手势需要识别出用户往哪个方向动
mGestureDetector=new GestureDetector(context,this);
//设置onTouchListener
setOnTouchListener(this);
//滑动帮助类
mScroller=new Scroller(context);
}
/**
* 1,由使用者输入一张图片,从流中读取图片,
* 2,用流是有好处的,不管是从网络,还是从你本地的文件都可以从流中读取
*/
public void setImage(InputStream is){
//先读取原图片的信息 高,宽
mOptions.inJustDecodeBounds=true;
//从流中读取图片,读取完成后信息会在mOption里
BitmapFactory.decodeStream(is,null,mOptions);
mImageWidth=mOptions.outWidth;
mImageHeight=mOptions.outHeight;
//开启复用,打开异变
mOptions.inMutable=true;
//设置格式成RGB_565,RGB_565存储的字节少,只要两字节
mOptions.inPreferredConfig=Bitmap.Config.RGB_565;
mOptions.inJustDecodeBounds=false;
//创建一个区域解码器,可以只解码一部分
try {
//1,第二个参数的意思是能不能共享这个区域,一般情况下,我们不会让多个程序读同一个流的内容。
//所以这个参数绝大部分情况下都是false.
mDecoder=BitmapRegionDecoder.newInstance(is,false);
} catch (IOException e) {
e.printStackTrace();
}
//1,设置完这张图片以后,我就可以要求测量绘制这些动作按照压缩之后的
//标准进行绘制。
requestLayout();
}
/**
* 1,在测量的时候把我们需要的内存区域获取到 存入到mRect中
* 2,测量出要显示的区域
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取测量的view的大小
//注意mViewWidth、mViewHeight不要写成局部变量,容易造成内存抖动
mViewWidth=getMeasuredWidth();
mViewHeight=getMeasuredHeight();
//1,确定要加载的图片的区域
//2,显示的内容肯定是从00(左上角)开始,
mRect.left=0;
mRect.top=0;
mRect.right=mImageWidth;
//获取一个高度缩放因子
mScale=mViewWidth/(float)mImageWidth;
//高度就根据缩放比进行获取
mRect.bottom=(int)(mViewHeight/mScale);
}
/**
* 1,画出内容
* 2,在Android 3.0 引进了BitmapFactory.Options.inBitmap. 如果这个值被设置了,
* decode方法会在加载内容的时候去重用已经存在的bitmap.
* 这意味着bitmap的内存是被重新利用的,这样可以提升性能,
* 并且减少了内存的分配与回收。
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//如果解码器拿不到,表示没有设置过要显示的图片
if(null==mDecoder){
return;
}
//复用上一张bitmap,bitmap所使用的内存块是可以复用的。
//可以长时间复用同一内存块
mOptions.inBitmap=bitmap;
//1,解码指定的区域
//2,在mRect这块空间里进行复用
//3,使用decodeRegion的好处是我可以指定只占某一块,把一张图片的一块解码出来,
// 放到bitmap里去
bitmap=mDecoder.decodeRegion(mRect,mOptions);
//1,把得到的矩阵大小的内存进行缩放 得到view的大小
//matrix的大小就是view 的大小
//2,定义一个matrix,负责缩放
Matrix matrix=new Matrix();
//宽高按照同样的方式缩放
matrix.setScale(mScale,mScale);
//画出来,不需要画笔
canvas.drawBitmap(bitmap,matrix,null);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
//交给手势处理,因为手势处理可以得到方向,
// onTouch处理方向很麻烦
return mGestureDetector.onTouchEvent(event);
}
/**
* 手按下的回调
* @param e
* @return
*/
@Override
public boolean onDown(MotionEvent e) {
//滑动会产生惯性,如果上次滑动惯性还没有停止,强制停止
if(!mScroller.isFinished()){
mScroller.forceFinished(true);
}
//继续接收后续动作/事件
return true;
}
/**
*
* @param e1 手指按下的事件,获取开始坐标
* @param e2 当前首饰的事件,获取当前坐标
* @param distanceX 左右(X)移动时的距离
* @param distanceY 上下(Y)移动时的距离
* @return
*/
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//1,上下移动的时候,需要改变显示区域,就是改mRect的内容
//2,这两个参数,第一个0表示左右不移动,第二个参数表示上下动的时候跟着distabceY进行移动
//手指移动的时候有坐标,跟着坐标一起动。
mRect.offset(0,(int)distanceY);
//1,处理移动时已经移到了两个顶端的问题
//顶部到了手机顶部就不能在向下走了。
if(mRect.bottom>mImageHeight){
mRect.bottom=mImageHeight;
//(int)(mViewHeight/mScale)整个这块的高度
mRect.top=mImageHeight-(int)(mViewHeight/mScale);
}
if(mRect.top<0){
mRect.top=0;
mRect.bottom=(int)(mViewHeight/mScale);
}
invalidate();
return false;
}
/**
* 处理惯性问题
* @param e1 手指按下的事件,获取开始坐标
* @param e2 当前首饰的事件,获取当前坐标
* @param velocityX 每秒移动的x轴方向的像素点,滑动的快,移动的像素点多,慢就少
* @param velocityY 每秒移动的y像素点
* @return
*/
//惯性滑动。 给定一个初始速度(velocityX,velocityY),该方法内部会根据这个速度去计算需要
// 滑动的距离以及需要耗费的时间。通常用于:界面的惯性滑动等。
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
//1,做计算,x不处理,只处理Y
//这个方法用来处理惯性滑动,比如我们手指松开了,由于惯性图片依然会继续滑动,惯性滑动到
//2,哪里呢?就是我们这八个参数
mScroller.fling(0,mRect.top,
0,(int)-velocityY,
0,0,
0,mImageHeight-(int)(mViewHeight/mScale));
return false;
}
/**
* onFling的计算结果交给该方法
*/
@Override
public void computeScroll() {
//已经不动了,滑动已经结束了。
if(mScroller.isFinished()){
return;
}
//true 表示当前滑动还没有结束,手指松开后会滑动一段距离
if(mScroller.computeScrollOffset()){
mRect.top=mScroller.getCurrY();
mRect.bottom=mRect.top+(int)(mViewHeight/mScale);
invalidate();
}
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
}
xml中的布局
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.administrator.lsn_8_demo.BigView
android:id="@+id/bigView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</android.support.constraint.ConstraintLayout>
在MainActivity中使用
package com.example.administrator.lsn_8_demo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import java.io.IOException;
import java.io.InputStream;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BigView bigView=findViewById(R.id.bigView);
InputStream is=null;
try{
//加载图片
is=getAssets().open("big.png");
bigView.setImage(is);
}catch(Exception e){
e.printStackTrace();
}finally {
if(is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
第二电量优化
一:电量测试
电量测试:就是测试移动设备电量消耗快慢的一种测试方法。一般用平均电流来衡量电量消耗速度。平均电流越小,说明设备使用时间越长。但是平均电流多大才说明不耗电却没有一个统一标准。
硬件测试:利用硬件设备测试被测设备的电流,统计一段时间内(使用某个功能模块)的平均电流值。跟我们Android程序员没有太多关系
软件测试:利用系统工具导出分析报告
二:Battery Historian
Battery Historian是google开源的电池历史数据分析工具。下载地址
如何安装下载这个文件?猛戳这里和这里
看下耗电量图