GVKun编程网logo

Top团队大牛带你玩转Android性能分析与优化(android性能优化实战解析)

15

如果您对Top团队大牛带你玩转Android性能分析与优化和android性能优化实战解析感兴趣,那么这篇文章一定是您不可错过的。我们将详细讲解Top团队大牛带你玩转Android性能分析与优化的各种

如果您对Top团队大牛带你玩转Android性能分析与优化android性能优化实战解析感兴趣,那么这篇文章一定是您不可错过的。我们将详细讲解Top团队大牛带你玩转Android性能分析与优化的各种细节,并对android性能优化实战解析进行深入的分析,此外还有关于Android ANR 分析与优化、android 多线程数据库读写分析与优化、Android 性能优化之 App 应用启动分析与优化、Android 性能全面分析与优化方案研究 — 几乎是史上最全最实用的的实用技巧。

本文目录一览:

Top团队大牛带你玩转Android性能分析与优化(android性能优化实战解析)

Top团队大牛带你玩转Android性能分析与优化(android性能优化实战解析)

  • 第1章 课程导学与学习指南【提供免费简历指导机会&内推大公司机会】 试看

    【高级面试必备课程】性能优化是高级工程师必备的技能,本课程将带你由表及里学到国内Top团队对性能问题的体系化解决方案,满满的干货让你轻松晋级高级工程师。

    共 6 节 (61分钟) 收起列表

    •  1-1 课前必读(不看会错过一个亿)
    •  1-2 课程导学 (06:45)试看
    •  1-3 【补充】面试准备及亮眼的简历 (15:55)
    •  1-4 【补充】提高面试通过率与谈薪最大化 (16:20)
    •  1-5 如何编写一份眼前一亮的简历?【选看-特邀名师:于海老师讲解】 (21:45)
    •  1-6 学习问题解决自助手册
  • 第2章 App性能概览与平台化实践 试看

    【学习中遇到任何疑问都可以在课程问答区提问,有问必答】本章节主要带领大家正确认识App性能优化,我会介绍关于性能优化的难题、性能解决方案的演进过程以及业界优秀的平台化实践。

    共 5 节 (30分钟) 展开列表

  • 第3章 App启动优化

    【学习中遇到任何疑问都可以在课程问答区提问,有问必答!】App启动速度是用户的第一印象,本章会介绍精准度量启动速度的方式,启动优化的相关工具、常规优化手段等,同时我会介绍异步初始化以及延迟初始化的最优解,以最优雅、可维护性高的的方式获得闪电般的启动速度。...

    共 14 节 (183分钟) 展开列表

  • 第4章 App内存优化

    本章从原理开始讲起,首先介绍Java及Android的内存管理机制,接下来手把手带领大家进行内存抖动、泄露的解决实战,同时通过ArtHook的方式优雅解检测出App所有不合理的图片。

    共 10 节 (112分钟) 展开列表

  • 第5章 App布局优化

    本章主要介绍了Android的绘制原理并结合源码带大家分析Android布局加载过程,并且介绍一种优雅获取界面布局耗时的方式,异步Inflate以及Xml布局转Java的方式是从不同的思路缩短布局的耗时。

    共 8 节 (78分钟) 展开列表

  • 第6章 App卡顿优化

    卡顿是对用户体验最明显的性能问题,本章节会带领大家学习卡顿优化的相关工具,自动化卡顿监测方案以及优化方案、ANR的监测及补充、卡顿单点问题的优雅解决等,同时也会教给大家如何实现界面秒开,最后会详细介绍一般App都不会注意到的技术点:耗时盲区的监控。...

    共 9 节 (76分钟) 展开列表

  • 第7章 App线程优化

    在开发中线程的使用必不可少,本章节带领大家学习线程调度的原理、常见的异步方式以及异步的优化,同时也会介绍大型项目中如何锁定线程创建位置、如何高效的收敛线程。

    共 7 节 (40分钟) 展开列表

  • 第8章 App网络优化

    本章节带领大家一起学习网络优化相关工具、优化纬度、监控及优化具体方案等,从而获取线上用户真实的流量消耗、网络使用情况,同时会介绍关于网络的体系化建设方案。

    共 7 节 (96分钟) 展开列表

  • 第9章 App电量优化

    电量是开发者经常容易忽略的问题,本章会讲解电量优化的测试手段、优化工具,会重点介绍Battery historian实战以及难题解决。

    共 5 节 (44分钟) 展开列表

  • 第10章 App瘦身优化

    安装包大小对用户的安装转换率至关重要,本章节带领大家学习Apk分析方案,并从代码、资源、So等方面进行优化,同时也会介绍长期优化效果保持的技术。

    共 5 节 (37分钟) 展开列表

  • 第11章 App稳定性优化

    质量是App的立足之本,本章节带领大家一起学习提升App稳定性的方案以及移动端容灾实践,通过本课程的学习会极大提升App的稳定性。

    共 6 节 (52分钟) 展开列表

  • 第12章 App专项技术优化

    本章节带领大家一起学习大型App优化中一定会有的专项技术优化,涉及列表卡顿页优化、Android存储优化、WebView白屏问题等。

    共 3 节 (25分钟) 展开列表

  • 第13章 课程总结

    本章节对一系列体系化解决方案进行梳理,涉及体系化的性能建设全套方案、单点问题追查方案等。

    共 2 节 (25分钟) 展开列表

http://www.iyx668.com/thread-3554-1-1.html

Android ANR 分析与优化

Android ANR 分析与优化

1.概览

    ANR 全称 Applicatipon No Response;Android 设计 ANR 的用意,是系统通过与之交互的组件(Activity,Service,Receiver,Provider)以及用户交互(InputEvent)进行超时监控,以判断应用进程(主线程)是否存在卡死或响应过慢的问题,通俗来说就是很多系统中看门狗(watchdog)的设计思想。

2.分析

