您的位置:首页 > 移动开发 > Android开发

作业——在线学习Android课程之第十二周(内存、视图、电量优化)

2016-05-22 15:28 489 查看

一、内存调试工具的简介

MemoryMonitor, AllocationTracker以及HeapDump,LeakCanary

(1)、Memory Monitor

方便显示内存使用和GC情况
快速定位卡顿是否与GC有关
快速定位Crash是否和内存占用过高有关
快速定位潜在的内存泄漏问题
简单易用
不能准确定位问题


(2)、Allocation Tracker

定位代码中分配的对象的类型,大小,时间,线程,堆栈等信息
定位内存抖动问题
配合Heap Viewer一起定位内存泄漏问题
使用复杂


(3)、HeapDump

内存快照信息
每次GC之后收集一次信息
查找内存泄漏利器
使用复杂


(4)、LeakCanary

查找内存泄漏利器


二、内存调试工具的调用方法

1、MemoryMonitor的调用方法

(1)选择“View”,选择“Tool Windows”,选择“Android Monitor”。

(2)在界面下方选择“Monitor”,往上滚动到“Memory”视图即可。



2、 AllocationTracker的调用方法

(1)在程序运行后,需要的时间点,点击“Start Allocation Tracking”,开启记录

(2)再次点击“Stop Allocation Tracking”,停止记录



(3)以”方法“或”对象“为目标查看



3、HeapDump的调用方法

(1)在程序运行后,需要的时间点,点击“Dump Java Heap”

(2)在弹出的新窗口内,可以选择以”Package Tree View“查看结果



4、LeakCanary的调用方法

(1)在build.gradle文件的依赖中添加

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.1'

debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1' // or 1.4-beta1
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // or 1.4-beta1
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // or 1.4-beta1
}


(2)新建MyApplication类,以初始化LeakCanary

public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();

LeakCanary.install(this);
}
}


(3)别忘记在AndroidMainfest.xml中注册MyApplication

<application
android:name=".MyApplication"

……   //其他代码

</application>


(4)在调试时若出现内存泄漏即会弹出通知,点击后显示详情



三、内存调试工具的实际使用

下载bug项目:https://github.com/lzyzsd/MemoryBugs,请注意配合使用MemoryMonitor, AllocationTracker以及HeapDump,LeakCanary等工具来查找潜在的内存问题,并尝试解决。

1、

(1)、运行程序,查看MemoryMonitor,发现当点击“STARTALLOCATION”后出现内存抖动现象。



(2)、启用AllocationTracker,再点击“STARTALLOCATION”按钮,发现MainActivity创建了大量的Rect



(3)、在程序中找到相关代码

private void startAllocationLargeNumbersOfObjects() {
Toast.makeText(this, "请注意查看MemoryMonitor 以及AllocationTracker", Toast.LENGTH_SHORT).show();
for (int i = 0; i < 10000; i++) {
Rect rect = new Rect(0, 0, 100, 100);
System.out.println("-------: " + rect.width());
}
}


(4)、将Rect的创建移出for循环

private void startAllocationLargeNumbersOfObjects() {
Toast.makeText(this, "请注意查看MemoryMonitor 以及AllocationTracker", Toast.LENGTH_SHORT).show();

Rect rect = new Rect(0, 0, 100, 100);
//将Rect的创建移出for循环

for (int i = 0; i < 10000; i++) {
System.out.println("-------: " + rect.width());
}
}


2、

(1)、点击“STARTACTIVITYB”按钮后,若干秒后LeakCanary弹出内存泄漏通知



(2)、查看代码,MainActivity已finish()

private void startB() {
finish();
startActivity(new Intent(this, ActivityB.class));
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
System.out.println("post delayed may leak");
}
}, 5000);
Toast.makeText(this, "请注意查看通知栏LeakMemory", Toast.LENGTH_SHORT).show();
}


(3)、查看HeapDump,MainActivity未被销毁,与sTextView和Handler有关



(4)、修改代码,如下:

//    private static TextView sTextView;
------------------------------------------------------------------
private TextView sTextView;

==================================================================

//    Handler mHandler = new Handler(new Handler.Callback() {
//        @Override
//        public boolean handleMessage(Message msg) {
//            return false;
//        }
------------------------------------------------------------------
public MyHandler mHandler = new MyHandler(this);
public static class MyHandler extends Handler {
WeakReference<MainActivity > mActivityReference;
MyHandler(MainActivity activity) {
mActivityReference= new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
final MainActivity activity = mActivityReference.get();
if (activity != null) {
return;
}
}

}


3、在onDraw方法中使用了“new”方法,移出

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

