Android性能优化(安卓性能优化)内存泄露 | 内存优化 | UI绘制优化 | 布局优化 | 图片加载 | 多线程优化 | 列表优化 | apk优化

一、简介

在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属性

---------------------------------------------------------------------------------------------------------------------------------

Android入门到精通学习资料全套(安卓系统化进阶学习)

---------------------------------------------------------------------------------------------------------------------------------

Android入门到精通学习资料全套(安卓系统化进阶学习)

---------------------------------------------------------------------------------------------------------------------------------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值