常规的分析思路:

  • BackTrace 日志
  • AnrInfo
  • Kernel 日志
  • Logcat 日志
  • Meminfo 日志

3.优化

上述手段只能分析一些比较简单的异常,如遇到NativePollOnce等异常时就无法分析了。今日头条有分享一系列文章,个人觉得阐述的非常深入浅出,受益匪浅。详情请自行阅读:
今日头条 ANR 优化实践系列 - 设计原理及影响因素

今日头条 ANR 优化实践系列 - Barrier 导致主线程假死

今日头条 ANR 优化实践系列分享 - 实例剖析集锦

今日头条 ANR 优化实践系列 - 监控工具与分析思路

android 多线程数据库读写分析与优化

android 多线程数据库读写分析与优化

最新需要给软件做数据库读写方面的优化,之前无论读写,都是用一个 SQLiteOpenHelper.getWriteableDataBase() 来操作数据库,现在需要多线程并发读写,项目用的是2.2的SDK。


android 的数据库系统用的是sqlite ,sqlite的每一个数据库其实都是一个.db文件,它的同步锁也就精确到数据库级了,不能跟别的数据库有表锁,行锁。

所以对写实在有要求的,可以使用多个数据库文件。

哎,这数据库在多线程并发读写方面本身就挺操蛋的。


下面分析一下不同情况下,在同一个数据库文件上操作,sqlite的表现。

测试程序在2.2虚拟手机,4.2.1虚拟手机,4.2.1真手机上跑。

1,多线程写,使用一个SQLiteOpenHelper。也就保证了多线程使用一个SQLiteDatabase。

先看看相关的源码

[java] view plaincopy

  1. //SQLiteDatabase.java   

  2.   

  3. public long insertWithOnConflict(String table, String nullColumnHack,  

  4.             ContentValues initialValues, int conflictAlgorithm) {  

  5.         if (!isOpen()) {  

  6.             throw new IllegalStateException("database not open");  

  7.         }  

  8.   

  9.         .... 省略  

  10.   

  11.         lock();  

  12.         SQLiteStatement statement = null;  

  13.         try {  

  14.             statement = compileStatement(sql.toString());  

  15.   

  16.             // Bind the values  

  17.             if (entrySet != null) {  

  18.                 int size = entrySet.size();  

  19.                 Iterator<Map.Entry<String, Object>> entriesIter = entrySet.iterator();  

  20.                 for (int i = 0; i < size; i++) {  

  21.                     Map.Entry<String, Object> entry = entriesIter.next();  

  22.                     DatabaseUtils.bindObjectToProgram(statement, i + 1, entry.getValue());  

  23.                 }  

  24.             }  

  25.   

  26.             // Run the program and then cleanup  

  27.             statement.execute();  

  28.   

  29.             long insertedRowId = lastInsertRow();  

  30.             if (insertedRowId == -1) {  

  31.                 Log.e(TAG, "Error inserting " + initialValues + " using " + sql);  

  32.             } else {  

  33.                 if (Config.LOGD && Log.isLoggable(TAG, Log.VERBOSE)) {  

  34.                     Log.v(TAG, "Inserting row " + insertedRowId + " from "  

  35.                             + initialValues + " using " + sql);  

  36.                 }  

  37.             }  

  38.             return insertedRowId;  

  39.         } catch (SQLiteDatabaseCorruptException e) {  

  40.             onCorruption();  

  41.             throw e;  

  42.         } finally {  

  43.             if (statement != null) {  

  44.                 statement.close();  

  45.             }  

  46.             unlock();  

  47.         }  

  48.     }  



[java] view plaincopy

  1. //SQLiteDatabase.java   

  2.   

  3.   

  4.  private final ReentrantLock mLock = new ReentrantLock(true);  

  5.   

  6. /* package */ void lock() {  

  7.   

  8.        if (!mLockingEnabled) return;   

  9.   

  10.              mLock.lock();   

  11.   

  12.              if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {   

  13.   

  14.                  if (mLock.getHoldCount() == 1) {   

  15.   

  16.                        // Use elapsed real-time since the CPU may sleep when waiting for IO  

  17.   

  18.                        mLockAcquiredWallTime = SystemClock.elapsedRealtime();   

  19.   

  20.                        mLockAcquiredThreadTime = Debug.threadCpuTimeNanos();   

  21.   

  22.                  }   

  23.   

  24.       }   

  25.   

  26. }  


通过源码可以知道,在执行插入时,会请求SQLiteDatabase对象的成员对象 mlock 的锁,来保证插入不会并发执行。

经测试不会引发异常。


但是我们可以通过使用多个SQLiteDatabase对象同时插入,来绕过这个锁。

2,多线程写,使用多个SQLiteOpenHelper,插入时可能引发异常,导致插入错误。


E/Database(1471): android.database.sqlite.SQLiteException: error code 5: database is locked08-01

 E/Database(1471):     at android.database.sqlite.SQLiteStatement.native_execute(Native Method)

E/Database(1471):     at android.database.sqlite.SQLiteStatement.execute(SQLiteStatement.java:55)

E/Database(1471):     at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1549)

多线程写,每个线程使用一个SQLiteOpenHelper,也就使得每个线程使用一个SQLiteDatabase对象。多个线程同时执行insert, 最后调用到本地方法  SQLiteStatement.native_execute

抛出异常,可见android 框架,多线程写数据库的本地方法里没有同步锁保护,并发写会抛出异常。

所以,多线程写必须使用同一个SQLiteOpenHelper对象。


3,多线程读

看SQLiteDatabase的源码可以知道,insert  , update ,  execSQL   都会 调用lock(), 乍一看唯有query 没有调用lock()。可是。。。

仔细看,发现


最后,查询结果是一个SQLiteCursor对象。

SQLiteCursor保存了查询条件,但是并没有立即执行查询,而是使用了lazy的策略,在需要时加载部分数据。

