1 、布局优化 和UI相关的首先就是布局,特别是在开发一些复杂界面的时候,通常我们都是采用布局嵌套的方法,每个人的布局思路不太一样,写出的也不太一样,,所以就可能造成嵌套的层级过多。 我们可以通过手机设置里面的开发者选项,打开Show GPU Overdraw的选项,轻松发现这些问题,然后尽量往蓝色靠近。

<ImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/gafricalogo" />
用include引用:
<include layout="@layout/titlebar"/>
<TextView android:layout_width=”match_parent”
android:layout_height="wrap_content"
android:text="@string/hello"
android:padding="10dp" />
...
注: 可以在include标签中覆盖被引用的根布局的android:layout_*属性,但是前提是android:layout_height和android:layout_width都要写,如:
1.2.2.减少布局层次merge 布局顶结点是FrameLayout且不需要设置background或padding等属性,可以用merge代替,因为Activity内容视图的parent view就是个FrameLayout,所以可以用merge消除只剩一个。 某布局作为子布局被其他布局include时,使用merge当作该布局的顶节点,这样在被引入时顶结点会自动被忽略,而将其子节点全部合并到主布局中。
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_40"
android:layout_above="@+id/text"/>
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_40"
android:layout_alignParentBottom="true"
android:text="@string/app_name" />
1.1.3.需要时加载ViewStub viewstub标签同include标签一样可以用来引入一个外部布局,不同的是,viewstub引入的布局默认不会扩张,即既不会占用显示也不会占用位置,从而在解析layout时节省cpu和内存。 viewstub常用来引入那些默认不会显示,只在特殊情况下显示的布局,如进度布局、网络失败显示的刷新布局、信息出错出现的提示布局等。 定义如下:
加载ViewStub:
((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE); // or View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate(); 一旦visible/inflated,ViewStub元素将被layout属性值替换,并且android:inflatedId的值将作为根布局的id
注:
- ViewStub目前有个缺陷就是还不支持 标签。
- 要想获得stub_import中的控件,用如下代码 //必须使用inflate方法,否则下面获取不到子控件 View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate(); importPanel.findViewById(R.id.children); 优化 1.3 复杂界面可选择ConstraintLayout,可有效减少层级
2、绘制优化 其实布局优化了对于性能提升影响不算很大,但是是我们最容易下手,最直接接触的优化,所以不管能提升多少,哪怕只有百分之一的提升,我们也要做,因为影响性能的地方太多了,每个部分都提升一点,我们应用就可以提升很多了。 我们平时感觉的卡顿问题最主要的原因之一是因为渲染性能,因为越来越复杂的界面交互,其中可能添加了动画,或者图片等等。我们希望创造出越来越炫的交互界面,同时也希望他可以流畅显示,但是往往卡顿就发生在这里。 第一点: onDraw方法中不要做耗时的任务,也不做过多的循环操作,特别是嵌套循环,虽然每次循环耗时很小,但是大量的循环势必霸占CPU的时间片,从而造成View的绘制过程不流畅。 第二点: 除了循环之外,onDraw()中不要创建新的局部对象,因为onDraw()方法一般都会频繁大量调用,就意味着会产生大量的零时对象,不进占用过的内存,而且会导致系统更加频繁的GC,大大降低程序的执行速度和效率。 其实这两点在android的UI线程中都适用。 升级进化: 优化3.0
onDraw中不要创建新的局部对象 onDraw方法中不要做耗时的任务
3、内存优化 内存泄漏指的是那些程序不再使用的对象无法被GC识别,这样就导致这个对象一直留在内存当中,占用了没来就不多的内存空间。

static List<Object> mList = new ArrayList<>();
for (int i = 0; i < 100; i++) {
Object obj = new Object();
mList.add(obj);
obj = null;
}
复制代码当mList没用的时候,我们如果不做处理的话,这就是典型的占着茅坑不拉屎,mList内部持有者众多集合元素的对象,不泄露天理难容啊。解决这个问题也超级简单。把mList清理掉,然后把它的引用也给释放掉。 mList.clear(); mList = null; 复制代码2、单例/静态变量造成的内存泄漏 单例模式具有其 静态特性,它的生命周期 等于应用程序的生命周期,正是因为这一点,往往很容易造成内存泄漏。 先来一个小栗子:
public class SingleInstance {
private static SingleInstance mInstance;
private Context mContext;
private SingleInstance(Context context){
this.mContext = context;
}
public static SingleInstance newInstance(Context context){
if(mInstance == null){
mInstance = new SingleInstance(context);
}
return sInstance;
}
}
复制代码当我们在Activity里面使用这个的时候,把我们Acitivty的context传进去,那么,这个单例就持有这个Activity的引用,当这个Activity没有用了,需要销毁的时候,因为这个单例还持有Activity的引用,所以无法GC回收,所以就出现了内存泄漏,也就是生命周期长的持有了生命周期短的引用,造成了内存泄漏。 所以我们要做的就是生命周期长的和生命周期长的玩,短的和短的玩。就好比你去商场,本来就是传个话的,话说完就要走了,突然保安过来非要拉着你的手,说要和你天长地久。只要商场在一天,他就要陪你一天。天呢?太可怕了。叔叔我们不约,我有我的小伙伴,我还要上学呢,你赶紧找你的保洁阿姨去吧。你在商场的生命周期本来可能就是1分钟,而保安的生命周期那是要和商场开关门一致的,所以不同生命周期的最好别一起玩的好。 解决方案也很简单:
public class SingleInstance {
private static SingleInstance mInstance;
private Context mContext;
private SingleInstance(Context context){
this.mContext = context.getApplicationContext();
}
public static SingleInstance newInstance(Context context){
if(mInstance == null){
mInstance = new SingleInstance(context);
}
return sInstance;
}
}
复制代码还有一个常用的地方就是Toast。你应该知道和谁玩了吧。 3、匿名内部类/非静态内部类 这里有一张宝图:

public class TestActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
new MyAscnyTask().execute();
}
class MyAscnyTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
复制代码我们经常会用这个方法去异步加载,然后更新数据。貌似很平常,我们开始学这个的时候就是这么写的,没发现有问题啊,但是你这么想一想,MyAscnyTask是一个非静态内部类,如果他处理数据的时间很长,极端点我们用sleep 100秒,在这期间Activity可能早就关闭了,本来Activity的内存应该被回收的,但是我们知道非静态内部类会持有外部类的引用,所以Activity也需要陪着非静态内部类MyAscnyTask一起天荒地老。好了,内存泄漏就形成了。 怎么办呢? 既然MyAscnyTask的生命周期可能比较长,那就把它变成静态,和Application玩去吧,这样MyAscnyTask就不会再持有外部类的引用了。两者也相互独立了。
public class TestActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
new MyAscnyTask().execute();
}
//改了这里 注意一下 static
static class MyAscnyTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
复制代码说完非静态内部类,我再来看看匿名内部类,这个问题很常见,匿名内部类和非静态内部类有一个共同的地方,就是会只有外部类的强引用,所以这哥俩本质是一样的。但是处理方法有些不一样。但是思路绝对一样。换汤不换药。 举个灰常熟悉的栗子:
public class TestActivity extends Activity {
private TextView mText;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//do something
mText.setText(" do someThing");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
mText = findVIewById(R.id.mText);
// 匿名线程持有 Activity 的引用,进行耗时操作
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
mHandler. sendEmptyMessageDelayed(0, 100000);
}
复制代码想必这两个方法是我们经常用的吧,很熟悉,也是这么学的,没感觉不对啊,老师就是这么教的,通过我们上面的分析,还这么想吗?关键是 耗时时间过长,造成内部类的生命周期大于外部类,对弈非静态内部类,我们可以静态化,至于匿名内部类怎么办呢?一样把它变成静态内部类,也就是说尽量不要用匿名内部类。完事了吗?很多人不注意这么一件事,如果我们在handleMessage方法里进行UI的更新,这个Handler静态化了和Activity没啥关系了,但是比如这个mText,怎么说?全写是activity.mText,看到了吧,持有了Activity的引用,也就是说Handler费劲心思变成静态类,自认为不持有Activity的引用了,准确的说是不自动持有Activity的引用了,但是我们要做UI更新的时候势必会持有Activity的引用,静态类持有非静态类的引用,我们发现怎么又开始内存泄漏了呢?处处是坑啊,怎么办呢?我们这里就要引出弱引用的概念了。 引用分为强引用,软引用,弱引用,虚引用,强度一次递减。 强引用 我们平时不做特殊处理的一般都是强引用,如果一个对象具有强引用,GC宁可OOM也绝不会回收它。看出多强硬了吧。 软引用(SoftReference) 如果内存空间足够,GC就不会回收它,如果内存空间不足了,就会回收这些对象的内存。 弱引用(WeakReference) 弱引用要比软引用,更弱一个级别,内存不够要回收他,GC的时候不管内存够不够也要回收他,简直是弱的一匹。不过GC是一个优先级很低的线程,也不是太频繁进行,所以弱引用的生活还过得去,没那么提心吊胆。 虚引用 用的甚少,我没有用过,如果想了解的朋友,可以自行谷歌百度。 所以我们用弱引用来修饰Activity,这样GC的时候,该回收的也就回收了,不会再有内存泄漏了。很完美。
public class TestActivity extends Activity {
private TextView mText;
private MyHandler myHandler = new MyHandler(TestActivity.this);
private MyThread myThread = new MyThread();
private static class MyHandler extends Handler {
WeakReference<TestActivity> weakReference;
MyHandler(TestActivity testActivity) {
this.weakReference = new WeakReference<TestActivity>(testActivity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
weakReference.get().mText.setText("do someThing");
}
}
private static class MyThread extends Thread {
@Override
public void run() {
super.run();
try {
sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
mText = findViewById(R.id.mText);
myHandler.sendEmptyMessageDelayed(0, 100000);
myThread.start();
}
//最后清空这些回调 @Override protected void onDestroy() { super.onDestroy(); myHandler.removeCallbacksAndMessages(null); } 复制代码4、资源未关闭造成的内存泄漏
网络、文件等流忘记关闭 手动注册广播时,退出时忘记 unregisterReceiver() Service 执行完后忘记 stopSelf() EventBus 等观察者模式的框架忘记手动解除注册
这些需要记住又开就有关,具体做法也很简单就不一一赘述了。给大家介绍几个很好用的工具: 1、leakcanary傻瓜式操作,哪里有泄漏自动给你显示出来,很直接很暴力。 2、我们平时也要多使用Memory Monitor进行内存监控,这个分析就有些难度了,可以上网搜一下具体怎么使用。 3、Android Lint 它可以帮助我们发现代码机构 / 质量问题,同时提供一些解决方案,内存泄露的会飘黄,用起来很方便,具体使用方法上网学习,这里不多做说明了。 so 优化3.0 解决各个情况下的内存泄漏,注意平时代码的规范。
强引用(strong reference)
如:Object object=new Object(),object就是一个强引用了。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
软引用(SoftReference)
只有内存不够时才回收,常用于缓存;当内存达到一个阀值,GC就会去回收它;
弱引用(WeakReference)
弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它,所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
如果只是想避免OutOfMemory异常的发生,则可以使用软引用。如果对于应用的性能更在意,想尽快回收一些占用内存比较大的对象,则可以使用弱引用。
还有就是可以根据对象是否经常使用来判断。如果该对象可能会经常使用的,就尽量用软引用。如果该对象不被使用的可能性更大些,就可以用弱引用。 4、启动速度优化 app启动分为冷启动(Cold start)、热启动(Hot start)和温启动(Warm start)三种。 明确一点, IntentService不同于Service, 它是工作在后台线程的.
InitializeService.java代码如下:
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.widget.ImageView;
import com.anly.githubapp.common.wrapper.AppLog;
import com.anly.githubapp.common.wrapper.CrashHelper;
import com.anly.githubapp.common.wrapper.FeedbackPlatform;
import com.anly.githubapp.common.wrapper.ImageLoader;
import com.anly.githubapp.common.wrapper.PushPlatform;
import com.anly.githubapp.common.wrapper.SharePlatform;
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader;
import com.mikepenz.materialdrawer.util.DrawerImageLoader;
/**
* Created by mingjun on 16/8/25.
*/
public class InitializeService extends IntentService {
private static final String ACTION_INIT_WHEN_APP_CREATE = "com.anly.githubapp.service.action.INIT";
public InitializeService() {
super("InitializeService");
}
public static void start(Context context) {
Intent intent = new Intent(context, InitializeService.class);
intent.setAction(ACTION_INIT_WHEN_APP_CREATE);
context.startService(intent);
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_INIT_WHEN_APP_CREATE.equals(action)) {
performInit();
}
}
}
private void performInit() {
AppLog.d("performInit begin:" + System.currentTimeMillis());
// init Drawer image loader
DrawerImageLoader.init(new AbstractDrawerImageLoader() {
@Override
public void set(ImageView imageView, Uri uri, Drawable placeholder) {
ImageLoader.loadWithCircle(getApplicationContext(), uri, imageView);
}
});
// init crash helper
CrashHelper.init(this.getApplicationContext());
// init Push
PushPlatform.init(this.getApplicationContext());
// init Feedback
FeedbackPlatform.init(this.getApplication());
// init Share
SharePlatform.init(this.getApplicationContext());
AppLog.d("performInit end:" + System.currentTimeMillis());
}
} GithubApplication的onCreate改成:
public class GithubApplication extends MultiDexApplication {
@Override
public void onCreate() {
super.onCreate();
// init logger.
AppLog.init();
InitializeService.start(this);
}
} 优化4.0 Application的onCreate中不要做太多事情. 避免I/O操作、反序列化、网络操作、布局嵌套等。
我们如何得知我们自己的app启动花费了多少时间? 确保设备连接到电脑; 启动cmd窗口 输入如下命令:adb shell am start -W [app包名]/[launcherActivity的全类名] 如 adb shell am start -W com.bsoft.pub.androidsx/com.bsoft.module_account.activity.LoginActivity 注意:没有在AndroidManifest.xml对应的Activity声明中指定或者属性没有android:exported="true"的Activity不能使用这种命令行的形式计算启动时间。
