Android 性能优化总结

将从以下几个方面总结Android的应用性能优化

性能

  • 框架API
  • UI 性能
  • I/O性能
  • 屏幕滚动性能

内存

  • Android 如何管理内存
    • OOM终结 & 低内存终结
    • 应用内存使用监测
  • 识别内存泄露
  • 最佳实践

糟糕的用户体验

  • Activity 启动时间过长
  • 应用无反应(ANR)
  • 帧速率差

关于帧

帧速率

  • 为了保证能达到60fps,最多只有16ms去处理每一帧
  • 而保证能达到24fps,最多只有41ms去处理每一帧

常见操作耗时

  • Binder RPC 调用大约花费 0.12ms
  • 从闪存读取一个字节大约花费 0.0x ~ 5ms(一个文件只读一个字节有可能大于1ms)
  • 写内容到闪存大约花费 1-100ms(一个文件只写一个字节有可能小于1ms)
  • 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();
}
    
  • 使用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线程性能总结

  • 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:

  • 降低布局层次结构的复杂性

  • 使用层次结构查看器来检查是否存在瓶颈

  • 使用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
  • 推迟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;
    }
  }
  • 在事务中使用批量操作

    • ContentProviderOperation!
    • ContentProviderOperation.Builder!
    • ContentResolver.applyBatch()
  • 在一个比较长的事务中允许偶尔的事务提前

    SQLiteDatabase.yieldIfContendedSafely()
    
  • 使用事件日志调试

    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

滑动性能优化(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"
    
  • 避免在运行时进行图片缩放(特殊业务需求除外)

  • 避免在视图(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();
    

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

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 

进程重要性级别

  • Persistent(持续存在)
    • OOM_ADJ < 0
    • system_server (-16) , com.android.phone (-12)
  • Foreground(前台进程)
    • FOREGROUND_APP_ADJ = 0
    • 运行前台Activity
    • 运行一个Service,执行onCreate(),onStartCommand(),onDestroy()
    • 托管由前台Activity或前台进程绑定的Service
    • 运行一个BroadcastReceiver,执行onReceive()
    • 托管由持续或前台进程使用的ContentProvider
  • Visible(可见进程)
    • VISIBLE_APP_ADJ = 1
    • 运行可见的Activity(不在前台,也就是,不是正在和用户交互的)
    • 运行由startService()启动的Service,Service使用startForeground()
      使自己处于前台状态
  • Service(服务进程)
    • SECONDARY_SERVER_ADJ = 4
    • 运行由startService()启动Service,并且不是可见进程
  • Background(后台进程)
    • HIDDEN_APP_MIN_ADJ (7) .. EMPTY_APP_ADJ (15)
    • 不包含任何活动应用程序组件的进程

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 发生的情形

  • 2.3之前

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

  • 2.3(包括)之后

Heap size + new allocation request >= Heap limit

new allocation request : 新的内存开辟请求大小

不代表进程内存使用的情形

  • 每个进程从zygote fork出来后,都会有2mb以上的开销
  • 在使用native的时候会开辟更多的内存:
    • Android应用程序运行在Dalvik VM中,同时通过JNI加载本地库
    • 由应用程序调用的Dalvik级API可以代表申请人使用本机库。
  • 如果你启用了硬件加速(4.0中默认开启),那么会多有8mb的内存去使用OpenGL

查看内存使用情况

  • 根据进程内存使用情况排序:

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

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

无法分页到磁盘并且不与任何其他进程共享的进程内部RAM量

当进程消失时,系统可以使用的RAM

  • 一些重要的虚拟内存区域

/dev/ashmem/dalvik-heap : 在Dalvik级别为堆分配的匿名页面

[heap], [anonymous] : 由malloc()在本机级别分配的匿名页面

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

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

Garbage collection(垃圾收集)

  • 2.3之前的GC

    收集垃圾的时候会停止其他所有的工作

    对整个堆进行收集

    造成的暂停时间一般都大于100ms

  • 2.3及其之后

    不会暂停其他工作,而是与其他工作同时进行(绝大部分是这样的)

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

    造成的暂停时间一般小于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();
        //....
      }
      //.....
    }
    
  2. 在Activity中有超出Activity生命周期且长期存活的线程

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

有用的方法

  • 使用logcat检查是否有内存随着时间的推移而不断增加(尤其注意某些方法的执行步骤!)

例如得到的日志信息:

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的原因:

内存释放

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

下划线处GC的原因:

内存释放

堆进行信息统计

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

下划线处GC的原因:

内存释放

堆进行信息统计

内部内存进行信息统计

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

下划线处GC的原因:

内存释放

堆进行信息统计

内部内存进行信息统计

时间暂停

  • 使用分配跟踪器查看是否有随着时间分配未预料的对象(Android Studio中的logcat窗口中有对应的按钮)
  • 使用(Histogram view)直方图视图查看活动实例的数量。 有多于一个Activity的一个实例,那么这是一个强烈的Activity / Context泄露的迹象。
  • 按保留大小排序的Dominator Tree视图有助于识别保留了大量的内存且不能被释放的对象。 他们通常是找到内存泄漏的好起点。

强烈推荐郭神关于内存泄露分析的文章:

Android最佳性能实践(二)——分析内存的使用情况

其他优化建议

  • 造成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);
  }
}
}

更多优化建议,请移步郭神博客

Android最佳性能实践

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,732评论 25 707
  • 1.简介 2.内存的管理和分析2.1 当界面不可见时释放内存2.2 当内存紧张时释放内存2.3 避免在Bitmap...
    JC_Mobile阅读 915评论 0 6
  • 本文大体分为四部分 内存优化 布局优化 编码优化 网络优化 内存优化 主要参考胡凯文章 首先说一下内存泄漏和OOM...
    KwokKwok阅读 345评论 0 2
  • 1,UI优化:这篇文章总结的不错 2,内存泄漏优化 常见的几种形式: 资源对象没关闭造成的内存泄漏: 资源对象没关...
    Richard_7df6阅读 260评论 0 0
  • 2016-12-16 [星期五 北京市朝阳区 分开第202天 凌晨1:30 刚给KK打了电话,因为看她的朋友圈,似...
    茶家坟里白阅读 292评论 0 0