Android性能优化

Android性能优化

简介

这篇文章主要打算从几个方面讲解一下怎么去对app进行性能优化。不打算涉及任何概念相关的东西,比如渲染机制等等,因为网上已经够多这方面的了。仅仅从自己的优化过程出发,列出一个能一步步去执行的清单和具体操作办法。

启动速度
查看每个界面的打开速度

在logcat中输入Diaplayed即可查看每个页面的打开时间。当然了,不同配置的手机打开速度肯定是不同的,这个其实没有太大的参考意义,只是知道就行。

image
设置闪屏启动图

一般是在splash页的主题设置

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">     <item name="android:windowBackground"@mipmap/launch</item>   //闪屏页图片     <item name="android:windowFullscreen"true</item>     <item name="android:windowContentOverlay"@null</item> 
</style> 

这样就会先展示一张图片出来,而不是出现白屏的效果,视觉效果优化而已,稍微提升用户体验,但是其实并没有加快启动速度。

还有一种启动图的思路是不用单独的Activity去加载广告和启动页,而是做成SplashFragment直接放到主界面里面,2,3秒之后移除。但是这种方法就没办法解决闪屏白屏的问题。这两种可以酌情选择吧。

application初始化

我们通常会在application里面做一些第三方组件的初始化操作,比如图片加载,数据库等,如果初始化的东西太多,肯定会影响到app的启动。建议将一些在开屏不必须的组件初始化放到子线程去做初始化操作,或者通过IntentService来做。

我自己就是通过IntentService来做的,因为IntentService的特点就是会开启一个工作线程(HandlerThread)来处理耗时操作,并且在完成后会自动停止。

 //初始化的IntentService,和普通的service启动方式并没有太大区别
 Intent intent = new Intent(mContext, InitializeService.class);
 intent.setAction(ACTION_INIT_WHEN_APP_CREATE);
 startService(intent);
 public class InitializeService extends IntentService {
     public InitializeService() {
         super("InitializeService");
     }

     @Override
     protected void onHandleIntent(Intent intent) {
        //初始化操作(你自己定义的操作)
         init();
         if (intent != null) {
             final String action = intent.getAction();
             if (ACTION_INIT_WHEN_APP_CREATE.equals(action)) {
                 //这里是个网络请求(你自己定义的请求)
                 performInit();
             }
         }
     }  
 }

我知道其实我就算这么一说,很多小伙伴在操作的时候还是通常会按照原来的习惯去操作。因为现在手机的配置越来越好了,app本身就已经得到了buff加成。但是如果app启动速度实在到了不可忍受的地步,可以参考我上面的做法

布局优化

布局嵌套层级过多。从前有个人说过一句话:没有什么布局是一层嵌套解决不了的,如果有那就两层。所以写着写着经常四五层嵌套就出来了,再加上文字图片的绘制,分分钟。。。

嵌套过多我们可以通过开发者选项里面的调试GPU过渡绘制打开。然后在界面上会显示出不同的颜色块。

布局层级颜色

