Android 性能優(yōu)化總結(jié)

將從以下幾個(gè)方面總結(jié)Android的應(yīng)用性能優(yōu)化

性能

  • 框架API
  • UI 性能
  • I/O性能
  • 屏幕滾動性能

內(nèi)存

  • Android 如何管理內(nèi)存
    • OOM終結(jié) & 低內(nèi)存終結(jié)
    • 應(yīng)用內(nèi)存使用監(jiān)測
  • 識別內(nèi)存泄露
  • 最佳實(shí)踐

糟糕的用戶體驗(yàn)

  • Activity 啟動時(shí)間過長
  • 應(yīng)用無反應(yīng)(ANR)
  • 幀速率差

關(guān)于幀

幀速率

  • 為了保證能達(dá)到60fps认罩,最多只有16ms去處理每一幀
  • 而保證能達(dá)到24fps躯枢,最多只有41ms去處理每一幀

常見操作耗時(shí)

  • Binder RPC 調(diào)用大約花費(fèi) 0.12ms
  • 從閃存讀取一個(gè)字節(jié)大約花費(fèi) 0.0x ~ 5ms(一個(gè)文件只讀一個(gè)字節(jié)有可能大于1ms)
  • 寫內(nèi)容到閃存大約花費(fèi) 1-100ms(一個(gè)文件只寫一個(gè)字節(jié)有可能小于1ms)
  • TCP 初始化加上HTTP提取通臣⒋桑花費(fèi)秒級的時(shí)間(此處指的是建立鏈接并獲取鏈接返回的數(shù)據(jù)的時(shí)間)

讀寫操作在性能差一些的機(jī)子上可能時(shí)間會有出入

往磁盤寫內(nèi)容的時(shí)候氓奈,會隨著磁盤的剩余空間的較少而導(dǎo)致寫速率不斷減低

永遠(yuǎn)不要做阻塞UI線程的事情,用一個(gè)新的線程去做可能會影響UI體驗(yàn)的事情

四種可以異步的實(shí)現(xiàn):

  1. Runnable
  2. Thread
  3. Future
  4. ExecutorService
  • 使用Thread
new Thread(new Runnable() {
  @Override
  public void run() {
    // do some heavy work
  }
}).start();

  • 使用內(nèi)置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();
}
    
  • 使用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"));

UI線程性能總結(jié)

  • Activity or Fragment
    • AsyncTask
    • Handler,HandlerThread
    • AsyncTaskLoader
  • ContentProvider
    • AsyncQueryHandler
    • CursorLoader
  • Service
    • IntentService
    • Parcel.writeStrongBinder(IBinder binder)

View Hierarchy

  • Measure
  • Layout
  • Draw
  • Key Events
  • Trackball Events
  • Touch Evnets

Tips:

  • 降低布局層次結(jié)構(gòu)的復(fù)雜性

  • 使用層次結(jié)構(gòu)查看器來檢查是否存在瓶頸

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

  • 使用<merge />標(biāo)簽來較少布局層次

  • 使用<ViewStub />標(biāo)簽來延遲該標(biāo)簽下的布局的渲染

    <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性能優(yōu)化

  • 異步寫SharedPreferences

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

  • 使用TraceView配置您的數(shù)據(jù)庫查詢

  • 使用LIMIT子句減少選擇行

  • 最小化完整窗口時(shí)間

  • 使用索引優(yōu)化數(shù)據(jù)庫查詢

  • 預(yù)編譯常用的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
  • 推遲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;
    }
  }
  • 在事務(wù)中使用批量操作

    • ContentProviderOperation!
    • ContentProviderOperation.Builder!
    • ContentResolver.applyBatch()
  • 在一個(gè)比較長的事務(wù)中允許偶爾的事務(wù)提前

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

    adb logcat -b events content_query_sample:I *:S
    adb logcat -b events content_update_sample:I *:S
    adb logcat -b events db_sample:I *:S

