Android性能优化

来源:互联网 发布:windows vista精简版 编辑:程序博客网 时间:2024/06/14 20:31
今天看了点性能优化的东西,虽然基础,还是拿出来和大家分享下。总所周知啊,手机作为一种移动设备,不管是内存还是CPU的性能都收到了一定的影响,这也意味着Android程序不可能无限制地使用内存和CPU资源,过多的使用内存会导致内存溢出(OOM)。过多地使用CPU资源,即做大量的耗时任务,会导致手机卡顿甚至出现ANR。因此,作为一个开放人员,我们在写代码之前要有一定的意识去优化代码,提高程序的性能。下面我就来介绍下通常我们在代码编程中要注意的内容:布局优化、绘制优化、内存泄露优化、响应速度优化、listview优化、Bitmap优化、线程优化等等。说到性能优化,我们不得不注意的一个问题就是内存泄露,内存泄露并不会导致程序功能异常,但是会导致Android程序的内存占用过大,这会提高内存溢出的几率。

1、布局优化

布局优化的思想很简单,就是尽量减少布局文件的层级,布局中的层级少了,这就意味着Android绘制时的工作量少了,那么程序的性能自然就提高了。
*首先删除布局中无用的控件和层级,有选择地使用性能较低的ViewGroup
*优先使用LinearLayout或FrameLayout,因为RelativeLayout的功能比较复杂,它的布局过程需要花费更多的CPU时间
*复杂的布局场景需要使用嵌套方式来实现时,优先考虑使用RelativeLayout
*采用标签、标签和ViewStub
主要用于布局重用,ViewStub提供了按需加载的功能,这提高了初始化的效率

1、重用

< include/>标签可以在一个布局中引入另外一个布局,这个的好处显而易见。类似于我们经常用到的工具类,随用随调。便于统一修改使用。

2、合并

减少嵌套:首先我们心中要有一个大原则:尽量保持布局层级的扁平化。在这个大原则下我们要知道:在不影响层级深度的情况下,使用LinearLayout而不是RelativeLayout。因为RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,才会让子View调用2次onMeasure。Measure的耗时越长那么绘制效率就低。如果非要是嵌套,那么尽量避免RelativeLayout嵌套RelativeLayout。这简直就是恶性循环,丧心病狂。实现方法就不细说了,大家都是明白人。
< merge/>主要用来去除不必要的FrameLayout。它的使用最理想的情况就是你的根布局是FrameLayout,同时没有使用background等属性。这时可以直接替换。因为我们布局外层就是FrameLayout,直接“合并”。

<?xml version="1.0" encoding="utf-8"?><merge xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent">    <include        layout="@layout/title_bar"/></merge>

3、用TextView同时显示图片和文字

<?xml version="1.0" encoding="utf-8"?><LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent">    <TextView        android:drawableLeft="@drawable/icon_1"        android:drawableRight="@drawable/icon_4"        android:drawablePadding="10dp"        android:paddingLeft="10dp"        android:paddingRight="10dp"        android:textSize="16sp"        android:text="我的卡券"        android:background="@color/white"        android:gravity="center_vertical"        android:layout_width="match_parent"        android:layout_height="50dp" /></LinearLayout>当然EditView等也一样的,还有属性drawableBottom和drawableTop供你使用。同时利用代码setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom)可以让我们动态去设置图片。

4、使用TextView的行间距

<?xml version="1.0" encoding="utf-8"?><LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_height="100dp"    android:background="@color/white"    android:layout_width="match_parent">    <ImageView        android:padding="25dp"        android:src="@drawable/kd_1"        android:layout_width="100dp"        android:layout_height="match_parent"/>    <TextView        android:textSize="14dp"        android:lineSpacingExtra="8dp"        android:gravity="center_vertical"        android:text="揽件方式:上门取件\n快递公司:顺丰快递\n预约时间:9月6日 立即取件\n快递费用:等待称重确定价格"        android:layout_width="match_parent"        android:layout_height="match_parent" /></LinearLayout>

可以看到我们仅仅利用Android:lineSpacingExtra=”8dp”这一行代码就省去了3个TextView,如果行数更多呢?是不是方便多了。其中:lineSpacingExtra属性代表的是行间距,他默认是0,是一个绝对高度值。同时还有lineSpacingMultiplier属性,它代表行间距倍数,默认为1.0f,是一个相对高度值。我们来使用一下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_height="100dp"    android:background="@color/white"    android:layout_width="match_parent">    <ImageView        android:padding="25dp"        android:src="@drawable/kd_1"        android:layout_width="100dp"        android:layout_height="100dp"/>    <TextView        android:textSize="14dp"        android:lineSpacingMultiplier="1.3"        android:gravity="center_vertical"        android:text="揽件方式:上门取件\n快递公司:顺丰快递\n预约时间:9月6日 立即取件\n快递费用:等待称重确定价格"        android:layout_width="match_parent"        android:layout_height="match_parent" /></LinearLayout>

