Android 性能優化總結

將從以下幾個方面總結Android的應用性能優化

性能

  • 框架API

  • UI 性能

  • I/O性能

  • 屏幕滾動性能

內存

  • Android 如何管理內存

    • OOM終結 & 低內存終結

    • 應用內存使用監測

    </li>
  • 識別內存泄露

  • 最佳實踐

  • </ul>

    糟糕的用戶體驗

    • Activity 啟動時間過長

    • 應用無反應(ANR)

    • 幀速率差

    關于幀

    幀速率

    • 為了保證能達到60fps,最多只有16ms去處理每一幀

    • 而保證能達到24fps,最多只有41ms去處理每一幀

    常見操作耗時

    • Binder RPC 調用大約花費 0.12ms

    • 從閃存讀取一個字節大約花費 5-25ms

    • 寫內容到閃存大約花費 5-100ms

    • TCP 初始化以及HTTP提取通常花費秒級的時間

    往磁盤寫內容的時候,會隨著磁盤的剩余空間的較少而導致寫速率不斷減低

    永遠不要做阻塞UI線程的事情,用一個新的線程去做可能會影響UI體驗的事情

    四種可以異步的實現:

    1. Runnable

    2. Thread

    3. Future

    4. ExecutorService

    • 使用Thread

    new Thread(new Runnable() {
      @Override
      public void run() {
          // do some heavy work
      }
    }).start();
    • 使用內置AsyncTask

    new AsyncTask<URL, Integer, Integer>() {
      protected Long doInBackground(URL... urls) {
        final int count = urls.length;
          for ( int i = 0; i < count; i++ ) {
            Downloader.download(url);
            publishProgress(i);
        }
      return count;
      }
      protected void onProgressUpdate(Integer... progress) {
          setProgress(progress[0]);
      }
      protected void onPostExecute(Integer result) {
          showDialog(“Downloaded “ + result + “ files”);
      }
    }
    • 使用HandlerThread

    HandlerThread mHandlerThread = new HandlerThread("WorkerThread");
    Handler handler = new Handler(mHandlerThread.getLooper()) {
      @Override
      public void handleMessage(Message msg) {
        switch (msg.what) {
          case JOB_1:
              // do job #1
          break;
          case JOB_2:
              // do job #2
          break;
        }
      }
    };

    handler.sendEmptyMessage(JOB_1); handler.sendEmptyMessage(JOB_2);

    handler.post(new Runnable() { @Override public void run() { // do more work } });

    @Override protected void onDestroy() { mHandlerThread.quit(); super.onDestroy(); }</code></pre>

    • 使用AsyncQueryHandler

    new AsyncQueryHandler(getContentResolver()) {
      @Override
      protected void onQueryComplete(int token, Object cookie,
        Cursor cursor) {
          if (token == 0) {
              // get data from cursor
          }
        }
        }.startQuery(0, // token
            null, // cookie
            RawContacts.CONTENT_URI, null, // projection
            RawContacts.CONTACT_ID + "<?", // selection
            new String[] { "888" }, // selectionArgs
            RawContacts.DISPLAY_NAME_PRIMARY + " ASC" // orderby
            );
    • 使用IntentService

    public class WorkerService extends IntentService {
      public WorkerService() {
        super("WorkerThread");
      }
      @Override
      protected void onHandleIntent(Intent intent) {
        String action = intent.getAction();
        if ("com.test.DO_JOB_1".equals(action)) {
            // do job #1
        }
      }
    }

    startService(new Intent("com.test.DO_JOB_1"));</code></pre>

    UI線程性能總結

    • Activity or Fragment

      • AsyncTask

      • Handler,HandlerThread

      • AsyncTaskLoader

      </li>
    • ContentProvider

      • AsyncQueryHandler

      • CursorLoader

      • </ul> </li>
      • Service

        • IntentService

        • Parcel.writeStrongBinder(IBinder binder)

        • </ul> </li> </ul>

          View Hierarchy

          • Measure

          • Layout

          • Draw

          • Key Events

          • Trackball Events

          • Touch Evnets

          Tips:

          • 降低布局層次結構的復雜性

          • 使用層次結構查看器來檢查是否存在瓶頸

          • 使用RelativeLayout或者GridLayout來簡化復雜布局的層次嵌套

          • 使用 <merge /> 標簽來較少布局層次

          • 使用 <ViewStub /> 標簽來延遲該標簽下的布局的渲染

            <ViewStub
            android:id="@+id/stub_import"
            android:inflatedId="@+id/panel_import"
            android:layout="@layout/progress_overlay"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom" />
            ((ViewStub)
            findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
            // or
            View importPanel = ((ViewStub)
            findViewById(R.id.stub_import)).inflate();
          • 使用layoutopt檢測常見問題

          I/O性能優化

          • 異步寫SharedPreferences

            SharedPreferences.Editor.apply(); // 異步
            SharedPreferences.Editor.commit(); // 同步
          • 數據庫query查詢語句中的 * 替換成具體的列值

          • 使用TraceView配置您的數據庫查詢

          • 使用LIMIT子句減少選擇行

          • 最小化完整窗口時間

          • 使用索引優化數據庫查詢

          • 預編譯常用的SQL語句

          String sql = “INSERT INTO table VALUES (?, ?)”;
          SQLiteStatement stmt = mDatabase.compileStatement(sql);
          DatabaseUtils.bindObjectToProgram(stmt, 1, 1);
          DatabaseUtils.bindObjectToProgram(stmt, 2, 2);
          stmt.execute();
          stmt.close();

          //或者使用 PreparaStatement</code></pre>

          • 推遲ContentObserver.onChange()中的自動重新檢查
          getContentResolver().registerContentObserver(uri, true,
            new ContentObserver(new Handler()) {
              @Override
              public void onChange(boolean selfChange) {
                mDirty = true;
              }
            });

          @Override protected void onResume() { super.onResume(); if (mDirty) { // start query again mDirty = false; } }</code></pre>

          • 在事務中使用批量操作

            • ContentProviderOperation!
            • ContentProviderOperation.Builder!
            • ContentResolver.applyBatch()
            </li>
          • 在一個比較長的事務中允許偶爾的事務提前

            SQLiteDatabase.yieldIfContendedSafely()
          • 使用事件日志調試

            adb logcat -b events content_query_sample:I

            :S

            :S

            adb logcat -b events db_sample:I *:S

          • </ul>

            滑動性能優化(List)

            • ListView : 通過復用view來避免不必要的inflate操作

              @Override
              public View getView(int position, View convertView, ViewGroup parent) {
                if (convertView == null) {
                    convertView = mInflater.inflate(R.layout.main, parent, false);
                }
                // ....
              }
            • 通過ViewHolder緩存v試圖,而避免不必要的findViewByI'd

              @Override
              public View getView(int position, View convertView, ViewGroup parent) {
                if (convertView == null) {
                  convertView = mInflater.inflate(R.layout.main, parent, false);
                  ViewHolder holder = new ViewHolder();
                  holder.img = (ImageView) convertView.findViewById(R.id.image);
                  holder.txt = (TextView) convertView.findViewById(R.id.text);
                  convertView.setTag(holder);
                }
                ViewHolder holder = (ViewHolder) convertView.getTag();
                holder.img.setImageResource(R.drawable.icon);
                holder.txt.setText(R.string.hello);
                return convertView;
              }
              private static class ViewHolder {
                ImageView img;
                TextView txt;
              }
            • 避免view的不必要繪制(例如背景的重復繪制)

              Android 中 會繪制每一個父view即使它被覆蓋在一個不透明的子view之下

              當你有一個父view并且是永遠不可見的,那么不要繪制它(包括他的背景)

            • 大多數的情況下你不需要繪制window的背景

              //Activity中
              getWindow().setBackgroundDrawable(null);

              //style中 android:windowBackground="@null"</code></pre> </li>

            • 避免在運行時進行圖片縮放(特殊業務需求除外)

            • 避免在視圖(ListView等)滾動的時候進行動畫,如果業務要求使用動畫,那么請關閉繪制緩存

              ListView.setDrawableCacheEnabled(false)
            • 使用Allocation Tracker(內存分配追蹤器)檢測并避免頻繁的垃圾回收

            • 考慮使用Object Pool ,StringBuilder等封裝類型

            • 緩存的時候考慮使用SoftReference

            • 在調試模式的時候啟用StrictMode(可以檢查大部分不規范,不安全操作)

              public void onCreate() {
                if (DEVELOPER_MODE) {
                      StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                          .detectDiskReads()
                          .detectDiskWrites()
                          .detectNetwork()
                          .penaltyLog()
                          .build());
                      StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                          .detectLeakedSqlLiteObjects()
                          .detectLeakedClosableObjects()
                          .penaltyLog()
                          .penaltyDeath()
                          .build());
                  }
                super.onCreate();
              }
            • 檢查主線程Looper是否有不必要的活動

              Looper.setMessageLogging();
            • </ul>

              Memory

              在系統級別,Android使用低內存驅動程序運行修改過的OOM Killer

              包括:

              • Linux OOM killer
              • OOM_ADJ
              • Android Low Memory Killer

              Android中的低內存閾值(init.rc中)

              # Define the memory thresholds at which the above process classes will

              be killed. These numbers are in pages (4k).

              setprop ro.FOREGROUND_APP_MEM 2048 setprop ro.VISIBLE_APP_MEM 3072 setprop ro.PERCEPTIBLE_APP_MEM 4096 setprop ro.HEAVY_WEIGHT_APP_MEM 4096 setprop ro.SECONDARY_SERVER_MEM 6144 setprop ro.BACKUP_APP_MEM 6144 setprop ro.HOME_APP_MEM 6144 setprop ro.HIDDEN_APP_MEM 7168 setprop ro.EMPTY_APP_MEM 8192</code></pre>

              OOM_ADJ基于重要性級別(init.rc中)

              # Define the oom_adj values for the classes of processes that can be

              killed by the kernel. These are used in ActivityManagerService.

              setprop ro.FOREGROUND_APP_ADJ 0 setprop ro.VISIBLE_APP_ADJ 1 setprop ro.PERCEPTIBLE_APP_ADJ 2 setprop ro.HEAVY_WEIGHT_APP_ADJ 3 setprop ro.SECONDARY_SERVER_ADJ 4 setprop ro.BACKUP_APP_ADJ 5 setprop ro.HOME_APP_ADJ 6 setprop ro.HIDDEN_APP_MIN_ADJ 7 setprop ro.EMPTY_APP_ADJ 15</code></pre>

              進程重要性級別

              • Persistent(持續存在)
                • OOM_ADJ < 0
                • system_server (-16) , com.android.phone (-12)
                </li>
              • Foreground(前臺進程)
                • FOREGROUND_APP_ADJ = 0
                • 運行前臺Activity
                • 運行一個Service,執行onCreate(),onStartCommand(),onDestroy()
                • 托管由前臺Activity或前臺進程綁定的Service
                • 運行一個BroadcastReceiver,執行onReceive()
                • 托管由持續或前臺進程使用的ContentProvider
                • </ul> </li>
                • Visible(可見進程)
                  • VISIBLE_APP_ADJ = 1
                  • 運行可見的Activity(不在前臺,也就是,不是正在和用戶交互的)
                  • 運行由startService()啟動的Service,Service使用startForeground()
                    使自己處于前臺狀態
                  • </ul> </li>
                  • Service(服務進程)
                    • SECONDARY_SERVER_ADJ = 4
                    • 運行由startService()啟動Service,并且不是可見進程
                    • </ul> </li>
                    • Background(后臺進程)
                      • HIDDEN_APP_MIN_ADJ (7) .. EMPTY_APP_ADJ (15)
                      • 不包含任何活動應用程序組件的進程
                      • </ul> </li> </ul>

                        Low Memory 回調

                        Activity.onLowMemory()
                        Fragment.onLowMemory()
                        Activity.onSaveInstanceState(Bundle)
                        Service.onLowMemory()
                        ContentProvider.onLowMemory()

                        在應用程序級別,Android限制了多少內存可以分配給每個應用程序。

                        Android為每個應用程序定義了一個堆限制,并指示何時拋出OutOfMemoryError

                        Android Studio 中 Heap窗口中的相關術語

                        術語 解釋
                        Heap limit 應用在Dalvik堆中的最大允許占用空間
                        Heap size 當前Dalvik堆的大小
                        Allocated 應用在Dalvik堆上分配的字節總數
                        Free Heap size – Allocated
                        % Used Allocated / Heap size * 100%
                        External allocation (3.0之前) Bitmap byte[]

                        ActivityManager.getMemoryClass() 可以查看當前應用Heap size limit

                        OOM 發生的情形

                        • 3.0之前

                        Heap size + external allocation + new allocation request >= Heap limit

                        • 3.0(包括)之后

                          Heap size + new allocation request >= Heap limit

                        new allocation request : 新的內存開辟請求大小

                        不代表進程內存使用的情形

                        • 每個進程從zygote fork出來后,都會有2mb以上的開銷

                        • 在使用native的時候會開辟更多的內存:

                          • Android應用程序運行在Dalvik VM中,同時通過JNI加載本地庫

                          • 由應用程序調用的Dalvik級API可以代表申請人使用本機庫。

                          </li>
                        • 如果你啟用了硬件加速(4.0中默認開啟),那么會多有8mb的內存去使用OpenGL

                        • </ul>

                          查看內存使用情況

                          • 根據進程內存使用情況排序:

                          adb shell procrank -p

                          PID Vss Rss Pss Uss cmdline!
                          3156 80272K 80220K 59228K 57624K com.htc.launcher
                          1455 94540K 58728K 37488K 36060K system_server
                          9000 55224K 55200K 33900K 32412K com.roguso.plurk
                          6713 47912K 47880K 27719K 26788K tw.anddev.aplurk
                          1624 44804K 44760K 24954K 24200K android.process.acore
                          2081 44992K 44960K 23205K 21628K com.htc.android.mail
                          1604 41288K 41248K 22393K 21752K com.htc.android.htcime
                          1594 40912K 40844K 21588K 20284K com.htc.weatheridlescreen
                          1622 39904K 39872K 21297K 20696K com.android.phone

                          VSS(Virtual Set Size):進程可以訪問的頁面總數

                          RSS(Resident Set Size): RAM中進程可以訪問的頁總數

                          PSS(Proportion Set Size):進程在RAM中使用的頁面總數,其中每個頁面的大小是頁面總數除以共享它的進程數

                          USS(Unique Set Size):進程可以訪問的非共享頁面的數量

                          • 列出進程的虛擬內存區域

                            adb shell procmem -p <pid>

                          Vss Rss Pss Uss ShCl ShDi PrCl PrDi Name


                          4K 4K 0K 0K 4K 0K 0K 0K /system/bin/app_process 4K 4K 0K 0K 4K 0K 0K 0K /system/bin/app_process 13908K 13908K 11571K 11508K 2400K 0K 11508K 0K [heap] 0K 0K 0K 0K 0K 0K 0K 0K [heap] 4K 4K 4K 4K 0K 0K 4K 0K [heap] 36K 36K 0K 0K 0K 36K 0K 0K /dev/properties .......</code></pre>

                          adb shell dumpsys meminfo <pid>

                          Applications Memory Usage (kB):
                          Uptime: 89133197 Realtime: 106110266

                          MEMINFO in pid 11961 [com.htc.friendstream] native dalvik other total limit bitmap nativeBmp size: 15032 8535 N/A 23567 32768 N/A N/A allocated: 14565 5697 N/A 20262 N/A 4669 1918 free: 162 2838 N/A 3000 N/A N/A N/A (Pss): 4105 2550 13952 20607 N/A N/A N/A (shared dirty): 2440 1928 5532 9900 N/A N/A N/A (priv dirty): 4044 708 12716 17468 N/A N/A N/A

                          Objects Views: 0 ViewRoots: 0 AppContexts: 0 Activities: 0 Assets: 7 AssetManagers: 7 Local Binders: 11 Proxy Binders: 15 Death Recipients: 1 OpenSSL Sockets: 0!</code></pre>

                          Private Dirty = USS

                          無法分頁到磁盤并且不與任何其他進程共享的進程內部RAM量

                          當進程消失時,系統可以使用的RAM

                          • 一些重要的虛擬內存區域

                          /dev/ashmem/dalvik-heap : 在Dalvik級別為堆分配的匿名頁面

                          [heap], [anonymous] : 由malloc()在本機級別分配的匿名頁面

                          /system/framework/*.odex (release build)

                          /data/dalvik-cache/*.odex (debug build) : 文件支持的mmap頁面

                          Garbage collection(垃圾收集)

                            1. 0之前的GC:

                            收集垃圾的時候會停止其他所有的工作

                            對整個堆進行收集

                            造成的暫停時間一般都大于100ms

                          • 3.0及其之后

                            不會暫停其他工作,而是與其他工作同時進行(絕大部分是這樣的)

                            一次垃圾收集只是對堆的一部分而已

                            造成的暫停時間一般小于5ms

                          Memory leaks(內存泄露)

                          • GC并不能避免內存泄露

                          • 有一個指向長期存在的且未使用的對象的應用,導致這個不被使用的對象不能被回收

                          • 在Android中,通常發生內存泄露的是對Context或者Activity的引用

                          常見的因為Context 或著 Activity造成的內存泄露

                          1. 在Activity中存在長期存在的指向非靜態內部類實例對象的引用

                            public class TestActivity extends Activity{
                              static LeakyTest leaky = null;
                              class LeakyTest{
                                void doSoming(){
                                  //doing
                                }
                              }

                            @Override protected void onCreate(Bundle saveInstanceStates){ super.onCreate(saveInstanceStates); if(leaky==null) leaky = new LeakyTest(); //.... } //..... }</code></pre> </li>

                          2. 在Activity中有超出Activity生命周期且長期存活的線程

                            new Thread(new Runnable(){
                                 @Override
                                 public void run(){
                                   //do long-live works
                                 }
                               }).start();
                          3. </ol>

                            有用的方法

                            • 使用logcat檢查是否有內存隨著時間的推移而不斷增加(尤其注意某些方法的執行步驟!)

                            例如得到的日志信息:

                            D/dalvikvm(9050):GC_CONCURRENT free 2049k, 65% free  3571k/9991k, external 4703k/5261k, paused 2ms+2ms

                            D/dalvikvm(9050): GC_CONCURRENT free 2049k, 65% free 3571k/9991k, external 4703k/5261k, paused 2ms+2ms

                            下劃線處GC的原因:

                            GC_CONCURRENT

                            GC_FOR_MALLOC

                            GC_EXTERNAL_ALLOC

                            GC_HPROF_DUMP_HEAP

                            GC_EXPLICIT

                            D/dalvikvm(9050): GC_CONCURRENT free 2049k , 65% free 3571k/9991k, external 4703k/5261k, paused 2ms+2ms

                            下劃線處GC的原因:

                            內存釋放

                            D/dalvikvm(9050): GC_CONCURRENT free 2049k, 65% free 3571k/9991k , external 4703k/5261k, paused 2ms+2ms

                            下劃線處GC的原因:

                            內存釋放

                            堆進行信息統計

                            D/dalvikvm(9050): GC_CONCURRENT free 2049k, 65% free 3571k/9991k, external 4703k/5261k, paused 2ms+2ms

                            下劃線處GC的原因:

                            內存釋放

                            堆進行信息統計

                            內部內存進行信息統計

                            D/dalvikvm(9050): GC_CONCURRENT free 2049k, 65% free 3571k/9991k, external 4703k/5261k, paused 2ms+2ms

                            下劃線處GC的原因:

                            內存釋放

                            堆進行信息統計

                            內部內存進行信息統計

                            時間暫停

                            • 使用分配跟蹤器查看是否有隨著時間分配未預料的對象(Android Studio中的logcat窗口中有對應的按鈕)

                            • 使用(Histogram view)直方圖視圖查看活動實例的數量。 有多于一個Activity的一個實例,那么這是一個強烈的Activity / Context泄露的跡象。

                            • 按保留大小排序的Dominator Tree視圖有助于識別保留了大量的內存且不能被釋放的對象。 他們通常是找到內存泄漏的好起點。

                            強烈推薦郭神關于內存泄露分析的文章:

                            其他優化建議

                            • 造成OutOfMemoryError的原因通常是Bitmap或者對象進行了太多的內存分配

                              加載圖片的時候盡可能不要加載原尺寸的大圖,可以使用縮略圖

                              回收已經不使用的Bitmap資源bitmap.recycle().

                              2.3(包括)之前,Bitmap的引用是放在堆中的,而Bitmap的數據部分是放在棧中的,需要用戶調用recycle方法手動進行內存回收 ,2.3之后,整個Bitmap,包括數據和引用,都放在了堆中,這樣,整個Bitmap的回收就全部交給GC了,這個recycle方法就再也不需要使用了。

                              列表中加載圖片注意使用小圖,以及做好緩存工作

                              盡可能的避免碎片化

                              減少Java在應用堆空間堆快滿的時候再堆分配

                              緩存中使用SoftReference

                              使用WeakReference避免堆存泄露

                            //縮放圖片
                            BitmapFactory.Options opts = new BitmapFactory.Options();
                            opts.inJustDecodeBounds = true;
                            BitmapFactory.decodeFile(path, opts);
                            final int originalWidth = opts.outWidth;
                            final int originalHeight = opts.outHeight;
                            final int originalDim = Math.max(originalWidth, originalHeight);
                            opts = new BitmapFactory.Options();
                            opts.inSampleSize = 1;
                            while ( originalDim > MAX_IMAGE_DIM ) {
                              opts.inSampleSize *= 2;
                              originalDim /= 2;
                            }
                            return BitmapFactory.decodeFile(path, opts);
                            //對比之間的例子,這里改成了靜態內部類
                            public class MainActivity extends Activity {
                              static Leaky leak = null;
                              static class Leaky {
                                void doSomething() {
                                    //doing
                                }
                              }
                              @Override
                              public void onCreate(Bundle savedInstanceState) {
                                  super.onCreate(savedInstanceState);
                                if (leak == null) {
                                  leak = new Leaky();
                                }
                              }
                            }
                            public class MainActivity extends Activity {
                            static Leaky leak = null;
                            static class Leaky {
                            private final Context mContext; //final修飾
                              public Leaky(Context context) {
                                super();
                                mContext = context;
                                doSomethingWithOuterInstance();
                              }
                              void doSomethingWithOuterInstance() {
                                String text = mContext.getString(R.string.hello);
                                System.out.println(text);
                              }
                            }
                            @Override
                            public void onCreate(Bundle savedInstanceState) {
                                super.onCreate(savedInstanceState);
                              if (leak == null) {
                                  leak = new Leaky(this);
                              }
                            }
                            }
                            public class MainActivity extends Activity {
                            static Leaky leak = null;
                            static class Leaky {
                            private final WeakReference<Context> mContext;//使用了弱引用
                            public Leaky(Context context) {
                              super();
                              mContext = new WeakReference<Context>(context);
                              doSomethingWithOuterInstance();
                            }
                            void doSomethingWithOuterInstance() {
                              Context context = mContext.get();
                              if (context != null) {
                                String text = context.getString(R.string.hello);
                                System.out.println(text);
                              }
                              }
                            }
                            @Override
                            public void onCreate(Bundle savedInstanceState) {
                              super.onCreate(savedInstanceState);
                              if (leak == null) {
                                  leak = new Leaky(this);
                              }
                            }
                            }

                             

                             

                            來自:http://www.jianshu.com/p/28f2bd6f32dc

                             

 本文由用戶 wzlx2000 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!