滑動性能優(yōu)化(List)

  • ListView : 通過復(fù)用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的不必要繪制(例如背景的重復(fù)繪制)

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

    當(dāng)你有一個(gè)父view并且是永遠(yuǎn)不可見的颜凯,那么不要繪制它(包括他的背景)

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

    //Activity中
    getWindow().setBackgroundDrawable(null);
    
    //style中
    android:windowBackground="@null"
    
  • 避免在運(yùn)行時(shí)進(jìn)行圖片縮放(特殊業(yè)務(wù)需求除外)

  • 避免在視圖(ListView等)滾動的時(shí)候進(jìn)行動畫,如果業(yè)務(wù)要求使用動畫仗扬,那么請關(guān)閉繪制緩存

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

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

  • 緩存的時(shí)候考慮使用SoftReference

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

    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();
    

Memory

在系統(tǒng)級別早芭,Android使用低內(nèi)存驅(qū)動程序運(yùn)行修改過的OOM Killer

包括:

  • Linux OOM killer
  • OOM_ADJ
  • Android Low Memory Killer

Android中的低內(nèi)存閾值(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

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 

進(jìn)程重要性級別

  • Persistent(持續(xù)存在)
    • OOM_ADJ < 0
    • system_server (-16) , com.android.phone (-12)
  • Foreground(前臺進(jìn)程)
    • FOREGROUND_APP_ADJ = 0
    • 運(yùn)行前臺Activity
    • 運(yùn)行一個(gè)Service穴豫,執(zhí)行onCreate(),onStartCommand(),onDestroy()
    • 托管由前臺Activity或前臺進(jìn)程綁定的Service
    • 運(yùn)行一個(gè)BroadcastReceiver精肃,執(zhí)行onReceive()
    • 托管由持續(xù)或前臺進(jìn)程使用的ContentProvider
  • Visible(可見進(jìn)程)
    • VISIBLE_APP_ADJ = 1
    • 運(yùn)行可見的Activity(不在前臺秤涩,也就是,不是正在和用戶交互的)
    • 運(yùn)行由startService()啟動的Service司抱,Service使用startForeground()
      使自己處于前臺狀態(tài)
  • Service(服務(wù)進(jìn)程)
    • SECONDARY_SERVER_ADJ = 4
    • 運(yùn)行由startService()啟動Service筐眷,并且不是可見進(jìn)程
  • Background(后臺進(jìn)程)
    • HIDDEN_APP_MIN_ADJ (7) .. EMPTY_APP_ADJ (15)
    • 不包含任何活動應(yīng)用程序組件的進(jìn)程

Low Memory 回調(diào)

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

在應(yīng)用程序級別,Android限制了多少內(nèi)存可以分配給每個(gè)應(yīng)用程序习柠。

Android為每個(gè)應(yīng)用程序定義了一個(gè)堆限制,并指示何時(shí)拋出OutOfMemoryError

Android Studio 中 Heap窗口中的相關(guān)術(shù)語

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

ActivityManager.getMemoryClass() 可以查看當(dāng)前應(yīng)用Heap size limit

OOM 發(fā)生的情形

  • 2.3之前

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

  • 2.3(包括)之后

Heap size + new allocation request >= Heap limit

new allocation request : 新的內(nèi)存開辟請求大小

不代表進(jìn)程內(nèi)存使用的情形

  • 每個(gè)進(jìn)程從zygote fork出來后匀谣,都會有2mb以上的開銷
  • 在使用native的時(shí)候會開辟更多的內(nèi)存:
    • Android應(yīng)用程序運(yùn)行在Dalvik VM中,同時(shí)通過JNI加載本地庫
    • 由應(yīng)用程序調(diào)用的Dalvik級API可以代表申請人使用本機(jī)庫资溃。
  • 如果你啟用了硬件加速(4.0中默認(rèn)開啟)武翎,那么會多有8mb的內(nèi)存去使用OpenGL

查看內(nèi)存使用情況

  • 根據(jù)進(jìn)程內(nèi)存使用情況排序:

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):進(jìn)程可以訪問的頁面總數(shù)

RSS(Resident Set Size): RAM中進(jìn)程可以訪問的頁總數(shù)

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

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

  • 列出進(jìn)程的虛擬內(nèi)存區(qū)域

    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__
.......

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!

Private Dirty = USS

無法分頁到磁盤并且不與任何其他進(jìn)程共享的進(jìn)程內(nèi)部RAM量