在加载数据时,调用了SQLiteQuery的fillWindow方法,而该方法依然会调用SQLiteDatabase.lock()

[java] view plaincopy

  1. /** 

  2.    * Reads rows into a buffer. This method acquires the database lock. 

  3.    * 

  4.    * @param window The window to fill into 

  5.    * @return number of total rows in the query 

  6.    */  

  7.   /* package */ int fillWindow(CursorWindow window,  

  8.           int maxRead, int lastPos) {  

  9.       long timeStart = SystemClock.uptimeMillis();  

  10.       mDatabase.lock();  

  11.       mDatabase.logTimeStat(mSql, timeStart, SQLiteDatabase.GET_LOCK_LOG_PREFIX);  

  12.       try {  

  13.           acquireReference();  

  14.           try {  

  15.               window.acquireReference();  

  16.               // if the start pos is not equal to 0, then most likely window is  

  17.               // too small for the data set, loading by another thread  

  18.               // is not safe in this situation. the native code will ignore maxRead  

  19.               int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex,  

  20.                       maxRead, lastPos);  

  21.   

  22.               // Logging  

  23.               if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {  

  24.                   Log.d(TAG, "fillWindow(): " + mSql);  

  25.               }  

  26.               mDatabase.logTimeStat(mSql, timeStart);  

  27.               return numRows;  

  28.           } catch (IllegalStateException e){  

  29.               // simply ignore it  

  30.               return 0;  

  31.           } catch (SQLiteDatabaseCorruptException e) {  

  32.               mDatabase.onCorruption();  

  33.               throw e;  

  34.           } finally {  

  35.               window.releaseReference();  

  36.           }  

  37.       } finally {  

  38.           releaseReference();  

  39.           mDatabase.unlock();  

  40.       }  

  41.   }  


所以想要多线程读,读之间没有同步锁,也得每个线程使用各自的SQLiteOpenHelper对象,经测试,没有问题。


4,多线程读写

我们最终想要达到的目的,是多线程并发读写

多线程写之前已经知道结果了,同一时间只能有一个写。

多线程读可以并发


所以,使用下面的策略:

一个线程写,多个线程同时读,每个线程都用各自SQLiteOpenHelper。

这样,在java层,所有线程之间都不会锁住,也就是说,写与读之间不会锁,读与读之间也不会锁。

发现有插入异常。

E/SQLiteDatabase(18263): Error inserting descreption=InsertThread#01375493606407
E/SQLiteDatabase(18263): android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
E/SQLiteDatabase(18263):     at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)

插入异常,说明在有线程读的时候写数据库,会抛出异常。


分析源码可以知道, SQLiteOpenHelper.getReadableDatabase() 不见得获得的就是只读SQLiteDatabase 。

[java] view plaincopy

  1. //  SQLiteOpenHelper.java  

  2.   

  3.   public synchronized SQLiteDatabase getReadableDatabase() {  

  4.         if (mDatabase != null && mDatabase.isOpen()) {  

  5.            <span style="color:#FF0000;"return mDatabase;</span>  // The database is already open for business  

  6.         }  

  7.   

  8.         if (mIsInitializing) {  

  9.             throw new IllegalStateException("getReadableDatabase called recursively");  

  10.         }  

  11.   

  12.         try {  

  13.             return getWritableDatabase();  

  14.         } catch (SQLiteException e) {  

  15.             if (mName == nullthrow e;  // Can''t open a temp database read-only!  

  16.             Log.e(TAG, "Couldn''t open " + mName + " for writing (will try read-only):", e);  

  17.         }  

  18.   

  19.         SQLiteDatabase db = null;  

  20.         try {  

  21.             mIsInitializing = true;  

  22.             String path = mContext.getDatabasePath(mName).getPath();  

  23.             db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY);  

  24.             if (db.getVersion() != mNewVersion) {  

  25.                 throw new SQLiteException("Can''t upgrade read-only database from version " +  

  26.                         db.getVersion() + " to " + mNewVersion + ": " + path);  

  27.             }  

  28.   

  29.             onOpen(db);  

  30.             Log.w(TAG, "Opened " + mName + " in read-only mode");  

  31.             mDatabase = db;  

  32.             return mDatabase;  

  33.         } finally {  

  34.             mIsInitializing = false;  

  35.             if (db != null && db != mDatabase) db.close();  

  36.         }  

  37.     }  

因为它先看有没有已经创建的SQLiteDatabase,没有的话先尝试创建读写 SQLiteDatabase ,失败后才尝试创建只读SQLiteDatabase 。

所以写了个新方法,来获得只读SQLiteDatabase 


[java] view plaincopy

  1. //DbHelper.java   

  2. //DbHelper extends SQLiteOpenHelper  

  3. public SQLiteDatabase getOnlyReadDatabase() {  

  4.         try{  

  5.             getWritableDatabase(); //保证数据库版本最新  

  6.         }catch(SQLiteException e){  

  7.             Log.e(TAG, "Couldn''t open " + mName + " for writing (will try read-only):",e);  

  8.         }  

  9.           

  10.         SQLiteDatabase db = null;  

  11.         try {  

  12.             String path = mContext.getDatabasePath(mName).getPath();  

  13.             db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY);  

  14.             if (db.getVersion() != mNewVersion) {  

  15.                 throw new SQLiteException("Can''t upgrade read-only database from version " +  

  16.                         db.getVersion() + " to " + mNewVersion + ": " + path);  

  17.             }  

  18.   

  19.             onOpen(db);  

  20.             readOnlyDbs.add(db);  

  21.             return db;  

  22.         } finally {  

  23.         }  

  24. }  


使用策略:一个线程写,多个线程同时读,只用一个SQLiteOpenHelper,读线程使用自己写的getOnlyReadDatabase()方法获得只读。
但是经过测试,还是会抛出异常,2.2上只有插入异常,4.1.2上甚至还有读异常。


