一、简介
在Android开发中,性能优化是软件开发中最难且也是知识面涉及最深、最广、最难的一个领域,更是开发一款优秀软件的必要条件。
更快:App流畅,响应速度快
更稳:App运行稳定,程序不崩溃(Crash)也不会无响应(ANR)
更省:节省资源,包括电量,内存,存储,流量等
二、内存优化
谈及内存优化,先来普及一下内存泄漏和内存溢出的知识:(熟悉的可以跳过)
内存泄漏 (memory leak):指程序在申请内存后,无法正常释放已申请的内存空间。程序在使用内存资源的时候,系统为程序开辟一段内存空间,程序使用完后忘记释放内存资源,这时内存资源一直被占用 (未被回收)。虚拟机宁愿抛出OOM,也不愿意去回收程序被占用的内存空间。
内存溢出(Out Of Memory,也叫OOM):系统在申请内存空间时,没有足够的内存空间供其使用。简单来说就是系统不能再为你分配更多的内存空间,比如你申请需要100M的内存空间,但是系统仅剩90M了,这时候就会内存溢出。举个例子,一个盘子最多只能装4个果子,你却装了5个果子,结果掉地上了,装不下了这就是溢出。(溢出会导致应用程序 Crash 崩溃)
内存泄漏的本质原因:本该被回收的对象没有被回,继续停留着内存空间中,导致内存被占用。其实是持有引用者的生命周期>被持有引用者的生命周期。过多内存泄漏会把内存空间占用完,最终会导致内存溢出。
从机制的角度来说,由于存在java垃圾回收机制(GC),理论上不存在内存泄漏,那么造成内存泄漏的绝大部分是外部认为因素,无意识持有引用。
常见的内存溢出和解决方案:
1.非静态内部类引用Activity的泄漏
在java中,非静态(匿名)内部类默认持有外部类的引用,而静态内部类不会持有外部类的引用。这种原因造成内存泄漏常见的三种情况:静态实例、多线程、Handler等。
(1)非静态内部类创建静态实例
非静态内部类创建静态实例会导致内存泄漏,通常项目中,通常在Activity启动的时候创建单例数据,避免重复创建相同的数据,会在Activity内部创建一个非静态内部类的静态实例。反例 :
public class NotStaticActivityextends AppCompatActivity {
//非静态内部类实例引用,设置为静态
private static InnerClass sInnerClass;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//保证非静态内部类实例只有一个
if (sInnerClass == null) {
sInnerClass = new InnerClass();
}
}
//非静态内部类
private class InnerClass {
//……
}
}
这样就在Activity里面创建了一个非静态内部类的静态实例InnerClass ,每次启动Activity都会使用该单例数据,这样做虽然避免了资源的重复创建,但是会造成内存泄漏,因为非静态内部类InnerClass 默认持有外部类Activity的引用,而该内部类InnerClass 又是一个静态的成员变量,则该实例的生命周期和应用的生命周期一样长,那么InnerClass 则会一直持有Activity的引用,导致Activity在销毁的时候无法被GC回收利用。
造成内存泄漏的原因:非静态外部类(InnerClass)的生命周期 = 应用的生命周期,而且持有外部类Activity的引用,在Activity在销毁的时候无法被GC回收,从而导致内存泄漏。
解决方法:1、将非静态内部类设置为静态内部类(静态内部类默认不持有外部类的引用)
//静态内部类
private static class InnerClass2 {
//……
}
解决方法:2、将外部类抽取出来封装成一个单例。(Java设计模式—单例模式)
public class InnerClass3 extends AppCompatActivity {
private static final String TAG = InnerClass3.class.getSimpleName();
private static InnerClass3 sInnerClass3;
//单例
public InnerClass3 getInstance() {
if (sInnerClass3 == null) {
sInnerClass3 = new InnerClass3();
}
return sInnerClass3;
}
}
多线程造成内存泄漏:我们知道线程类属性非静态(匿名)内部类,多线程的使用主要是AsyncTask、实现Runnable接口,继承Thread类,在使用线程类时需要注意内存泄漏。
(2)AsyncTask的使用
造成内存泄漏的原因:在AsyncTask里面处理耗时操作时,AsyncTask还没操作完就将AsyncTaskActivity退出,但是此时AsyncTask已然持有AsyncTaskActivity的引用,导致AsyncTaskActivity无法被回收处理。
解决办法:在使用AsyncTask的时候,在AsyncTaskActivity销毁时也取消AsyncTask相关的任务AsyncTask.cancel(),避免任务在后台浪费资源,避免内存泄漏。举个正例:
public class AsyncTaskActivity extends AppCompatActivity {
private AsyncTask<Void, Void, Void> mAsyncTask;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//AsyncTask执行任务
mAsyncTask = new AsyncTask<Void, Void, Void>() {
//开始之前的准备工作
@Override
protected void onPreExecute() {
super.onPreExecute();
}
//相当于子线程,耗时操作
@Override
protected Void doInBackground(Void... voids) {
return null;
}
//主线程显示进度
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
}
//相当于主线程,获取数据更新UI
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
}
};
//执行异步线程任务
mAsyncTask.execute();
}
@Override
protected void onDestroy() {
//强制退出AsyncTask
mAsyncTask.cancel(true);
super.onDestroy();
}
}
(3)Thread类使用
我们来看看Thread类的错误用法,反例:
public class ThreadActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//方式一:新建内部类
new MyThread().start();
//方式二:匿名Thread内部类
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
//自定义Thread
private class MyThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
上面两种创建Thread造成内存泄漏的原因:线程类Thread属于非静态内部类,它的生命周期比ThreadActivity的生命周期长,运行时默认持有外部类的引用,如果ThreadActivity需要销毁时,线程类Thread持有ThreadActivity的引用,导致ThreadActivity无法回收利用,造成内存泄漏。
解决方法:1、将线程类Thread修改为静态内部类,线程类Thread则不再持有外部类的引用,如下例子:
//自定义Thread,设置为静态内部类
private static class MyThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
解决方法:2、当外部类结束时,强制结束线程,使得线程类的生命周期与外部类的生命周期一致。注意:Thread.stop()停止线程方法已经过时,stop()停止线程会产生不可预期的错误,不建议使用,可以参考Thread线程停止的正确方式。
@Override
protected void onDestroy() {
//Thread.stop方法以及过时而且这样停止线程会产生不可预期的错误,
//mMyThread.stop();
super.onDestroy();
}
(4)Handler造成的内存泄漏
Handler使用不当也会造成内存泄漏,我们来看看反例:
public class HandlerActivity extends AppCompatActivity {
private Handler mHandler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//创建Handler
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//接收信息
switch (msg.what) {
case 1:
Log.e(TAG, "Handler==" + msg.obj);
break;
default:
break;
}
}
};
//创建线程模拟发送数据
new Thread(new Runnable() {
@Override
public void run() {
//封装信息数据
Message message = Message.obtain();
message.what = 1;
message.obj = "Handler使用";
//Handler发送信息
mHandler.sendMessage(message);
}
}).start();
}
}
原因分析:通过内部类创建的mHandler对象,会隐式持有Activity的引用(HandlerActivity),当mHandler.sendMessage(msg)时,最后会将mHander装入到Message中,并把这条Message推送到MessageQueque中,MessageQueque是在一个Looper线程中不断轮询处理消息的,如果Activity销毁时,handler消息队列中还有没处理或者正在处理消息,而Message持有Handler的引用(Android Handler消息机制使用和原理),Handler又持有Activity的引用,所以导致Activity无法被回收利用。
我们用图解说明一下:
解决方法:1、当外部类结束生命周期时,清空Handler内消息队列:
@Override
protected void onDestroy() {
//当外部类结束生命周期时,清空Handler内消息队列;
mHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
解决方法:2、将内名内部类改为静态内部类,并对上下文或者Activity使用弱引用。
//将匿名内部类改为静态内部类,并对上下文或者Activity使用弱引用。
private static class MyHandler extends Handler{
//定义弱引用实例
WeakReference<Activity> mWeakReference;
public MyHandler(HandlerActivity activity) {
//构造方法中将Activity使用弱引用
mWeakReference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (mWeakReference.get() != null){
//UI更新
}
}
}
静态static可以解决内存泄漏问题,使用弱引用也可以解决内存泄漏,但是需要等到Handler中的任务执行完才释放Activity,没有直接static释放得快。
2.2 static关键字修饰的成员变量
(1)静态变量的单例模式
我们知道被static修饰的成员变量的生命周期等于app应用程序的生命周期,如果静态成员变量持有Activity的引用,则会静态成员变量的生命周期大于Activity的引用的生命周期,当Activity的引用需要销毁回收利用时,导致静态成员变量持有Activity的引用而无法回收,从而导致内存泄漏。这里有一个非常典型的例子:单例模式(Java设计模式—单例模式)
public class SingleInstanceClass extends AppCompatActivity {
private static final String TAG = SingleInstanceClass.class.getSimpleName();
private Context mContext;
private static SingleInstanceClass sInstanceClass;
//构造方法传入Activity的引用
public SingleInstanceClass(Context context) {
mContext = context;
}
//单例方式获取实例
public static SingleInstanceClass getInstance(Context context) {
if (sInstanceClass == null) {
sInstanceClass = new SingleInstanceClass(context);
}
return sInstanceClass;
}
}
在开发中单例经常需要持有Context的实例,如上面代码,如果一个Activity调用getInstance(Context context)方法,则在Activity销毁时SingleInstanceClass仍然持有Activity的引用,导致Activity无法回收利用,出现内存泄漏。
解决办法:1、保证Context的生命周期与应用的生命周期一致
public SingleInstanceClass(Context context) {
mContext = context.getApplicationContext();
}
解决办法:2、使用弱引用代替强引用持有实例
public SingleInstanceClass(Context context) {
//弱引用
WeakReference<Context> weakReference = new WeakReference<>(context);
mContext = weakReference.get();
}
(2)错误使用静态变量
使用静态方法是十分方便的,但是创建的对象建议不要全局化,全局话变量必须加上static,全局化的变量或者对象会造成内存泄漏。
2.3 未移除监听
对于监听类相关资源需要在Activity销毁时进行注销和回收,否则导致资源不回收造成内存泄漏。
(1)广播监听
public class LisenterActivity extends AppCompatActivity {
private static final String TAG = LisenterActivity.class.getSimpleName();
private MyReceiver mMyReceiver;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//1.自定义广播
mMyReceiver = new MyReceiver();
//创建过滤器,增加手势参数Action
IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
//动态注册广播
registerReceiver(mMyReceiver, filter);
}
//自定义广播接收类
private class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//对接收到的广播进行处理,intent里面包含数据
}
}
@Override
protected void onDestroy() {
//正例
//对于监听类相关资源需要在Activity销毁时进行注销和回收,否则导致资源不回收造成内存泄漏。
//解除广播
unregisterReceiver(mMyReceiver);
super.onDestroy();
}
}
在Activity销毁的时候unregisterReceiver()解除广播,回收资源。
(2)add监听(addOnWindowFocusChangeListener)
public class ListenerActivity extends AppCompatActivity implements ViewTreeObserver.OnWindowFocusChangeListener{
private static final String TAG = ListenerActivity.class.getSimpleName();
private TextView mTextView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTextView = new TextView(this);
//监听执行完回收对象,不用考虑内存泄漏
//textView.setOnClickListener(null);
//add监听,放到集合里面,需要考虑内存泄漏
mTextView.getViewTreeObserver().addOnWindowFocusChangeListener(this);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
//监听View的加载,加载处理计算他的宽高
}
@Override
protected void onDestroy() {
//正例
//对于监听类相关资源需要在Activity销毁时进行注销和回收,否则导致资源不回收造成内存泄漏。
//解除控件监听
mTextView.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
super.onDestroy();
}
}
在Activity销毁的时候解除监听,回收资源。
2.4 相关资源未关闭
对于资源的使用,在Activity销毁或者不需要的场景时,需要手动关闭/注销这些资源,否则这些资源不会被回收。
(1)动画相关资源
在动画结束或不需要动画的时候或在Activity销毁的时候结束并回收动画。
private void startAnimation() {
mAnimator = ObjectAnimator.ofFloat(mTextView, "rotationY", 0, 360);
mAnimator.setRepeatCount(ValueAnimator.INFINITE);
mAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
//动画结束时,清除控件动画并退出动画
mTextView.clearAnimation();
mAnimator.cancel();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
}
@Override
protected void onDestroy() {
//清除控件动画并退出动画
mTextView.clearAnimation();
mAnimator.cancel();
super.onDestroy();
}
动画中有无限循环的动画,动画播放时,Activity会被View所持有,从而导致Activity无法被释放,需要cancel()退出动画。
(2)IO流相关类
在使用IO流,File文件类或者Sqlite、Cursor等资源时要及时关闭,这些资源在读写操作时一般都行了缓冲,如果不及时关闭,这些缓冲对象就会一直被占用得不到释放,以致内存泄漏,所以在在它们不需要使用时,及时关闭,缓冲释放资源,避免内存泄漏。
//IO流使用完毕后及时关闭
InputStream stream = null;
try {
stream = new FileInputStream("name");
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
try {
//关闭流
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
注意:关闭语句必须要finally中关闭,否则可能因为异常未关闭导致资源,导致Activity泄漏。
(3)游标cursor
//查询数据库,返回Cursor
Cursor cursor = db.query(SQLiteHelper.TABLE_NAME, COLUMNS, key + " = ?", new String[]{values}, null, null, null);
cursor.close();
对于数据库游标Cursor,使用完毕后关闭。
(4)Bitmap类
//Bitmap 使用完毕后回收
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
bitmap.recycle();
bitmap = null;
对于图片资源Bitmap,Andorid分配给图片的资源只有8M,如果1个Bitmap对象占用资源较多时,当不再使用时应该recycle()回收对象像素所占用的内存,最后赋值为Null。
(5)集合类
项目中我们常常把一些对象加入到集合中,如果不需要这些对象时,如果不把他们从集合中清理掉,那么GC就无法回收该对象。如果集合声明为静态的话,泄漏就更严重了,所以在不需要使用的时候将对象从集合中移除
ArrayList<String> arrayList = new ArrayList();
for (int i = 0; i < 10; i++) {
String s = new String();
arrayList.add(s);
s = null;
}
//虽然释放了对象本身:s=null;但是集合list仍然引用这该对象,导致GC无法回收该对象
arrayList.clear();
arrayList = null;
内存优化总结:
三、布局优化
3.1 Android组件是如何处理UI组件的更新操作的
Android中通常把布局文件写在XML中,Android需要把XML布局文件转换成GPU能够识别并绘制的对象,CPU负责把UI组件计算成Polygons,Texture纹理,然后交给GPU进行栅格化渲染,最后硬件将信息绘制到屏幕上。
为了使APP流畅,我们需要在每一帧16ms内完成所有的CPU与GPU的计算、绘制、渲染等操作,也就是帧率为60fps,为什么要帧率为60fps呢?因为人眼与大脑之间的协作无法感知超过60fps的画面更新,开发APP的性能目标就是保持60fps,这意味着每一帧只有16ms(1000/60)的时间来处理所有任务
但是我们遇到很多帧率小于刷新频率,这种情况下,某些帧显示的画面内容就会与上一帧的相同,帧率如果从高于60fps突然掉落到低于60fps就会发生卡顿不顺滑的情况。这也是用户感受卡顿的原因所在
在View第一次被渲染时,DisplayList就会被构建,View显示到屏幕上时,会执行GPU的指令来进行渲染,如果后续还有移动位置等操作时再次渲染这个View时,仅仅额外执行一次渲染指令就可以了。但是如果修改了View中某些可见组件,DisplayList就无法再次使用,需要重新构建再渲染。任何View中的绘制内容发生变化时,都会执行DisplayList构建和渲染,更新到屏幕等一系列操作,这个流程表现的性能取决于View的复杂程度,View的状态变化以及View渲染管道的执行力。所以我们需要尽量减少过渡绘制(OverDraw)。
综上,针对页面布局的性能,层级,测量绘制时间进行优化,所以布局优化就是减少层级,减少OverDraw,性能耗费少,越简单越好。
3.2 布局优化的方案
布局优化的思想就是尽量减少布局文件的层级。布局性能的好坏直接影响Android应用中页面的响应速度,布局太过于复杂,层级嵌套太深导致绘制操作耗时,而且增加内存的消耗,优化的思路主要有以下几种方案:
我们用图表总结一下:
(1)选择耗费性能少的布局
如果FrameLayout,LinearLayout,RelativeLayout都能实现效果的情况下,优先选择那种布局?
从源码上来看FrameLayout,LinearLayout,RelativeLayout都是ViewGroup的子类,FrameLayout的代码量最少,逻辑最少,子View只和父类存在关系。LinearLayout,RelativeLayout都涉及子类的计算,这样看来FrameLayout似乎是性能最好的;LinearLayout会让子View调用一次onMeasure,如果存在weight(权重)的时候回调用两次onMeasure(会增加一倍的耗时),RelativeLayout会让子View调用两次onMeasure。LinearLayout比RelativeLayout绘制时间更短。
所以在不影响层级深度的情况下,尽量使用FrameLayout和LinearLayout,如果布局比较复杂则使用RelativeLayout。优先级:FrameLayout>LinearLayout>RelativeLayout。
注意:
1.如果需要用到两个LinearLayout,则使用一个RelativeLayout替换;
2.RelativeLayout子View与RelativeLayout高度不同时,会引发效率问题,当自View复杂时,这个问题更严重,如果可以尽量使用padding代替margin;
3.但是在约束布局未出来之前,为什么默认的是RelativeLayout呢?因为当遇到复杂的布局时,RelativeLayout相对LinearLayout能减少布局层级,减少过多父布局,在findviewbyid()查找控件上RelativeLayout比LinearLayout更省时间。
(2)提高布局的复用性
使用<incloude>来重用布局,抽取通用的公共部分让其他布局使用,让布局更清晰。通过布局的复用性,从而减少测量/绘制时间。如果布局比较复杂且行数较多也可以抽取一部分出来,使布局不那么臃肿。
具体操作方法:选中你想要抽取的布局—鼠标右键—Refactor—Extract-Layout…—输入文件名
activity_include.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:gravity="center"
android:orientation="vertical"
android:layout_height="match_parent">
<!--include标签抽取公共部分的布局-->
<include layout="@layout/include_btn"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:background="#9C27B0"
android:padding="8dp"
android:text="布局优化2"/>
</LinearLayout>
include_btn.xml
<?xml version="1.0" encoding="utf-8"?>
<!--include标签抽取公共部分的布局-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:background="#E91E63"
android:padding="8dp"
android:text="布局优化-include-1"/>
</LinearLayout>
include标签允许布局引入另一个布局,这里将一个Button抽取到一个布局中,方便相同的UI和可以复用该布局。
(3)减少布局的层级
使用<merge>标签引用布局,能减少布局层级,从而减少绘制时间,提高性能。merge标签作为include标签的拓展使用,主要是为了防止引用布局文件时产生多于的布局嵌套,在上面的例子中,include标签抽取的布局文件中多了一个LinearLayout的父布局,其实它是可以不存在的。下面使用merge标签进行改动
activity_include.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<!--include标签抽取公共部分的布局
它的布局层级:LinearLayout-> Button
-> LinearLayout ->Button
-->
<include layout="@layout/include_btn"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:background="#9C27B0"
android:padding="8dp"
android:text="布局优化2"/>
<!--merge标签
它的布局层级:LinearLayout-> Button
-> merge ->Button
-->
<include layout="@layout/include_merge_layout"/>
</LinearLayout>
include_merge_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<!--merge标签-->
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:background="#FF9800"
android:padding="8dp"
android:text="布局优化-merge1"/>
</merge>
merge优化将布局include_merge_layout.xml的根标签<LinearLayout>改为<merge>,去掉了多于的无意义的<LinearLayout>这个根节点
(4)减少初次绘制/测量时间
通常情况我们需要在某种条件下使用某种布局通过Gone和Invisible来隐藏布局,但是界面显示的时候还是会实例化的,使用ViewStub标签可以避免内存浪费,加速渲染速度,其实ViewStub就是一个宽高都为0的View,它默认是不可见的,通过setVisibility()或者inflate()才会将目标加载出来,从而达到延迟加载的效果。<ViewStub>标签最大的有点就是你在需要的时候才会加载,使用它并不会影响UI初始化的性能。
final ViewStub viewStub = findViewById(R.id.view_stub);
//延迟2秒后显示viewStub布局
viewStub.postDelayed(new Runnable() {
@Override
public void run() {
//加载显示
viewStub.inflate();
//viewStub.setVisibility(View.VISIBLE);
}
}, 2000);
activity_include.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<ViewStub
android:id="@+id/view_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout="@layout/layout_viewstub"/>
</LinearLayout>
layout_viewstub.xml
<?xml version="1.0" encoding="utf-8"?>
<!--ViewStub标签在需要的时候才显示布局-->
<Button
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#3F51B5"
android:padding="8dp"
android:text="布局优化-ViewStub"/>
这里ViewStub标签,在延迟2秒后layout_viewstub布局通过infalte()显示出来,效果如下:
- 注意:1.ViewStub布局里面不能使用<merge>标签,否则会报错;
- 2.ViewStub的inflate()只能执行一次,显示之后就再不能使用ViewStub控制他了。
(5)尽可能少使用wrap_content属性
---------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------