當(dāng)進(jìn)程消失時(shí)溶锭,系統(tǒng)可以使用的RAM

  • 一些重要的虛擬內(nèi)存區(qū)域

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

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

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

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

Garbage collection(垃圾收集)

  • 2.3之前的GC

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

    對整個(gè)堆進(jìn)行收集

    造成的暫停時(shí)間一般都大于100ms

  • 2.3及其之后

    不會暫停其他工作宝恶,而是與其他工作同時(shí)進(jìn)行(絕大部分是這樣的)

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

    造成的暫停時(shí)間一般小于5ms

Memory leaks(內(nèi)存泄露)

  • GC并不能避免內(nèi)存泄露
  • 有一個(gè)指向長期存在的且未使用的對象的應(yīng)用,導(dǎo)致這個(gè)不被使用的對象不能被回收
  • 在Android中趴捅,通常發(fā)生內(nèi)存泄露的是對Context或者Activity的引用

常見的因?yàn)镃ontext 或著 Activity造成的內(nèi)存泄露

  1. 在Activity中存在長期存在的指向非靜態(tài)內(nèi)部類實(shí)例對象的引用

    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();
        //....
      }
      //.....
    }
    
  2. 在Activity中有超出Activity生命周期且長期存活的線程

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

有用的方法

  • 使用logcat檢查是否有內(nèi)存隨著時(shí)間的推移而不斷增加(尤其注意某些方法的執(zhí)行步驟5姹小)

例如得到的日志信息:

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

D/dalvikvm(9050): <u>GC_CONCURRENT</u> 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 <u>free 2049k</u>, 65% free 3571k/9991k, external 4703k/5261k, paused 2ms+2ms

下劃線處GC的原因:

內(nèi)存釋放

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

下劃線處GC的原因:

內(nèi)存釋放

堆進(jìn)行信息統(tǒng)計(jì)

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

下劃線處GC的原因:

內(nèi)存釋放

堆進(jìn)行信息統(tǒng)計(jì)

內(nèi)部內(nèi)存進(jìn)行信息統(tǒng)計(jì)

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

下劃線處GC的原因:

內(nèi)存釋放

堆進(jìn)行信息統(tǒng)計(jì)

內(nèi)部內(nèi)存進(jìn)行信息統(tǒng)計(jì)

時(shí)間暫停

  • 使用分配跟蹤器查看是否有隨著時(shí)間分配未預(yù)料的對象(Android Studio中的logcat窗口中有對應(yīng)的按鈕)
  • 使用(Histogram view)直方圖視圖查看活動實(shí)例的數(shù)量。 有多于一個(gè)Activity的一個(gè)實(shí)例拱绑,那么這是一個(gè)強(qiáng)烈的Activity / Context泄露的跡象综芥。
  • 按保留大小排序的Dominator Tree視圖有助于識別保留了大量的內(nèi)存且不能被釋放的對象。 他們通常是找到內(nèi)存泄漏的好起點(diǎn)猎拨。

強(qiáng)烈推薦郭神關(guān)于內(nèi)存泄露分析的文章:

Android最佳性能實(shí)踐(二)——分析內(nèi)存的使用情況

其他優(yōu)化建議

  • 造成OutOfMemoryError的原因通常是Bitmap或者對象進(jìn)行了太多的內(nèi)存分配

    加載圖片的時(shí)候盡可能不要加載原尺寸的大圖膀藐,可以使用縮略圖

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

    2.3(包括)之前,Bitmap的引用是放在堆中的红省,而Bitmap的數(shù)據(jù)部分是放在棧中的额各,需要用戶調(diào)用recycle方法手動進(jìn)行內(nèi)存回收 ,2.3之后类腮,整個(gè)Bitmap,包括數(shù)據(jù)和引用蛉加,都放在了堆中蚜枢,這樣,整個(gè)Bitmap的回收就全部交給GC了针饥,這個(gè)recycle方法就再也不需要使用了厂抽。

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

    盡可能的避免碎片化

    減少Java在應(yīng)用堆空間堆快滿的時(shí)候再堆分配

    緩存中使用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);