4.1.2上测试,读异常。
 E/SQLiteLog(18263): (5) database is locked
W/dalvikvm(18263): threadid=21: thread exiting with uncaught exception (group=0x41e2c300)
 E/AndroidRuntime(18263): FATAL EXCEPTION: onlyReadThread#8
E/AndroidRuntime(18263): android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5): , while compiling: SELECT * FROM test_t


看来此路不同啊。


其实SQLiteDataBase 在API 11 多了一个 属性 ENABLE_WRITE_AHEAD_LOGGING

可以打,enableWriteAheadLogging(),可以关闭disableWriteAheadLogging(),默认是关闭的。


这个属性是什么意思呢?

参考api文档,这个属性关闭时,不允许读,写同时进行,通过 锁 来保证。

当打开时,它允许一个写线程与多个读线程同时在一个SQLiteDatabase上起作用。实现原理是写操作其实是在一个单独的文件,不是原数据库文件。所以写在执行时,不会影响读操作,读操作读的是原数据文件,是写操作开始之前的内容。

在写操作执行成功后,会把修改合并会原数据库文件。此时读操作才能读到修改后的内容。但是这样将花费更多的内存。
有了它,多线程读写问题就解决了,可惜只能在API 11 以上使用。

所以只能判断sdk版本,如果3.0以上,就打开这个属性

[java] view plaincopy

  1. public DbHelper(Context context , boolean enableWAL) {  

  2.         this(context, DEFAULT_DB_NAME, null, DEFAULT_VERSION);  

  3.         if( enableWAL && Build.VERSION.SDK_INT >= 11){  

  4.             getWritableDatabase().enableWriteAheadLogging();  

  5.         }  

  6. }  


关于SQLiteDatabase的这个属性,参考api文档,也可以看看SQLiteSession.java里对多线程数据库读写的描述。

SQLiteSession.java


结论

想要多线程并发读写,3.0以下就不要想了,3.0以上,直接设置enableWriteAheadLogging()就ok。

如果还是达不到要求,就使用多个db文件吧。


另:

单位有一个三星 note2手机,上面所有的例子跑起来都啥问题也没有。。。。很好很强大。


最后,附上我的测试程序。

https://github.com/zebulon988/SqliteTest.git


Android 性能优化之 App 应用启动分析与优化

Android 性能优化之 App 应用启动分析与优化

前言:

     昨晚新版本终于发布了,但是还是记得有测试反馈 app 启动好长时间也没进入 app 主页,所以今天准备加个班总结一下 App 启动那些事!

app 的启动方式:

 1.)冷启动

     当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化 Application 类,再创建和初始化 MainActivity 类(包括一系列的测量、布局、绘制),最后显示在界面上。

 2.)热启动

     当启动应用时,后台已有该应用的进程(例:按 back 键、home 键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动。热启动因为会从已有的进程中来启动,所以热启动就不会走 Application 这步了,而是直接走 MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个 MainActivity 就行了,而不必创建和初始化 Application,因为一个应用从新进程的创建到进程的销毁,Application 只会初始化一次。

app 的启动流程:

   通过上面的两种启动方式可以看出 app 启动流程为:

    Application 的构造器方法 ——>attachBaseContext ()——>onCreate ()——>Activity 的构造方法 ——>onCreate ()——> 配置主题中背景等属性 ——>onStart ()——>onResume ()——> 测量布局绘制显示在界面上

app 的启动优化:

    基于上面的启动流程我们尽量做到如下几点

  1. Application 的创建过程中尽量少的进行耗时操作

  2. 如果用到 SharePreference, 尽量在异步线程中操作

  3. 减少布局的层次,并且生命周期回调的方法中尽量减少耗时的操作

app 启动遇见黑屏或者白屏问题

  1.)产生原因

     其实显示黑屏或者白屏实属正常,这是因为还没加载到布局文件,就已经显示了 window 窗口背景,黑屏白屏就是 window 窗口背景。

    示例:

  

 2.)解决办法

     通过设置设置 Style

(1)设置背景图 Theme

    通过设置一张背景图。 当程序启动时,首先显示这张背景图,避免出现黑屏

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="android:screenOrientation">portrait</item>
        <item name="android:windowBackground">>@mipmap/splash</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowNoTitle">true</item>
</style>

(2)设置透明 Theme

   通过把样式设置为透明,程序启动后不会黑屏而是整个透明了,等到界面初始化完才一次性显示出来

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:screenOrientation">portrait</item>
</style>

两者对比:

  • Theme1 程序启动快,界面先显示背景图,然后再刷新其他界面控件。给人刷新不同步感觉。

  • Theme2 给人程序启动慢感觉,界面一次性刷出来,刷新同步。

(3)修改 AndroidManifest.xml

 1 <application
 2         android:name=".App"
 3         android:allowBackup="true"
 4         android:icon="@mipmap/ic_launcher"
 5         android:label="@string/app_name"
 6         android:supportsRtl="true">
 7         <activity android:name=".MainActivity"
 8          android:theme="@style/AppTheme">
 9             <intent-filter>
10                 <action android:name="android.intent.action.MAIN" />
11 
12                 <category android:name="android.intent.category.LAUNCHER" />
13             </intent-filter>
14         </activity>
15 
16     //......
17 
18 </application>

解决后示例:

3.)常见的 Theme 主题
 1 android:theme="@android:style/Theme.Dialog" //Activity显示为对话框模式
 2 
 3 android:theme="@android:style/Theme.NoTitleBar" //不显示应用程序标题栏
 4 
 5 android:theme="@android:style/Theme.NoTitleBar.Fullscreen" //不显示应用程序标题栏,并全屏
 6 
 7 android:theme="Theme.Light " //背景为白色
 8 
 9 android:theme="Theme.Light.NoTitleBar" //白色背景并无标题栏