RectF rect =  new RectF(0, 0, 100, 100);
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStrokeWidth(4);
canvas.drawArc(rect, 0, 180, true, paint);
}


四、视图优化

(1)Overdraw简介

Overdraw就是过度绘制,是指在一帧的时间内(16.67ms)像素被绘制了多次,理论上一个像素每次只绘制一次是最优的,但是由于重叠的布 局导致一些像素会被多次绘制,而每次绘制都会对应到CPU的一组绘图命令和GPU的一些操作,当这个操作耗时超过16.67ms时,就会出现掉帧现象,表现为应用卡顿,所以对重叠不可见元素的重复绘制会产生额外的开销,需要尽量减少Overdraw的发生。

(2)Overdraw检测

Android提供了测量Overdraw的选项,在开发者选项-调试GPU过度绘制(Show GPU Overdraw),打开选项就可以看到当前页面Overdraw的状态,就可以观察屏幕的绘制状态。该工具会使用三种不同的颜色绘制屏幕,来指示 overdraw发生在哪里以及程度如何,其中:

没有颜色: 意味着没有overdraw。像素只画了一次。

蓝色: 意味着overdraw 1倍。像素绘制了两次。大片的蓝色还是可以接受的(若整个窗口是蓝色的,可以摆脱一层)。

绿色: 意味着overdraw 2倍。像素绘制了三次。中等大小的绿色区域是可以接受的但你应该尝试优化、减少它们。

浅红: 意味着overdraw 3倍。像素绘制了四次,小范围可以接受。

暗红: 意味着overdraw 4倍。像素绘制了五次或者更多。这是错误的,要修复它们。

(3)怎么消灭overdraw

第一招:合理选择控件容器

既然overdraw是因为重复绘制了同一片区域的像素点,那我们首先想到的是解决布局问题。Android提供的Layout控件主要包括LinearLayout、TableLayout、FrameLayout、RelativeLayout。俗话说条条大路通罗马,同一个界面我们可以使用不同的容器控件来表达,但是各个容器控件描述界面的复杂度是不一样的。一般来说LinearLayout最易,RelativeLayout较复杂。但是尺有所短,寸有所长,LinearLayout只能用来描述一个方向上连续排列的控件,而RelativeLayout几乎可以用于描述任意复杂度的界面。但是我又要说但是了,表达能力越强的容器控件,性能往往略低一些,因为系统需要将更多的时间花在计算子控件的位置上。综上所述:LinearLayout易用,效率高,表达能力有限。RelativeLayout复杂,表达能力强,效率稍逊。

那么对于同一界面而言,作为开发者考虑是使用尽量少的、表达能力强的RelativeLayout作为容器,还是选择多个、表达能力稍弱的LinearLayout来展示。从减少overdraw的角度来看,LinearLayout会增加控件数的层级,自然是RelativeLayout更优,但是当某一界面在使用LinearLayout并不会比RelativeLayout带来更多的控件数和控件层级时,LinearLayout则是首选。所以在表达界面的时候,作为一个有前瞻性的开发者要根据实际情况来选择合适容器控件,在保证性能的同时,尽量避免overdraw。

第二招:去掉window的默认背景

当我们使用了Android自带的一些主题时,window会被默认添加一个纯色的背景,这个背景是被DecorView持有的。当我们的自定义布局时又添加了一张背景图或者设置背景色,那么DecorView的background此时对我们来说是无用的,但是它会产生一次Overdraw,带来绘制性能损耗。

去掉window的背景可以在onCreate()中setContentView()之后调用

getWindow().setBackgroundDrawable(null);


或者在theme中添加

android:windowbackground="null";


第三招:去掉其他不必要的背景

有时候为了方便会先给Layout设置一个整体的背景,再给子View设置背景,这里也会造成重叠,如果子View宽度mach_parent,可以看到完全覆盖了Layout的一部分,这里就可以通过分别设置背景来减少重绘。再比如如果采用的是selector的背景,将normal状态的color设置为“@android:color/transparent”,也同样可以解决问题。这里只简单举两个例子,我们在开发过程中的一些习惯性思维定式会带来不经意的Overdraw,所以开发过程中我们为某个View或者ViewGroup设置背景的时候,先思考下是否真的有必要,或者思考下这个背景能不能分段设置在子View上,而不是图方便直接设置在根View上。

第四招:ClipRect & QuickReject

为了解决Overdraw的问题,Android系统会通过避免绘制那些完全不可见的组件来尽量减少消耗。但是不幸的是,对于那些过于复杂的自定义的View(通常重写了onDraw方法),Android系统无法检测在onDraw里面具体会执行什么操作,系统无法监控并自动优化,也就无法避免Overdraw了。但是我们可以通过canvas.clipRect()来帮助系统识别那些可见的区域。这个方法可以指定一块矩形区域,只有在这个区域内才会被绘制,其他的区域会被忽视。这个API可以很好的帮助那些有多组重叠组件的自定义View来控制显示的区域。同时clipRect方法还可以帮助节约CPU与GPU资源,在clipRect区域之外的绘制指令都不会被执行,那些部分内容在矩形区域内的组件,仍然会得到绘制。除了clipRect方法之外,我们还可以使用canvas.quickreject()来判断是否没和某个矩形相交,从而跳过那些非矩形区域内的绘制操作。

第五招:ViewStub

ViewStub是个什么东西?一句话总结:高效占位符。

我们经常会遇到这样的情况,运行时动态根据条件来决定显示哪个View或布局。常用的做法是把View都写在上面,先把它们的可见性都设为View.GONE,然后在代码中动态的更改它的可见性。这样的做法的优点是逻辑简单而且控制起来比较灵活。但是它的缺点就是,耗费资源。虽然把View的初始可见View.GONE但是在Inflate布局的时候View仍然会被Inflate,也就是说仍然会创建对象,会被实例化,会被设置属性。也就是说,会耗费内存等资源。

推荐的做法是使用android.view.ViewStub,ViewStub是一个轻量级的View,它一个看不见的,不占布局位置,占用资源非常小的控件。可以为ViewStub指定一个布局,在Inflate布局的时候,只有ViewStub会被初始化,然后当ViewStub被设置为可见的时候,或是调用了ViewStub.inflate()的时候,ViewStub所向的布局就会被Inflate和实例化,然后ViewStub的布局属性都会传给它所指向的布局。这样,就可以使用ViewStub来方便的在运行时,要还是不要显示某个布局。

<ViewStub
android:id="@+id/stub_view"
android:inflatedId="@+id/panel_stub"
android:layout="@layout/progress_overlay"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />


当你想加载布局时,可以使用下面其中一种方法:

((ViewStub) findViewById(R.id.stub_view)).setVisibility(View.VISIBLE);
View importPanel = ((ViewStub) findViewById(R.id.stub_view)).inflate();


第六招:Merge

Merge标签有什么用呢?简单粗暴点回答:干掉一个view层级。

Merge的作用很明显,但是也有一些使用条件的限制。有两种情况下我们可以使用Merge标签来做容器控件。第一种子视图不需要指定任何针对父视图的布局属性,就是说父容器仅仅是个容器,子视图只需要直接添加到父视图上用于显示就行。另外一种是假如需要在LinearLayout里面嵌入一个布局(或者视图),而恰恰这个布局(或者视图)的根节点也是LinearLayout,这样就多了一层没有用的嵌套,无疑这样只会拖慢程序速度。而这个时候如果我们使用merge根标签就可以避免那样的问题。另外Merge只能作为XML布局的根标签使用,当Inflate以开头的布局文件时,必须指定一个父ViewGroup,并且必须设定attachToRoot为true。

举个简单的例子吧:

<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="merge标签使用" />
</RelativeLayout>


把上面这个XML加载到页面中,布局层级是RelativeLayout-TextView。但是采用下面的方式,把RelativeLayout提换成merge,RelativeLayout这一层级就被干掉了。

<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="merge标签使用" />
</merge>


第七招:善用draw9patch

给ImageView加一个边框,你肯定遇到过这种需求,通常在ImageView后面设置一张背景图,露出边框便完美解决问题,此时这个ImageView,设置了两层drawable,底下一层仅仅是为了作为图片的边框而已。但是两层drawable的重叠区域去绘制了两次,导致overdraw。

优化方案: 将背景drawable制作成draw9patch,并且将和前景重叠的部分设置为透明。由于Android的2D渲染器会优化draw9patch中的透明区域,从而优化了这次overdraw。 但是背景图片必须制作成draw9patch才行,因为Android 2D渲染器只对draw9patch有这个优化,否则,一张普通的Png,就算你把中间的部分设置成透明,也不会减少这次overdraw。

第八招:慎用Alpha

假如对一个View做Alpha转化,需要先将View绘制出来,然后做Alpha转化,最后将转换后的效果绘制在界面上。通俗点说,做Alpha转化就需要对当前View绘制两遍,可想而知,绘制效率会大打折扣,耗时会翻倍,所以Alpha还是慎用。

如果一定做Alpha转化的话,可以采用缓存的方式。

view.setLayerType(LAYER_TYPE_HARDWARE);
doSmoeThing();
view.setLayerType(LAYER_TYPE_NONE);