//對比之間的例子丁眼,這里改成了靜態(tài)內(nèi)部類
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);
  }
}
}

更多優(yōu)化建議筷凤,請移步郭神博客

Android最佳性能實(shí)踐

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子藐守,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,294評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件籽腕,死亡現(xiàn)場離奇詭異河劝,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)慎恒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,493評論 3 385
  • 文/潘曉璐 我一進(jìn)店門任内,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人融柬,你說我怎么就攤上這事死嗦。” “怎么了粒氧?”我有些...
    開封第一講書人閱讀 157,790評論 0 348
  • 文/不壞的土叔 我叫張陵越除,是天一觀的道長。 經(jīng)常有香客問我靠欢,道長廊敌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,595評論 1 284
  • 正文 為了忘掉前任门怪,我火速辦了婚禮骡澈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘掷空。我一直安慰自己肋殴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,718評論 6 386
  • 文/花漫 我一把揭開白布坦弟。 她就那樣靜靜地躺著护锤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪酿傍。 梳的紋絲不亂的頭發(fā)上烙懦,一...
    開封第一講書人閱讀 49,906評論 1 290
  • 那天,我揣著相機(jī)與錄音赤炒,去河邊找鬼氯析。 笑死,一個(gè)胖子當(dāng)著我的面吹牛莺褒,可吹牛的內(nèi)容都是我干的掩缓。 我是一名探鬼主播,決...
    沈念sama閱讀 39,053評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼遵岩,長吁一口氣:“原來是場噩夢啊……” “哼你辣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,797評論 0 268
  • 序言:老撾萬榮一對情侶失蹤舍哄,失蹤者是張志新(化名)和其女友劉穎宴凉,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蠢熄,經(jīng)...
    沈念sama閱讀 44,250評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡跪解,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,570評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了签孔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叉讥。...
    茶點(diǎn)故事閱讀 38,711評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖饥追,靈堂內(nèi)的尸體忽然破棺而出图仓,到底是詐尸還是另有隱情,我是刑警寧澤但绕,帶...
    沈念sama閱讀 34,388評論 4 332
  • 正文 年R本政府宣布救崔,位于F島的核電站,受9級特大地震影響捏顺,放射性物質(zhì)發(fā)生泄漏六孵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,018評論 3 316
  • 文/蒙蒙 一幅骄、第九天 我趴在偏房一處隱蔽的房頂上張望劫窒。 院中可真熱鬧,春花似錦拆座、人聲如沸主巍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,796評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽孕索。三九已至,卻和暖如春躏碳,著一層夾襖步出監(jiān)牢的瞬間搞旭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,023評論 1 266
  • 我被黑心中介騙來泰國打工菇绵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肄渗,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,461評論 2 360
  • 正文 我出身青樓脸甘,卻偏偏與公主長得像恳啥,于是被迫代替她去往敵國和親偏灿。 傳聞我的和親對象是個(gè)殘疾皇子丹诀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,595評論 2 350

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,825評論 25 707
  • 1.簡介 2.內(nèi)存的管理和分析2.1 當(dāng)界面不可見時(shí)釋放內(nèi)存2.2 當(dāng)內(nèi)存緊張時(shí)釋放內(nèi)存2.3 避免在Bitmap...
    JC_Mobile閱讀 915評論 0 6
  • 本文大體分為四部分 內(nèi)存優(yōu)化 布局優(yōu)化 編碼優(yōu)化 網(wǎng)絡(luò)優(yōu)化 內(nèi)存優(yōu)化 主要參考胡凱文章 首先說一下內(nèi)存泄漏和OOM...
    KwokKwok閱讀 346評論 0 2
  • 1,UI優(yōu)化:這篇文章總結(jié)的不錯(cuò) 2,內(nèi)存泄漏優(yōu)化 常見的幾種形式: 資源對象沒關(guān)閉造成的內(nèi)存泄漏: 資源對象沒關(guān)...
    Richard_7df6閱讀 262評論 0 0
  • 2016-12-16 [星期五 北京市朝陽區(qū) 分開第202天 凌晨1:30 剛給KK打了電話铆遭,因?yàn)榭此呐笥讶ο踝?..
    茶家墳里白閱讀 294評論 0 0