10 
11 android:theme="Theme.Light.NoTitleBar.Fullscreen" //白色背景,无标题栏,全屏
12 
13 android:theme="Theme.Black" //背景黑色
14 
15 android:theme="Theme.Black.NoTitleBar" //黑色背景并无标题栏
16 
17 android:theme="Theme.Black.NoTitleBar.Fullscreen" //黑色背景,无标题栏,全屏
18 
19 android:theme="Theme.Wallpaper" //用系统桌面为应用程序背景
20 
21 android:theme="Theme.Wallpaper.NoTitleBar" //用系统桌面为应用程序背景,且无标题栏
22 
23 android:theme="Theme.Wallpaper.NoTitleBar.Fullscreen" //用系统桌面为应用程序背景,无标题栏,全屏
24 
25 android:theme="Theme.Translucent" //透明背景
26 
27 android:theme="Theme.Translucent.NoTitleBar" //透明背景并无标题
28 
29 android:theme="Theme.Translucent.NoTitleBar.Fullscreen" //透明背景并无标题,全屏
30 
31 android:theme="Theme.Panel " //面板风格显示
32 
33 android:theme="Theme.Light.Panel" //平板风格显示

 

Android 性能全面分析与优化方案研究 — 几乎是史上最全最实用的

Android 性能全面分析与优化方案研究 — 几乎是史上最全最实用的

写在前面,如果面对复杂的动画效果你一筹莫展,不烦看看这篇文章:LottieAndroid 使用详解及源码解析 — 轻而易举实现各种复杂动画

该文章是结合我司产品手机迅雷做的一个全面的性能分析及优化方案。本文篇幅较长,几乎涵盖了所有的性能方面问题,以及给出了如何查找和解决问题的方案,几乎是史上最全最实用的 Android 性能分析和优化文章。
另外,由于简书对 MarkDown 的支持问题导致图片格式看着有点乱,还请多多谅解,细心阅读。

结合以下四个部分讲解:

  • 性能问题分类

  • 性能优化原则和方法

  • 借助性能优化工具分析解决问题

  • 性能优化指标

性能问题分类

1、渲染问题:过度绘制、布局冗杂

2、内存问题:内存浪费(内存管理)、内存泄漏

3、功耗问题:耗电

性能优化原则和方法

1、性能优化原则

  • 坚持性能测试(开发和测试同学的测试方法略有不同):不要凭感觉去检测性能问题、评估性能优化的效果,应该保持足够多的测量,用数据说话(主要针对测试同学)。使用各种性能工具测试及快速定位问题(主要针对开发同学)。

  • 使用低配置的设备:同样的程序,在低端配置的设备中,相同的问题会暴露得更为明显。

  • 权衡利弊:在能够保证产品稳定、按时完成需求的前提下去做优化。

2、优化方法

  • 了解问题(分为可感知和不可感知的性能问题):对于性能问题来讲,这个步骤只适用于某些明显的性能问题,很多无法感知的性能问题需要通过工具定位。例如:内存泄漏、层级冗杂、过度绘制等无法感知。滑动卡顿是可以感知到的。

  • 定位问题:通过工具检测、分析数据,定位在什么地方存在性能问题。

  • 分析问题:找到问题后,分析针对这个问题该如何解决,确定解决方案。

  • 解决问题:根据分析结果寻找解决方案。

  • 验证问题:保证优化有效,没有产生新的问题,以及产品稳定性。

性能优化工具

以下优化工具在下面文章中具体介绍使用方法。

1、手机开发者选项:调试 GPU 过度绘制、启用严格模式、显示 CPU 使用情况、GPU 呈现模式分析、显示所有 "应用程序无响应"。(小米手机开发开发者选项中名字)

2、IDE 中:Android Studio,比如静态代码检测工具、Memory Monitor、CPU Monitor、NetWork Monitor、GPU Monitor、Layout Inspector、Analyze APK 等。

3、SDK 中:sdk\tools,比如 DDMS、HierarchyViewer、TraceView 等。

4、第三方工具:MAT、LeakCanary、GT 等。

性能优化指标

1、渲染

  • 滑动流畅度:FPS,即 Frame per Second,一秒内的刷新帧数,越接近 60 帧越好;

  • 过度绘制:单页面的 3X(粉红色区域) Overdraw 小于 25%

  • 启动时间:这里主要说的是 Activity 界面启动时间,一般低于 300ms,需要用高频摄像机计算时间。

2、内存

  • 内存大小:峰值越低越好,需要优化前后做对比

  • 内存泄漏:需要用工具检查对比优化前后

3、功耗

  • 单位时间内的掉电量,掉电量越少越好,业内没有固定标准。华为有专门测试功耗的机器,以及自己的标准。

一、渲染问题

先来看看造成应用 UI 卡顿的常见原因都有哪些?

1、人为在 UI 线程中做轻微耗时操作,导致 UI 线程卡顿;

2、布局 Layout 过于复杂,无法在 16ms 内完成渲染;

3、同一时间动画执行的次数过多,导致 CPU 或 GPU 负载过重;

4、View 过度绘制,导致某些像素在同一帧时间内被绘制多次,从而使 CPU 或 GPU 负载过重;

5、View 频繁的触发 measure、layout,导致 measure、layout 累计耗时过多及整个 View 频繁的重新渲染;

6、内存频繁触发 GC 过多(同一帧中频繁创建内存),导致暂时阻塞渲染操作;

7、冗余资源及逻辑等导致加载和执行缓慢;

8、臭名昭著的 ANR;

大多数用户感知到的卡顿等性能问题的最主要根源都是因为渲染性能。(Google 官方说的)

Android 系统每隔 16ms 发出 VSYNC 信号(vertical synchronization -- 场扫描同步,场同步,垂直同步),触发对 UI 进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的 60fps,为了能够实现 60fps,这意味着程序的大多数操作都必须在 16ms(1000/60=16.67ms)内完成。