当然了这两条属性可以同时使用,查看源码可以知道,他们的高度计算规则为mTextPaint.getFontMetricsInt(null) * 行间距倍数 + 行间距。

5、使用Spannable或Html.fromHtml


如果实现上图红框中的效果,笨办法就是写三个TextView,“¥”,“价格”,“门市价”分别实现,其实用一个TextVIew就可以实现,类似如下代码:

String text = String.format("¥%1$s  门市价:¥%2$s", 18.6, 22); int z = text.lastIndexOf("门"); SpannableStringBuilder style = new SpannableStringBuilder(text); style.setSpan(new AbsoluteSizeSpan(DisplayUtil.dip2px(mContext,14)), 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); //字号 style.setSpan(new ForegroundColorSpan(Color.parseColor("#afafaf")), z, text.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE); //颜色 style.setSpan(new AbsoluteSizeSpan(DisplayUtil.dip2px(mContext,14)), z, text.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE); //字号 tv.setText(style);

同样Html.fromHtml也可以实现。

6、按需载入:ViewStub

在开发中经常会遇到这样的情况,会在程序运行时动态根据条件来决定显示哪个View或某个布局。那么通常做法就是把用到的View都写在布局中,然后在代码中动态的更改它的可见性。但是它的这样仍然会创建View,会影响性能。
这时就可以用到ViewStub了,ViewStub是一个轻量级的View,不占布局位置,占用资源非常小。ViewStub标签同include标签一样可以用来引入一个外部布局,不同的是,ViewStub引入的布局默认不会扩张,即既不会占用显示也不会占用位置,从而在解析layout时节省CPU和内存。
例子:比如我们请求网络加载列表,如果网络异常或者加载失败我们可以显示一个提示View,上面可以点击重新加载。当然一直没有错误时,我们就不显示。

<?xml version="1.0" encoding="utf-8"?><merge xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent" >    ……    <ViewStub        android:layout_gravity="center"        android:id="@+id/hint_view"        android:layout_width="match_parent"        android:inflatedId="@+id/hint_view"        android:layout_height="wrap_content"        android:layout="@layout/hint_view"/></merge>用法:private View hintView;if (网络异常。。。) {    if (hintView == null) {        ViewStub viewStub = (ViewStub)this.findViewById(R.id.hint_view);        hintView = viewStub.inflate();        TextView textView = (TextView) hintView.findViewById(R.id.tv);        textView.setText("网络异常!");    }    hintView.setVisibility(View.VISIBLE);}else{    if (hintView != null) {        hintView.setVisibility(View.GONE);    }}注意:1.一旦ViewStub可见或是被inflate了,ViewStub就不存在了,取而代之的是被inflate的Layout。所以它也被称做惰性控件。并且android:inflatedId的值将作为根布局的id。注:2. ViewStub目前有个缺陷就是还不支持 <merge /> 标签。

7、其他小技巧
用LinearLayout自带的分割线
还记得上文用TextView同时显示图片和文字中的例子吗?我们可以看到每个条目之间都是有一根分隔线的,那么怎么实现呢?别人我不知道,反正我原来是用一个View设置高度实现的。相信一定有人和我一样。

<LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    android:divider="@drawable/divider"    android:showDividers="middle">    <TextView        android:drawableLeft="@drawable/icon_1"        android:drawableRight="@drawable/icon_4"        android:drawablePadding="10dp"        android:paddingLeft="10dp"        android:paddingRight="10dp"        android:textSize="16sp"        android:text="我的卡券"        android:background="@color/white"        android:gravity="center_vertical"        android:layout_width="match_parent"        android:layout_height="50dp" />    <TextView        android:drawableLeft="@drawable/icon_2"        android:drawableRight="@drawable/icon_4"        android:drawablePadding="10dp"        android:paddingLeft="10dp"        android:paddingRight="10dp"        android:textSize="16sp"        android:text="地址管理"        android:background="@color/white"        android:gravity="center_vertical"        android:layout_width="match_parent"        android:layout_height="50dp" />    <TextView        android:drawableLeft="@drawable/icon_3"        android:drawableRight="@drawable/icon_4"        android:drawablePadding="10dp"        android:paddingLeft="10dp"        android:paddingRight="10dp"        android:textSize="16sp"        android:text="检查更新"        android:background="@color/white"        android:gravity="center_vertical"        android:layout_width="match_parent"        android:layout_height="50dp" /></LinearLayout>showDividers 是分隔线的显示位置,beginning、middle、end分别代表显示在开始位置,中间,末尾。还有dividerPadding属性这里没有用到,意思很明确给divider添加padding。感兴趣可以试试。

Space控件
还是接着上面的例子,如果要给条目中间添加间距,怎么实现呢?当然也很简单,比如添加一个高10dp的View,或者使用android:layout_marginTop=”10dp”等方法。但是增加View违背了我们的初衷,并且影响性能。使用过多的margin其实会影响代码的可读性。
这时你就可以使用Space,他是一个轻量级的。

<?xml version="1.0" encoding="utf-8"?><LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    android:divider="@drawable/divider"    android:showDividers="middle|beginning|end">    <TextView        android:drawableLeft="@drawable/icon_1"        android:drawableRight="@drawable/icon_4"        android:drawablePadding="10dp"        android:paddingLeft="10dp"        android:paddingRight="10dp"        android:textSize="16sp"        android:text="我的卡券"        android:background="@color/white"        android:gravity="center_vertical"        android:layout_width="match_parent"        android:layout_height="50dp" />    <TextView        android:drawableLeft="@drawable/icon_2"        android:drawableRight="@drawable/icon_4"        android:drawablePadding="10dp"        android:paddingLeft="10dp"        android:paddingRight="10dp"        android:textSize="16sp"        android:text="地址管理"        android:background="@color/white"        android:gravity="center_vertical"        android:layout_width="match_parent"        android:layout_height="50dp" />    <Space        android:layout_width="match_parent"        android:layout_height="15dp"/>    <TextView        android:drawableLeft="@drawable/icon_3"        android:drawableRight="@drawable/icon_4"        android:drawablePadding="10dp"        android:paddingLeft="10dp"        android:paddingRight="10dp"        android:textSize="16sp"        android:text="检查更新"        android:background="@color/white"        android:gravity="center_vertical"        android:layout_width="match_parent"        android:layout_height="50dp" /></LinearLayout>

防止过度绘制
这个完全可以看鸿洋大神这篇 《Android UI性能优化实战 识别绘制中的性能问题》:http://blog.csdn.net/lmj623565791/article/details/45556391

2、绘制优化

View的onDraw方法要避免执行大量的操作。首先onDraw中不要创建新的局部对象,这是因为onDraw方法可能会被频繁调用,这样一瞬间产生大量的临时对象,导致系统频繁的Gc,降低了执行效率。另一方面,onDraw中不要做耗时任务,也不能执行成千上万的循环操作

3、内存泄露优化

  • 变量导致的内存泄漏

  • 模式导致的内存泄露

  • 动画导致的内存泄露:属性动画中使用了无限循环,在onDestroy方法中没有停止动画,虽然无法在界面上看到动画了,但是这个时候Activity的View会被动画持有,导致Activity无法释放。

  • 不要长期引用context-activity(引用一个活动应该有相同的生命周期活动本身)

  • 使用getApplicationContext而不是context或activity试试
    避免非静态内部类的一个活动如果你不控制自己的生命周期,使用静态内部类,让一个弱引用来传递,并且记住,垃圾收集器对于内存泄漏并不保险。
    第三方库如果需要传入Activity的时候,传递一个弱引用进去, 可以避免内存泄漏,如下:

Reference<HomeActivity> reference = new WeakReference(getActivity());new ShareAction(reference.get()).setDisplayList(displaylist);
  • 在Handler中容易造成内存泄漏,最好使用弱引用方式,要么,在Activity销毁的时候清空所有的handler栈。

  • 在使用WebView的时候也可能出现内存泄漏,解决办法参考:
    http://blog.csdn.net/fancy_xty/article/details/51595697

  • 在使用RxJava的时候也可能会出现内存泄漏,可以使用RxLifecycler来解决。

  • 注:内存泄露检测方法:

今天分享一个非常方便快捷的库来检测内存泄露:LeakCanary
开始使用:
1、在 build.gradle 中加入引用,不同的编译使用不同的引用:

 dependencies {   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3' }

2、在 Application 中实现:

public class ExampleApplication extends Application {  @Override public void onCreate() {    super.onCreate();    if(isApkDebugable()){        if (!LeakCanary.isInAnalyzerProcess(this)) {             // This process is dedicated to LeakCanary for heap analysis.             // You should not init your app in this process.             mRefWatcher = LeakCanary.install(this);        }     }  }}public  boolean isApkDebugable() {    try {         ApplicationInfo pkginfo = getApplicationInfo();         if (pkginfo != null ) {            return (pkginfo.flags& ApplicationInfo.FLAG_DEBUGGABLE)!=0;         }        } catch (Exception e) {        }        return false;    }    public RefWatcher mRefWatcher;    public static RefWatcher getRefWatcher(Context context) {        App application = (App) context.getApplicationContext();        return application.mRefWatcher;    }

3、如何使用:
使用 RefWatcher 监控那些本该被回收的对象。
RefWatcher refWatcher = {…};
// 监控
refWatcher.watch(schrodingerCat);
LeakCanary.install() 会返回一个预定义的 RefWatcher,同时也会启用一个 ActivityRefWatcher,用于自动监控调用 Activity.onDestroy() 之后泄露的 activity。
在Activity中如何检测:

public class BaseActivity extends AppCompatActivity {    @Override    protected void onDestroy() {        super.onDestroy();        RefWatcher refWatcher = App.getRefWatcher(this);        if(refWatcher!=null){            refWatcher.watch(this);        }    }}

在Fragment中如何检测:

public class BaseFragment extends Fragment {    @Override    public void onDestroy() {        super.onDestroy();        RefWatcher refWatcher = App.getRefWatcher(getActivity());        if(refWatcher!=null){            refWatcher.watch(this);        }    } }

工作机制:
1、RefWatcher.watch() 创建一个 KeyedWeakReference 到要被监控的对象。
2、然后在后台线程检查引用是否被清除,如果没有,调用GC。
3、如果引用还是未被清除,把 heap 内存 dump 到 APP 对应的文件系统中的一个 .hprof 文件中。
4、在另外一个进程中的 HeapAnalyzerService 有一个 HeapAnalyzer 使用HAHA 解析这个文件。
5、得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄露。
6、HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并确定是否是泄露。如果是的话,建立导致泄露的引用链。
7、引用链传递到 APP 进程中的 DisplayLeakService, 并以通知的形式展示出来。

上传 leak trace 到服务器:
你可以改变处理完成的默认行为,将 leak trace 和 heap dump 上传到你的服务器以便统计分析。
创建一个 LeakUploadService, 最简单的就是继承DisplayLeakService :

public class LeakUploadService extends DisplayLeakService {  @Override  protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {    if (!result.leakFound || result.excludedLeak) {      return;    }    myServer.uploadLeakBlocking(heapDump.heapDumpFile, leakInfo);  }}

请确认 release 版本 使用 RefWatcher.DISABLED:

public class ExampleApplication extends Application {  public static RefWatcher getRefWatcher(Context context) {    ExampleApplication application = (ExampleApplication) context.getApplicationContext();    return application.refWatcher;  }  private RefWatcher refWatcher;  @Override public void onCreate() {    super.onCreate();    refWatcher = installLeakCanary();  }  protected RefWatcher installLeakCanary() {    return RefWatcher.DISABLED;  }}

自定义 RefWatcher:

public class DebugExampleApplication extends ExampleApplication {  protected RefWatcher installLeakCanary() {    return LeakCanary.install(app, LeakUploadService.class);  }}

别忘了注册 service:

<?xml version="1.0" encoding="utf-8"?><manifest    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    >  <application android:name="com.example.DebugExampleApplication">    <service android:name="com.example.LeakUploadService" />  </application></manifest>

详细内容请参考:LeakCanary 中文使用说明

Leaks查看:
LeakCanary.install(this);将会安装一个Leak app,如下图:

当切换到release版本的时候,leakcanary-debug不会被打包.所以切换到release之后不用对leakcanary做注释或者删除等操作.
现在就可以开始使用了,重新编译你的工程,运行在模拟器或真机上.
在各个页面中测试,如果存在内存泄漏的情况,leaks会弹出通知提醒你查看.

4、响应速度优化

核心思想就是避免在主线程中做耗时操作,耗时操作都放到线程中去。响应速度主要体现在Activity的启动速度上面,如果在主线程中做太多事情导致Activity启动时黑屏,甚至出现ANR。Android规定,Activity如果5秒之内无法响应屏幕触摸或者键盘输入操作就会出现ANR,而BroadcastReceiver如果在10s之内还未执行完操作也会出现ANR。当系统出现ANR之后会在/data/anr目录下创建一个traces.txt文件。分析此文件即可看出原因。

5、ListView和Bitmap优化

此问题放在以后详细讲解

6、线程优化

采用线程池,避免 程序中存在大量的Thread。线程池可以重用内部的线程,从而避免了线程创建和销毁带来的性能开销,同事线程池还能有效地控制线程池的最大并发数,避免大量的线程因互相抢占锡系统资源从而导致阻塞现象的发生。

7、性能优化的一些建议

*避免创建过多的对象
*不要过多使用枚举,枚举占用的内存空间要比整型大
*常量请使用static final来修饰
*使用一些Android特有的数据结构,比如SparseArray和Pair等
*适当使用软引用和弱引用
*采用内存缓存和磁盘缓存
*尽量采用静态内部类,这样可以避免潜在的内部类导致的内存泄露


小女不才,内容略显粗鄙,多多包涵。以后再有类似经验会继续分享哒~

1 0
原创粉丝点击