通过setLayerType方式可以将当前界面缓存在GPU中,这样不需要每次绘制原始界面,但是GPU内存是相当宝贵的,所以用完要马上释放掉。

第九招:避免“OverDesign”

overdraw会给APP带来不好的体验,overdraw产生的原因无外乎:复杂的Layout层级,重叠的View,重叠的背景这几种。开发人员无节制的View堆砌,究其根本无非是产品无节制的需求设计。有道是“由俭入奢易,由奢入俭难”,很多APP披着过度设计的华丽外衣,却忘了简单易用才是王道的本质,纷繁复杂的设计并不会给用户带来好的体验,反而会让用户有压迫感,产品本身也有可能因此变得卡顿。当然,一切抛开业务谈优化都是空中楼阁,这就需要产品设计也要有一个权衡,在复杂的业务逻辑与简单易用的界面展现中做一个平衡,而不是一味的OverDesign。

链接:www.jianshu.com/p/145fc61011cd

五、电量优化

我们应该尽量减少唤醒屏幕的次数与持续的时间,使用WakeLock来处理唤醒的问题,能够正确执行唤醒操作并根据设定及时关闭操作进入睡眠状态。

某些非必须马上执行的操作,例如上传歌曲,图片处理等,可以等到设备处于充电状态或者电量充足的时候才进行。

触发网络请求的操作,每次都会保持无线信号持续一段时间,我们可以把零散的网络请求打包进行一次操作,避免过多的无线信号引起的电量消耗。

链接:www.cnblogs.com/lipeil/p/5222917.html

1、消耗电量的几个主要原因、功能

大数据量的网络传输(网络)

不停的网络切换(网络)

解析大量的数据(CPU)

2、关于网络方面的优化

1)、网络请求之前,检查网络连接。没有网络连接不进行请求

2)、判断网络类型,针对特定的数据在特定的网络下请求。例如:大量数据传输的时候,在wifi下请求。wifi下下载数据耗电量只有2、3、4G的1/3.

3)、使用效率高的解析工具。根据具体业务数据量的大小,选择合适的解析工具。例如android上面的协议解析一般推荐json。

4)、使用GZIP压缩方式下载数据,能减少网络流量,缩短下载时间

5)、合理使用缓存,避免重复操作

6)、使用推送,代替循环请求

7)、触发网络请求的操作,每次都会保持无线信号持续一段时间,我们可以把零散的网络请求打包进行一次操作,避免过多的无线信号引起的电量消耗。

8)、是JobScheduler API所做的事情。它会根据当前的情况与任务,组合出理想的唤醒时间,例如等到正在充电或者连接到WiFi的时候,或者集中任务一起执行。我们可以通过这个API实现很多免费的调度算法。

复制代码

3、控件:

1)、对于自定义控件,我们可以通过canvas.clipRect()来帮助系统识别那些可见的区域。这个方法可以指定一块矩形区域,只有在这个区域内才会被绘制,其他的区域会被忽视。

2)、避免嵌套太多层控件

3)、合理使用include、merge

4、GC相关优化:

1)、android的GC机制:Android里面是一个三级Generation的内存模型,最近分配的对象会存放在Young Generation区域,当这个对象在这个区域停留的时间达到一定程度,它会被移动到Old Generation,最后到Permanent Generation区域。

2)、执行GC操作的时候,任何线程的任何操作都会需要暂停,等待GC操作完成之后,其他操作才能够继续运行。

3)、Android系统里面有一个Generational Heap Memory的模型,系统会根据内存中不同的内存数据类型分别执行不同的GC操作。例如,最近刚分配的对象会放在Young Generation区域,这个区域的对象通常都是会快速被创建并且很快被销毁回收的,同时这个区域的GC操作速度也是比Old Generation区域的GC操作速度更快的。

4)、避免GC频繁操作:

a、避免内存抖动;Memory Churn内存抖动,内存抖动是因为大量的对象被创建又在短时间内马上被释放。

b、瞬间产生大量的对象会严重占用Young Generation的内存区域,当达到阀值,剩余空间不够的时候,也会触发GC。即使每次分配的对象占用了很少的内存,但是他们叠加在一起会增加Heap的压力,从而触发更多其他类型的GC。这个操作有可能会影响到帧率,

并使得用户感知到性能问题。

c、避免在for循环,onDraw中创建对象,无法避免的可以创建对象池,然后在不使用的时候释放

5)、主动回收java对象,特别是较大的,例如bitmap。减少GC的工作频率

复制代码

5、其他:

1)、尽量不要使用浮点运算

2)、定位可以使用wifi和移动网络基站,不要使用GPS。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android