如果你的某个操作花费时间是 24ms,系统在得到 VSYNC 信号的时候就无法进行正常渲染,这样就发生了丢帧现象。那么用户在 32ms 内看到的会是同一帧画面。

 

 

 

1、过度绘制

Overdraw(过度绘制)描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次的 UI 结构里面,如果不可见的 UI 也在做绘制的操作,这就会导致某些像素区域被绘制了多次。这就浪费大量的 CPU 以及 GPU 资源,找出界面滑动不流畅、界面启动速度慢、手机发热。

  • 如何查看过度绘制?

    设置 — 开发中选项 — 调试 GPU 过度绘制

  • 来看看手雷里的过度绘制和优化效果(目前手雷还存在很多待优化的页面)

     

     

     

  • 上图中的各种颜色都代表什么意思?

     

     

     

      每个颜色的说明如下:
    
      原色:没有过度绘制
      紫色:1 次过度绘制
      绿色:2 次过度绘制
      粉色:3 次过度绘制
      红色:4 次及以上过度绘制
    
  • 造成过度优化的关键是什么?多余的背景(Background)

  • 接下来举例说明:

    • 1、MainTabActivity

      在 MainTabActivity 的 Theme 中修改背景

       

       

       

      去除布局(main_activity_linerlayout.xml)中的 background

       

       

       

      如果不给当前 Activity 设置主题,默认主题是什么,默认主题背景是什么?

       

       

       

       

       

       

       



       

       

       

       

       

       

       

       

        可以在默认主题中添加通用主题背景
        <item name="android:windowBackground">@drawable/common_layout_content_bkg</item>
        去除背景
        <item name="android:windowBackground">null</item>
      
    • 2、除了布局中多余背景,还有可能在代码里添加了多余的背景。

       

       

       

      查看分享弹窗的布局代码发现只有一个 background,但为什么会过度绘制呢?

       

       

       

       

       

       

      代码修改(SharePlatformsDialog.java)

       

       

       

    • 3、弹窗底部布局不会导致弹窗本身过度绘制

       

       

       

      弹窗的绘制是属于剪切式绘制不是覆盖绘制,蒙层是透明度亮度的调节不是绘制一层灰色。
      如果我们不想用系统 dialog 而是自定义一个弹窗 view,就需要考虑过度绘制问题。

    • 4、自定义 view 时,通过 Canvas 的 clipRect 方法控制每个视图每次刷新的区域,这样可以避免刷新不必要的区域,从而规避过渡绘制的问题。还可以使用 canvas.quickreject () 来判断是否和某个矩形相交,从而跳过那些非矩形区域内的绘制操作。参考:http://jaeger.itscoder.com/android/2016/09/29/android-performance-overdraw.html

  • 优化方法和步骤关键总结

      总结一下,优化步骤如下:
    
      1、移除或修改Window默认的Background
      2、移除XML布局文件中非必需的Background
      3、按需显示占位背景图片
      4、控制绘制区域
    

2、布局优化

