深入探索Android布局优化(下)

下面,笔者将与大家一起进入进行布局优化的实操环节。

六、布局优化常规方案

布局优化的方法有很多,大部分主流的方案笔者已经在Android性能优化之绘制优化里讲解过了。下面,我将介绍一些其它的优化方案。

1、布局Inflate优化方案演进
1、代码动态创建View

使用Java代码动态添加控件的简单示例如下:

Button button=new Button(this);        
button.setBackgroundColor(Color.RED);
button.setText("Hello World");
ViewGroup viewGroup = (ViewGroup) LayoutInflater.from(this).inflate(R.layout.activity_main, null);
viewGroup.addView(button); 
2、替换MessageQueue来实现异步创建View

在使用子线程创建视图控件的时候,我们可以把子线程Looper的MessageQueue替换成主线程的MessageQueue,在创建完需要的视图控件后记得将子线程Looper中的MessageQueue恢复为原来的。在Awesome-WanAndroid项目下的UiUtils的Ui优化工具类中,提供了相应的实现,代码如下所示:

 /**
 * 实现将子线程Looper中的MessageQueue替换为主线程中Looper的
 * MessageQueue,这样就能够在子线程中异步创建UI。
 *
 * 注意:需要在子线程中调用。
 *
 * @param reset 是否将子线程中的MessageQueue重置为原来的,false则表示需要进行替换
 * @return 替换是否成功
 */
public static boolean replaceLooperWithMainThreadQueue(boolean reset) {
    if (CommonUtils.isMainThread()) {
        return true;
    } else {
        // 1、获取子线程的ThreadLocal实例
        ThreadLocal<Looper> threadLocal = ReflectUtils.reflect(Looper.class).field("sThreadLocal").get();
        if (threadLocal == null) {
            return false;
        } else {
            Looper looper = null;
            if (!reset) {
                Looper.prepare();
                looper = Looper.myLooper();
                // 2、通过调用MainLooper的getQueue方法区获取主线程Looper中的MessageQueue实例
                Object queue = ReflectUtils.reflect(Looper.getMainLooper()).method("getQueue").get();
                if (!(queue instanceof MessageQueue)) {
                    return false;
                }
                // 3、将子线程中的MessageQueue字段的值设置为主线的MessageQueue实例
                ReflectUtils.reflect(looper).field("mQueue", queue);
            }

            // 4、reset为false,表示需要将子线程Looper中的MessageQueue重置为原来的。
            ReflectUtils.reflect(threadLocal).method("set", looper);
            return true;
        }
    }
} 
3、AsynclayoutInflater异步创建View

在第三小节中,我们对Android的布局加载原理进行了深入地分析,从中我们得出了布局加载过程中的两个耗时点:

  • 1、布局文件读取慢:IO过程。
  • 2、创建View慢:使用反射,比直接new的方式要慢3倍。布局嵌套层级越多,控件个数越多,反射的次数就会越频繁。

很明显,我们无法从根本上去解决这两个问题,但是Google提供了一个从侧面解决的方案:使用AsyncLayoutInflater去异步加载对应的布局,它的特点如下:

  • 1、工作线程加载布局。
  • 2、回调主线程。
  • 3、节省主线程时间。

接下来,我将详细地介绍AsynclayoutInflater的使用。

首先,在项目的build.gradle中进行配置:

implementation 'com.android.support:asynclayoutinflater:28.0.0' 

然后,在Activity中的onCreate方法中将setContentView注释:

super.onCreate(savedInstanceState);
// 内部分别使用了IO和反射的方式去加载布局解析器和创建对应的View
// setContentView(R.layout.activity_main); 

接着,在super.onCreate方法前继续布局的异步加载:

// 使用AsyncLayoutInflater进行布局的加载
new AsyncLayoutInflater(MainActivity.this).inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
        @Override
        public void onInflateFinished(@NonNull View view, int i, @Nullable ViewGroup viewGroup) {
            setContentView(view);
            // findViewById、视图操作等
    }
});
super.onCreate(savedInstanceState); 

接下来,我们来分析下AsyncLayoutInflater的实现原理与工作流程。

由于我们是使用new的方式创建的AsyncLayoutInflater,所以我们先来看看它的构造函数:

 public AsyncLayoutInflater(@NonNull Context context) {
    // 1
    this.mInflater = new AsyncLayoutInflater.BasicInflater(context);
    // 2
    this.mHandler = new Handler(this.mHandlerCallback);
    // 3
    this.mInflateThread = AsyncLayoutInflater.InflateThread.getInstance();
} 

在注释1处,创建了一个BasicInflater,它内部的onCreateView并没有使用Factory做AppCompat控件兼容的处理:

protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
    String[] var3 = sClassPrefixList;
    int var4 = var3.length;

    for(int var5 = 0; var5 < var4; ++var5) {
        String prefix = var3[var5];

        try {
            View view = this.createView(name, prefix, attrs);
            if (view != null) {
                return view;
            }
        } catch (ClassNotFoundException var8) {
        }
    }

    return super.onCreateView(name, attrs);
} 

由前面的分析可知,在createView方法中仅仅是做了反射创建出对应View的处理。

接着,在注释2处,创建了一个全局的Handler对象,主要是用于将异步线程创建好的View实例及其相关信息回调到主线程。

最后,在注释3处,获取了一个用于异步加载View的线程实例。

接着,我们继续跟踪AsyncLayoutInflater实例的inflate方法:

@UiThread
public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent, @NonNull AsyncLayoutInflater.OnInflateFinishedListener callback) {
    if (callback == null) {
        throw new NullPointerException("callback argument may not be null!");
    } else {
        // 1
        AsyncLayoutInflater.InflateRequest request = this.mInflateThread.obtainRequest();
        request.inflater = this;
        request.resid = resid;
        request.parent = parent;
        request.callback = callback;
        this.mInflateThread.enqueue(request);
    }
} 

在注释1处,这里使用InflateRequest对象将我们传进来的三个参数进行了包装,并最终将这个InflateRequest对象加入了mInflateThread线程中的一个ArrayBlockingQueue中:

public void enqueue(AsyncLayoutInflater.InflateRequest request) {
    try {
        this.mQueue.put(request);
      } catch (InterruptedException var3) {
        throw new RuntimeException("Failed to enqueue async inflate request", var3);
    }
} 

并且,在InflateThread这个静态内部类的静态代码块中调用了其自身实例的start方法以启动线程:

static {
    sInstance.start();
}

public void run() {
    while(true) {
        this.runInner();
    }
}

public void runInner() {
    AsyncLayoutInflater.InflateRequest request;
    try {
        // 1
        request = (AsyncLayoutInflater.InflateRequest)this.mQueue.take();
    } catch (InterruptedException var4) {
        Log.w("AsyncLayoutInflater", var4);
        return;
    }

    try {
        // 2
        request.view = request.inflater.mInflater.inflate(request.resid, request.parent, false);
    } catch (RuntimeException var3) {
        Log.w("AsyncLayoutInflater", "Failed to inflate resource in the background! Retrying on the UI thread", var3);
    }

    // 3
    Message.obtain(request.inflater.mHandler, 0, request).sendToTarget();
} 

在run方法中,使用了死循环的方式去不断地调用runInner方法,在runInner方法中,首先在注释1处从ArrayBlockingQueue队列中获取一个InflateRequest对象,然后在注释2处将异步加载好的view对象赋值给了InflateRequest对象,最后,在注释3处,将请求作为消息发送给了Handler的handleMessage:

private Callback mHandlerCallback = new Callback() {
    public boolean handleMessage(Message msg) {
        AsyncLayoutInflater.InflateRequest request = (AsyncLayoutInflater.InflateRequest)msg.obj;
        // 1
        if (request.view == null) {
            request.view = AsyncLayoutInflater.this.mInflater.inflate(request.resid, request.parent, false);
        }

        request.callback.onInflateFinished(request.view, request.resid, request.parent);
        AsyncLayoutInflater.this.mInflateThread.releaseRequest(request);
        return true;
    }
}; 

在handleMessage方法中,当异步加载得到的view为null时,此时在注释1处还做了一个fallback处理,直接在主线程进行view的加载,以此兼容某些异常情况,最后,就调用了回调接口的onInflateFinished方法将view的相关信息返回给Activity对象。

小结

由以上分析可知,AsyncLayoutInflater是通过侧面缓解的方式去缓解布局加载过程中的卡顿,但是它依然存在一些问题:

  • 1、不能设置LayoutInflater.Factory,需要通过自定义AsyncLayoutInflater的方式解决,由于它是一个final,所以需要将代码直接拷处进行修改。
  • 2、因为是异步加载,所以需要注意在布局加载过程中不能有依赖于主线程的操作。

由于AsyncLayoutInflater仅仅只能通过侧面缓解的方式去缓解布局加载的卡顿,因此,我们下面将介绍一种从根本上解决问题的方案。对于AsynclayoutInflater的改进措施,可以查看祁同伟同学封装之后的代码,具体的改进分析可以查看Android AsyncLayoutInflater 限制及改进,这里附上改进之后的代码:

/**
* 实现异步加载布局的功能,修改点:
* 1. 单一线程;
* 2. super.onCreate之前调用没有了默认的Factory;
 * 3. 排队过多的优化;
*/
public class AsyncLayoutInflaterPlus {

    private static final String TAG = "AsyncLayoutInflaterPlus";
    private Handler mHandler;
    private LayoutInflater mInflater;
    private InflateRunnable mInflateRunnable;
    // 真正执行加载任务的线程池
    private static ExecutorService sExecutor = Executors.newFixedThreadPool(Math.max(2,
        Runtime.getRuntime().availableProcessors() - 2));
    // InflateRequest pool
    private static Pools.SynchronizedPool<AsyncLayoutInflaterPlus.InflateRequest> sRequestPool = new Pools.SynchronizedPool<>(10);
    private Future<?> future;

    public AsyncLayoutInflaterPlus(@NonNull Context context) {
        mInflater = new AsyncLayoutInflaterPlus.BasicInflater(context);
        mHandler = new Handler(mHandlerCallback);
    }

    @UiThread
    public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent, @NonNull CountDownLatch countDownLatch,
                    @NonNull AsyncLayoutInflaterPlus.OnInflateFinishedListener callback) {
        if (callback == null) {
            throw new NullPointerException("callback argument may not be null!");
        }
        AsyncLayoutInflaterPlus.InflateRequest request = obtainRequest();
        request.inflater = this;
        request.resid = resid;
        request.parent = parent;
        request.callback = callback;
        request.countDownLatch = countDownLatch;
        mInflateRunnable = new InflateRunnable(request);
        future = sExecutor.submit(mInflateRunnable);
    }

    public void cancel() {
        future.cancel(true);
    }

    /**
    * 判断这个任务是否已经开始执行
    *
    * @return
    */
    public boolean isRunning() {
        return mInflateRunnable.isRunning();
    }

    private Handler.Callback mHandlerCallback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            AsyncLayoutInflaterPlus.InflateRequest request = (AsyncLayoutInflaterPlus.InflateRequest) msg.obj;
            if (request.view == null) {
                request.view = mInflater.inflate(
                    request.resid, request.parent, false);
            }
            request.callback.onInflateFinished(
                request.view, request.resid, request.parent);
            request.countDownLatch.countDown();
            releaseRequest(request);
            return true;
        }
    };

    public interface OnInflateFinishedListener {
        void onInflateFinished(View view, int resid, ViewGroup parent);
    }

    private class InflateRunnable implements Runnable {
        private InflateRequest request;
        private boolean isRunning;

        public InflateRunnable(InflateRequest request) {
            this.request = request;
        }

        @Override
        public void run() {
            isRunning = true;
            try {
                request.view = request.inflater.mInflater.inflate(
                    request.resid, request.parent, false);
            } catch (RuntimeException ex) {
                // Probably a Looper failure, retry on the UI thread
                Log.w(TAG, "Failed to inflate resource in the background! Retrying on the UI"
                    + " thread", ex);
            }
            Message.obtain(request.inflater.mHandler, 0, request)
                .sendToTarget();
        }

        public boolean isRunning() {
            return isRunning;
        }
    }

    private static class InflateRequest {
        AsyncLayoutInflaterPlus inflater;
        ViewGroup parent;
        int resid;
        View view;
        AsyncLayoutInflaterPlus.OnInflateFinishedListener callback;
        CountDownLatch countDownLatch;

        InflateRequest() {
        }
    }

    private static class BasicInflater extends LayoutInflater {
        private static final String[] sClassPrefixList = {
                "android.widget.",
                "android.webkit.",
                &
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: Android Framework是指Android操作系统中提供的一套API和工具集合,可以帮助开发者快速构建Android应用程序。底层开发主要涉及到Android Framework层的源代码和相关文档,本质上是深入了解Android操作系统原理和架构,具有较高的技术门槛。 探索Android Framework底层开发可以帮助开发者更好地理解Android的运行原理,提高代码质量和效率。具体来说,需要掌握以下几个方面: 1. Java编程语言:Android开发是基于Java的,需要掌握Java的语法和开发环境。 2. Android系统架构:了解Android系统的四层架构是必要的,包括应用层、框架层、系统运行库和Linux内核。 3. Android Framework层的API:掌握常用的Android Framework层的API,如Activity、Service、BroadcastReceiver等。 4. Android框架源代码:深入了解Android框架层的源码实现,可以帮助开发者更好地掌握Android系统的工作机制和运行原理。 5. Android开发工具:熟练掌握Android Studio等开发工具,可以提高Android开发的效率和质量。 总之,探索Android Framework底层开发是一个深入了解Android操作系统原理和架构的过程,需要不断学习和实践才能不断提高自己的技术水平。 ### 回答2: Android 底层开发需要具备一定的计算机基础知识和编程技能。Android 应用程序由 Android Framework 和 Android 应用程序层组成,Android Framework 包含了很多核心组件和类库,例如 Activity、Service 和 ContentProvider 等。 若要深入探究 Android Framework,我们需要从底层的 Linux 内核开始了解。因为 Android 基于 Linux 内核设计,所以我们需要了解 Linux 内核的结构和原理,熟悉 Linux 的命令行操作。接着,我们需要掌握 Java 编程语言的基础知识,了解 Android 的四大组件和应用开发模式。 此外,我们还需要了解 Android Framework 最核心部分的 Binder 服务。Binder 运行在 Linux 内核空间中,用于在 Android 应用程序层和 Android Framework 层之间传递信息。理解 Binder 的运作原理将对深入理解 Android Framework 有很大帮助。 探索 Android Framework 底层开发需要不断学习和实践。建议参考 Android 官方文档、查阅相关书籍和博客,积极参与开源社区和论坛,不断提高自己的技能和水平。只有积累了足够的经验和技能,才能够开发出高质量的 Android 应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值