通常我们可以从这几个角度去优化:

  • 在主题里面使用WindowBackground属性,而不是每个xml的根布局里面去添加android:background="@color/bg_set_frag"

  • 减少嵌套层级,这里我强烈推荐ConstraintLayout(ConstraintLayout教程),但是我认识好多小伙伴都没用过这个,还是习惯于常规的线性布局,相对布局,帧布局这些。

  • 检查背景颜色设置,很多父布局里面设置了背景颜色,在子布局里面还设置同样的颜色,这样就会导致绘制两遍。还有列表所在的布局已经有颜色了,列表的item里面还设置了跟他一样的颜色。如果颜色不同另当别论。

  • 还有就是善用<include>,<merge>,<ViewStub>

    include就不介绍了这个还比较常用。

    merge作用就是可以消除不必要的解析结点一般比较适用于两种情况,第一种布局顶结点是FrameLayout且不需要设置background或padding等属性,可以用merge代替 。第二种某布局作为子布局被其他布局include时,使用merge当作该布局的顶节点,这样在被引入时顶结点会自动被忽略,而将其子节点全部合并到主布局中。

    ViewStub可以理解为延迟加载的布局,需要他的时候才去加载,而不是一次性全部加载进来,减轻了CPU和GPU的负担。一般用来加载一些页面状态,比如没网络,网络请求错误,空数据等状态。你需要的时候才要他加载出来,不需要的时候他就乖乖等着又不吃你家大米。

     <!-- 定义一个ViewStub 给其父Layout指定Id为inflatedStart -->
     <ViewStub
         android:id="@+id/stub"
         android:inflatedId="@+id/inflatedStart"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout="@layout/no_data" />
    

    上面这个layout就是实际要显示的布局,看你需求去设置了。

    View mNoDataView;
    ......
    //比如加载空数据时候的状态
    public void showEmptyData() {
            if (mNoDataView == null) {
                ViewStub noDataViewStub = (ViewStub)findViewById(R.id.no_data_view);
                //这个mNoDataView就对应android:layout="@layout/no_data"
                mNoDataView = noDataViewStub.inflate();
            } else {
                mNoDataView.setVisibility(View.VISIBLE);
            }
        }
    
    内存优化

    提到内存优化就不得不提内存泄漏。为什么会产生内存泄漏呢?简单来讲就是长生命周期持有对短生命周期对象的引用,导致对无用对象的引用一直未被释放,就会导致内存泄漏。这句话我见过很多版本,角度都各不相同,这段是我自己的总结。

    常见的内存泄漏
    1.内部类导致的泄漏。
    • Thread线程导致的内存泄漏:在Activity中创建一个内部类去继承Thread,然后让该Thread执行一些后台任务,未执行完时,关闭Activity,此时会内存泄露.
    ......
    new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                      //模拟代码运行五秒钟,在这期间会一直持有对外部Activity的引用
                      //此时如果Activity被销毁了就会导致内存泄漏
                        Thread.sleep(5*1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
    ......
    

    解决办法有两种:1.保证任务在Activity销毁之前完成。2.改成静态内部类的形式

    ......
    new Thread(new MyRunnable());
    ......
    static class MyRunnable implements Runnable{
    
            @Override
            public void run() {
                try {
                    Thread.sleep(5*1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    ......
    
  • Handler导致的内存泄漏:内部类Handler会拿着外部类Activity的引用,而那个Message又拿着Handler的引用。这个Message又要在消息队列里排队等着被handler中的死循环来取消息。从而形成了一个引用链,最后导致关于外部类Activity的引用不会被释放。
 //采用静态内部类的形式创建Handler,并使用WeakReference引用Activity
 private static class MyHandler extends Handler {
        private final WeakReference<Main2Activity> mActivity;

        public MyHandler(Main2Activity activity) {
            mActivity = new WeakReference<Main2Activity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            Main2Activity activity = mActivity.get();
            if (activity != null) {
                // ...dosomething
            }
        }
    }

//在销毁Activity之后清空handler里面的消息
@Override
  protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
  }
  • 非静态内部类的静态实例:非静态的内部类创建了一个静态实例。非静态内部类会持有外部类Activity的引用,后来又创建了一个这个内部类的静态实例。这个静态实例不会在Activity被关掉时一块被回收(注:静态实例的生命周期跟应用的生命周期一样长)。

解决方案:将内部类改为静态内部类,使用弱引用或者软引用的方式,来引用外部类。

2.Context导致的内存泄漏。

一般我们写工具方法的时候会使用单例模式去创建一个对象,在构造器里面要传入一个context,这个context若是ApplicationContext就不会存在内存泄漏,但是如果是其他的context就会导致内存泄漏

public class SingleInstance {

    private Context mContext;
    private static SingleInstance instance;

    private SingleInstance(Context context) {
        //这样写会有内存泄漏
        //this.mContext = context;

        //不管传入什么context,都应该使用ApplicationContext,单例的生命周期和应用的一样长,这样就防止了内存泄漏。
        this.mContext = context.getApplicationContext();
    }

    public static SingleInstance getInstance(Context context) {
        if (instance == null) {
            synchronized (SingleInstance.class) {
                if (instance == null) {
                    instance = new SingleInstance(context);
                }
            }
        }
        return instance;
    }
}

补充关于Context作用域的问题

Context的使用
3.其他可能导致内存泄漏的补充。
  • 监听器的注销:在Android程序里面存在很多需要register与unregister的监听器,我们需要确保在合适的时候及时unregister那些监听器。自己手动add的listener,需要记得及时remove这个listener。比如说:广播接收者BroadcastReceiver、EventBus等。
  • Cursor对象和IO流是否及时关闭
  • WebView的泄漏:Android中的WebView存在很大的兼容性问题,不仅仅是Android系统版本的不同对WebView产生很大的差异,另外在不同手机厂商出货的ROM里面,WebView也存在着很大的差异。所以通常根治这个问题的办法是为展示WebView的页面开启另外一个进程,通过AIDL与主进程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。
  • 图片Bitmap的使用,采用软引用,记得recycle
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • 作者:花菜 编辑:花菜 周末的下午在星巴克冬子跟我发着牢骚:为什么每个人都泼我冷水?!我不过想不想窝囊平庸的过一生...
    爱情菜阅读 569评论 5 10
  • 苏仕爱上赵尚的时候,只是重逢后的第一眼。这个男人望向她时的嘴角弧度,还有眼睛里完完全全,带着光芒的自己。那时候网上...
    嘉嘉仕阅读 368评论 0 0
  • 2017年8月6日 你来到了我的家 我成了你的妈妈 每天照顾你吃喝拉撒 虽然生活乱成一团麻 但看到你摇尾巴 心里还...
    三门峡079唐剑红阅读 325评论 0 0