布局太过复杂,层级嵌套太深导致绘制操作耗时,且增加内存的消耗。
我们的目标就是,层级扁平化

  • 布局优化的建议:

    • 第一个建议:可以使用相对布局减少层级的就使用相对布局,否则使用线性布局。Android 中 RelativeLayout 和 LinearLayout 性能分析,参考:http://www.jianshu.com/p/8a7d059da746#

    • 第二个建议:用 merge 标签来合并布局,这可以减少布局层次

    • 第三个建议:用 include 标签来重用布局,抽取通用的布局可以让布局的逻辑更清晰明了,但要避免 include 乱用

    • 第四个建议:避免创建不必要的布局层级。(最容易发生的!

    • 第五个建议:使用惰性控件 ViewStub 实现布局动态加载

  • 如何借助工具查看代码布局?

    Android SDK 工具箱中有一个叫做 Hierarchy Viewer 的工具,能够在程序运行时分析 Layout。
    可以用这个工具找到 Layout 的性能瓶颈
    该工具的使用条件:模拟器或者 Root 版真机。
    如何开启该功能:AndroidStudio 中,Tools — Android — Android Devices Monitor
    该工具的缺点:使用起来麻烦。

     

     

     

  • 看看项目中遇到的问题(MainTabAvtivity)。

    • merge 标签的使用

       

       

       

      未使用 merge,例如:XLTabLayout.java

       



       

       

      使用 merge,例如:账号信息页的条目 UserAccountItem

       

       

       

    • include 标签的使用导致的问题

       

       

       

       

       

       

    • 避免创建不必要的层级(MainTabActivity)

       

       

       

    • ViewStub 的使用

      这个标签最大的优点是当你需要时才会加载,使用他并不会影响 UI 初始化时的性能。
      通常情况下我们需要在某个条件下使用某个布局的时候会通过 gone 或者 invisible 来隐藏,其实这样的方式虽然隐藏了布局,但是当显示该界面的时候还是将该布局实例化的。使用 ViewStub 可以避免内存的浪费,加快渲染速度。
      其实 ViewStub 就是一个宽高都为 0 的一个 View,它默认是不可见的,只有通过调用 setVisibility 函数或者 Inflate 函数才会将其要装载的目标布局给加载出来,从而达到延迟加载的效果,这个要被加载的布局通过 android:layout 属性来设置。

       

       

       

      当准备 inflate ViewStub 时,调用 inflate () 方法即可。还可以设定 ViewStub 的 Visibility 为 VISIBLE 或 INVISIBLE,也会触发 inflate。注意的是,使用 inflate () 方法能返回布局文件的根 View。

        ((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
        // or
        View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();
      

      setVisibility 的时候会触发了 inflate

       

       

       

      注意:使用 ViewStub 加载的布局中不能使用 merge 标签。

  • 看看 Space 标签(不常用)

    space 标签可以只在布局文件中占位,不绘制,Space 标签有对应的 java 类 Space.java,通过阅读源码可以发现,它继承至 View.java,并且复写了 draw 方法,该方法为空,既没有调用父类的 draw 方法,也没有执行自己的代码,表示该类是没有绘制操作的,但 onMeasure 方法正常调用,说明是有宽高的。
    主要功能用来设置间距,这个标签不常用,常使用 margin 或 padding。

3、介绍一下查看渲染性能的工具

  • GPU 呈现模式分析(大致定位问题)

    开发者选项 — GPU 呈现模式分析 — 选择 “在屏幕上显示为条形图”

     

     

     

    Android 开发者选项 ——Gpu 呈现模式分析,参考:http://www.voidcn.com/blog/gjy211/article/p-6210447.html
    自动播放的视频停止的时候会有两条很长的柱线,下个视频播放的时候还会有一条。这里有一个明显的卡顿。
    播放器操作(DefaultPlayerView.java - doPlay,player_auto_control_layout.xml):

     

     

     

      上图的E total time = 68 是播放器停止播放的时候耗费的时间。
    
      total time = 29 是播放器开始播放的时候耗费的时间。
      其中,大部分时间耗费在了 5total time = 18 上面,这个是inflate播放器界面的时候耗费的时间。
    

     

     

     

    是不是所有的 inflate 都很耗费时间,看一下账号信息页:

     

     

     

     

     

     

  • GPU Monitor

  • 启用严格模式(不止渲染性能)

    应用在主线程上执行长时间操作时会闪烁屏幕。
    通过代码进行严格模式(StrictMode)调试,参考:http://www.tuicool.com/articles/ueeM7b6

二、内存问题

1、内存浪费

程序内存的管理是否合理高效对应用的性能有着很大的影响。
推荐阅读 Android 性能优化典范 - 第 3 季,参考:http://hukai.me/android-performance-patterns-season-3/

  • ArrayMap(我们项目中没有用到,Android 源码中很多使用)

    Android 为移动操作系统特意编写了一些更加高效的容器,例如 ArrayMap、SparseArray。
    为了解决 HashMap 更占内存的弊端,Android 提供了内存效率更高的 ArrayMap。

    • 先来看看 HashMap 的原理

      HashMap 的整体结构如下:

       

       

       

      存储位置的确定流程:

      !()[http://img.hb.aicdn.com/16b2993c41a413b24470e1b9b74227674cd16fdb9a09-UTrZIe]

       

       

       

    • 再看来看看 ArrayMap 是如何优化内存的

      它内部使用两个数组进行工作,其中一个数组记录 key hash 过后的顺序列表,另外一个数组按 key 的顺序记录 Key-Value 值,如下图所示:

       

       

       

      当你想获取某个 value 的时候,ArrayMap 会计算输入 key 转换过后的 hash 值,然后对 hash 数组使用二分查找法寻找到对应的 index,然后我们可以通过这个 index 在另外一个数组中直接访问到需要的键值对。

       

       

       

      既然 ArrayMap 中的内存占用是连续不间断的,那么它是如何处理插入与删除操作的呢?它跟 HashMap 有什么区别?二者之间的删除插入效率有什么差异?请看下图所示,演示了 Array 的特性:

       

       

       

       

       

       

      HashMap 与 ArrayMap 之间的内存占用效率对比图如下:

       

       

       

      与 HashMap 相比,ArrayMap 在循环遍历的时候更加高效。

       

       

       

      什么时候使用 ArrayMap 呢?

        1、对象个数的数量级最好是千以内,没有频繁的插入删除操作
        2、数据组织形式包含Map结构
      
  • Autoboxing(避免自动装箱)

    Autoboxing 的行为还经常发生在类似 HashMap 这样的容器里面,对 HashMap 的增删改查操作都会发生了大量的 autoboxing 的行为。当 key 是 int 类型的时候,HashMap 和 ArrayMap 都有 Autoboxing 行为。

  • SparseArray(项目中用到较多 -- 后面再说如何利用工具查找该用 SparseArray 而没有用到的地方)

    为了避免 Autoboxing 行为 Android 提供了 SparseArray,此容器使用于 key 为 int 类型
    SparseBooleanMap,SparseIntMap,SparseLongMap 等容器,是 key 为 int,value 类型相应为 boolean、int、long 等。

  • Enum(枚举,项目中较多使用,应尽量避免)

    Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
    Android 官方强烈建议不要在 Android 程序里面使用到 enum。
    关于 enum 的效率,请看下面的讨论。假设我们有这样一份代码,编译之后的 dex 大小是 2556 bytes,在此基础之上,添加一些如下代码,这些代码使用普通 static 常量相关作为判断值:

     

     

     

    增加上面那段代码之后,编译成 dex 的大小是 2680 bytes,相比起之前的 2556 bytes 只增加 124 bytes。假如换做使用 enum,情况如下:

     

     

     

    使用 enum 之后的 dex 大小是 4188 bytes,相比起 2556 增加了 1632 bytes,增长量是使用 static int 的 13 倍。不仅仅如此,使用 enum,运行时还会产生额外的内存占用,如下图所示:

     

     

     

  • 推荐一些文章:

    • HashMap,ArrayMap,SparseArray 源码分析及性能对比,参考:http://www.jianshu.com/p/7b9a1b386265#

    • Android 性能优化 -- 小心自动装箱:http://blog.csdn.net/lgz_ei/article/details/69208784

    • Android 性能优化篇:Android 中如何避免创建不必要的对象:http://blog.csdn.net/jia635/article/details/52525243

    • HashMap、ArrayMap、SparseArray 分析比较:http://blog.csdn.net/chen_lifeng/article/details/52057427

    • Android 性能优化之 String 篇:http://www.androidchina.net/5940.html

    • SharedPreferences 的 commit 和 apply 分析:http://blog.csdn.net/u010198148/article/details/51706483

2、内存泄漏

  • 什么是内存泄漏?

    一些不用的对象被长期持有,导致内存无法被释放。

  • 可能发生内存泄漏的地方有哪些?

    • 内部类引用导致 Activity 的泄漏

      在 Java 中,非静态 (匿名) 内部类会默认隐性引用外部类对象。而静态内部类不会引用外部类对象。
      最典型的场景是 Handler 导致的 Activity 泄漏,如果 Handler 中有延迟的任务或者是等待执行的任务队列过长,都有可能因为 Handler 继续执行而导致 Activity 发生泄漏。
      为了解决这个问题,可以在 UI 退出之前,执行 remove Handler 消息队列中的消息与 runnable 对象。或者是使用 Static + WeakReference 的方式来达到断开 Handler 与 Activity 之间存在引用关系的目的。
      举例,MainTabActivity - MainTabHandler:

       





       

       

      如何修复?

       

       

       

       

       

       

       

       

       

      Android Weak Handler:可以避免内存泄漏的 Handler 库,参考:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1123/2047.html

    • Activity Context 被传递到其他实例中,这可能导致自身被引用而发生泄漏。

      考虑使用 Application Context 而不是 Activity Context。
      例如:全局 Dialog 或者 Context 被单例持有。

    • 静态造成的内存泄漏

       

       

       

       

       

       

      还有静态变量持有 View,例如:

        private static View view;
      
        void setStaticView() {
            view = findViewById(R.id.sv_button);
        }
      
    • 注意监听器的注销(稍后利用工具分析一个例子)

      regist 就要 unregist

    • 注意 Cursor 对象是否及时关闭(项目中也存在,不再列举)

    • WebView 的引起的泄漏(暂时没有研究)

  • 使用工具分析定位解决内存泄漏

    • Memory monitor

      通过 MemoryMonitor 可以看到,启动手雷进入手雷的内存情况如下(为什么没有做任何操作内存一直在增加?):

       

       

       

       

       

       

      通过实例分析一处内存泄漏,操作步骤如下:
      启动手雷 - 进入首页 - 切换底部 tab 到我的 tab - 点击登录弹窗弹窗 - 登录成功返回首页

       

       

       

       

       

       

    • MAT(Memory Analyzer Tool)

      需要下载 MAT 独立版,可到这里下载解压使用:\\192.168.8.188\上传2\ltsoft
      分析刚才的操作内存情况

       

       

       

       

       

       

       

       

       

      进入首页并没有进行任何活动操作,为什么会有那么多 bitmap 对象呢?

       

       

       

    • LeakCanary

       

       

       

      LeakCanary 中文使用说明,参考:https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/
      LeakCanary: 让内存泄露无所遁形,参考:https://www.liaohuqiu.net/cn/posts/leak-canary/

    • TraceView(不做详细分析)

    • GT(应该更适合测试同学测试 APP 性能)

      利用 GT,仅凭一部手机,无需连接电脑,您即可对 APP 进行快速的性能测试 (CPU、内存、流量、电量、帧率 / 流畅度等等)、开发日志的查看、Crash 日志查看、网络数据包的抓取、APP 内部参数的调试、真机代码耗时统计等。
      GT 官网:http://gt.qq.com/index.html

  • 内存使用策略优化

    • 看看下载一个视频加上浏览一下精选页,然后将应用切到后台,内存使用情况

       

       

       

       

       

       

       

       

       

    • 有什么优化内存的策略

      • onLowMemory():Android 系统提供了一些回调来通知当前应用的内存使用情况,通常来说,当所有的 background 应用都被 kill 掉的时候,forground 应用会收到 onLowMemory () 的回调。在这种情况下,需要尽快释放当前应用的非必须的内存资源,从而确保系统能够继续稳定运行。

      • onTrimMemory(int):Android 系统从 4.0 开始还提供了 onTrimMemory () 的回调,当系统内存达到某些条件的时候,所有正在运行的应用都会收到这个回调,同时在这个回调里面会传递参数,代表不同的内存使用情况,收到 onTrimMemory () 回调的时候,需要根据传递的参数类型进行判断,合理的选择释放自身的一些内存占用,一方面可以提高系统的整体运行流畅度,另外也可以避免自己被系统判断为优先需要杀掉的应用。

  • 文章推荐:

    • Android 内存优化之 OOM:http://hukai.me/android-performance-oom/

    • 内存泄露从入门到精通三部曲之基础知识篇:http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=21&extra=page%3D4

    • 内存泄露从入门到精通三部曲之排查方法篇:http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=62&extra=page%3D5

    • 内存泄露从入门到精通三部曲之常见原因与用户实践:http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=125&highlight=%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2

    • 胡凯 Android 性能典范系列

3、性能优化必备神器推荐(Lint)

  • 上面分析的一些项目中的问题,怎么找到的呢?

    Lint:静态代码分析工具

  • 如何通过 Lint 查找项目中的问题,如何使用?

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

  • 如果只想分析某个文件夹的代码

     

     

     

     

     

     

  • 设置代码分析选项

     

     

     

三、耗电问题

例如:关闭屏幕时关闭掉登录 ping,屏幕亮时再打开。
以上的很多问题都会导致耗电量增加。
如何优化后台下载时的耗电手机发烫问题?
需要具体的硬件工具测试应用耗电量,需要一套耗电量测试标准。



作者:李通 Tookie
链接:https://www.jianshu.com/p/307ba8911799
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

今天关于Top团队大牛带你玩转Android性能分析与优化android性能优化实战解析的分享就到这里,希望大家有所收获,若想了解更多关于Android ANR 分析与优化、android 多线程数据库读写分析与优化、Android 性能优化之 App 应用启动分析与优化、Android 性能全面分析与优化方案研究 — 几乎是史上最全最实用的等相关知识,可以在本站进行查询。

本文标签: