补:《Android面试题思考与解答》2021年1月刊(二)

简介: 今年最后一篇,《Android面试题思考与解答21年1月刊》送给大家。

在java有Serializable的前提下,Android为什么设计出了Parcelable?


java中的序列化方式Serializable效率比较低,主要有以下原因:


  • Serializable在序列化过程中会创建大量的临时变量,这样就会造成大量的GC。
  • Serializable使用了大量反射,而反射操作耗时。
  • Serializable使用了大量的IO操作,也影响了耗时。


所以Android就像重新设计了IPC方式Binder一样,重新设计了一种序列化方式,结合Binder的方式,对上述三点进行了优化,一定程度上提高了序列化和反序列化的效率。


Serializable、Parcelable、Json等序列化方式我们该怎么选择?


先说说序列化的用处,主要用在三个方面:


1、内存数据传输


内存传输方面,主要用Parcelable。一是因为Parcelable在内存传输的效率比Serializable高。二是因为在Android中很多传输数据的方法中,自带了对于Serializable、Parcelable类型的传输方法。比如:


  • Bundle.putParcelable,
  • Intent putExtra(String name, Parcelable value)


等等吧,基本上对象传输的方法都支持了,所以这也是Parcelable的优势。


2、 数据持久化(本地存储)


如果只针对Serializable和Parcelable两种序列化方式,需要选择Serializable。


首先,Serializable本身就是存储到二进制文件,所以用于持久化比较方便。而Parcelable序列化是在内存中操作,如果进程关闭或者重启的时候,内存中的数据就会消失,那么Parcelable序列化用来持久化就有可能会失败,也就是数据不会连续完整。而且Parcelable还有一个问题是兼容性,每个Android版本可能内部实现都不一样,知识用于内存中也就是传递数据的话是不影响的,但是如果持久化可能就会有问题了,低版本的数据拿到高版本可能会出现兼容性问题。


但是实际情况,对于Android中的对象本地化存储,一般是以数据库、SP的方式进行保存。


3、 网络传输


而对于网络传输的情况,一般就是使用JSON了。主要有以下几点原因:


  • 1、轻量级,没有多余的数据。
  • 2、与语言无关,所以能兼容所有平台语言。
  • 3、易读性,易解析。


Parcelable一定比Serializable快吗?


正常情况下,对象在内存中进行传输确实是Parcelable比较快,但是Serializable是有缓存的概念的,有人做了一个比较有趣的实验:


当序列化一个超级大的对象图表(表示通过一个对象,拥有通过某路径能访问到其他很多的对象),并且每个对象有10个以上属性时,并且Serializable实现了writeObject()以及readObject(),在平均每台安卓设备上,Serializable序列化速度大于Parcelable 3.6倍,反序列化速度大于1.6倍.


具体原因就是因为Serilazable的实现方式中,是有缓存的概念的,当一个对象被解析过后,将会缓存在HandleTable中,当下一次解析到同一种类型的对象后,便可以向二进制流中,写入对应的缓存索引即可。但是对于Parcel来说,没有这种概念,每一次的序列化都是独立的,每一个对象,都当作一种新的对象以及新的类型的方式来处理。


具体过程可以看看这篇:https://juejin.cn/post/6854573218334769166


为什么Java提供了Serializable的序列化方式,而不是直接使用json或者xml?


我觉得是历史遗留问题。


有的人可能会想到各种理由,比如可以标记哪些类可以被序列化。又或者可以通过UID来标示反序列化为同一个对象。等等。


但是我觉得最大的问题还是历史遗留问题,在以前,json还没有成为大家认同的数据结构,所以Java就设计出了Serializable的序列化方式来解决对象持久化和对象传输的问题。然后Java中各种API就会依赖于这种序列化方式,这么些年过去了,Java体系的庞大也造成难以改变这个问题,牵一发而动全身。


为什么我这么说呢?


主要有两点依据:



  • 二是因为在Serializable类的介绍注释中,明确说到推荐大家选择JSON 和 GSON库,因为它简洁、易读、高效。


* <h3>Recommended Alternatives</h3>
 * <strong>JSON</strong> is concise, human-readable and efficient. Android
 * includes both a {@link android.util.JsonReader streaming API} and a {@link
 * org.json.JSONObject tree API} to read and write JSON. Use a binding library
 * like <a href="http://code.google.com/p/google-gson/">GSON</a> to read and
 * write Java objects directly.


Window是什么


窗口。你可以理解为手机上的整个画面,所有的视图都是通过Window呈现的,比如Activity、dialog都是附加在Window上的。Window类的唯一实现是PhoneWindow,这个名字就更加好记了吧,手机窗口呗。


那Window到底在哪里呢?我们看到的View是Window吗?是也不是。

如果说的只是Window概念的话,那可以说是的,View就是Window的存在形式,Window管理着View。


如果说是Window类的话,那确实不是View,唯一实现类PhoneWindow管理着当前界面上的View,包括根布局——DecorView,和其他子view的添加删除等等。


不知道你晕没有,我总结下,Window是个概念性的东西,你看不到他,如果你能感知它的存在,那么就是通过View,所以View是Window的存在形式,有了View,你才感知到View外层有一个皇帝的新衣——window。


WindowManager是什么?和WMS的关系?


WindowManager就是用来管理Window的,实现类为WindowManagerImpl,实际工作会委托给WindowManagerGlobal类中完成。


而具体的Window操作,WM会通过Binder告诉WMS,WMS做最后的真正操作Window的工作,会为这个Window分配Surface,并绘制到屏幕上。


怎么添加一个Window?


var windowParams: WindowManager.LayoutParams = WindowManager.LayoutParams()
    windowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    windowParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG
    var btn = Button(this)
    windowManager.addView(btn, windowParams)


简单贴了下代码,加了一个Button。


有的朋友可能会疑惑了,这明明是个Button,是个View啊,咋成了Window?


刚才说过了,View是Window的表现形式,在实际实现中,添加window其实就是添加了一个你看不到的window,并且里面有View才能让你感觉得到这个是一个Window。


所以通过windowManager添加的View其实就是添加Window的过程。


这其中还有两个比较重要的属性:flags和type,下面会依次说到。


Window怎样可以显示到锁屏界面


Window的flag可以控制Window的显示特性,也就是该怎么显示、touch事件处理、与设备的关系、等等。所以这里问的锁屏界面显示也是其中的一种Flag。


// Window不需要获取焦点,也不接受各种输入事件。
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
// @deprecated Use {@link android.R.attr#showWhenLocked} or
// {@link android.app.Activity#setShowWhenLocked(boolean)} instead to prevent an
// unintentional double life-cycle event.
// 窗口可以在锁屏的 Window 之上显示
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;


Window三种类型都存在的情况下,显示层级是怎样。


Type表示Window的类型,一共三种:


  • 应用Window。对应着一个Activity,Window层级为1~99,在视图最下层。
  • 子Window。不能单独存在,需要附属在特定的父Window之中(如Dialog就是子Window),Window层级为1000~1999。
  • 系统Window。需要声明权限才能创建的Window,比如Toast和系统状态栏,Window层级为2000-2999,处在视图最上层。


可以看到,区别就是有个Window层级(z-ordered),层级高的能覆盖住层级低的,离用户更近。


Window就是指PhoneWindow吗?


如果有人问我这个问题,我肯定心里要大大的疑惑了🤔。


可不就是PhoneWindow吗?都唯一实现类了,净问些奇怪问题。


但是面试的时候遇到这种问题总要答啊?这时候就要扯出Window的概念了。


如果指的Window类,那么PhoneWindow作为唯一实现类,一般指的就是PhoneWindow。


如果指的Window这个概念,那肯定不是指PhoneWindow,而是存在于界面上真实的View。当然也不是所有的View都是Window,而是通过WindowManager添加到屏幕的view才是Window,所以PopupWindow是Window,上述问题中添加的单个View也是Window。


PhoneWindow什么时候被创建的?


熟悉Activity启动流程的朋友应该知道,启动过程会执行到ActivityThread的handleLaunchActivity方法,这里初始化了WindowManagerGlobal,也就是WindowManager实际操作Window的类,待会会看到:


public Activity handleLaunchActivity(ActivityClientRecord r,
                                         PendingTransactionActions pendingActions, Intent customIntent) {
        //...
        WindowManagerGlobal.initialize();
        //...
        final Activity a = performLaunchActivity(r, customIntent);
        //...
        return a;
    }


然后会执行到performLaunchActivity中创建Activity,并调用attach方法进行一些数据的初始化(伪代码):


final void attach() {
        //初始化PhoneWindow
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        mWindow.setCallback(this);
        //和WindowManager关联
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        mWindowManager = mWindow.getWindowManager();
}


可以看到,在Activity的attach方法中,创建了PhoneWindow,并且设置了callback,windowManager。


这里的callback待会会说到,跟事件分发有关系,可以说是当前Activity和PhoneWindow建立联系。



目录
相关文章
|
4月前
|
安全 Android开发 Kotlin
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
**Kotlin中的`by lazy`和`lateinit`都是延迟初始化技术。`by lazy`用于只读属性,线程安全,首次访问时初始化;`lateinit`用于可变属性,需手动初始化,非线程安全。`by lazy`支持线程安全模式选择,而`lateinit`适用于构造函数后初始化。选择依赖于属性特性和使用场景。**
156 5
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
|
4月前
|
安全 Android开发 Kotlin
Android经典面试题之Kotlin中常见作用域函数
**Kotlin作用域函数概览**: `let`, `run`, `with`, `apply`, `also`. `let`安全调用并返回结果; `run`在上下文中执行代码并返回结果; `with`执行代码块,返回结果; `apply`配置对象后返回自身; `also`附加操作后返回自身
62 8
|
4月前
|
Android开发 开发者
Android经典面试题之SurfaceView和TextureView有什么区别?
分享了`SurfaceView`和`TextureView`在Android中的角色。`SurfaceView`适于视频/游戏,独立窗口低延迟,但变换受限;`TextureView`支持复杂变换,视图层级中渲染,适合动画/视频特效,但性能略低。两者在性能、变换、使用和层级上有差异,开发者需按需选择。
84 1
|
4月前
|
SQL 安全 Java
Android经典面试题之Kotlin中object关键字实现的是什么类型的单例模式?原理是什么?怎么实现双重检验锁单例模式?
Kotlin 单例模式概览 在 Kotlin 中,`object` 关键字轻松实现单例,提供线程安全的“饿汉式”单例。例如: 要延迟初始化,可使用 `companion object` 和 `lazy` 委托: 对于参数化的线程安全单例,结合 `@Volatile` 和 `synchronized`
64 6
|
4月前
|
SQL Java Unix
Android经典面试题之Java中获取时间戳的方式有哪些?有什么区别?
在Java中获取时间戳有多种方式,包括`System.currentTimeMillis()`(毫秒级,适用于日志和计时)、`System.nanoTime()`(纳秒级,高精度计时)、`Instant.now().toEpochMilli()`(毫秒级,ISO-8601标准)和`Instant.now().getEpochSecond()`(秒级)。`Timestamp.valueOf(LocalDateTime.now()).getTime()`适用于数据库操作。选择方法取决于精度、用途和时间起点的需求。
67 3
|
4月前
|
XML Android开发 数据格式
Android面试题之DialogFragment中隐藏导航栏
在Android中展示全屏`DialogFragment`并隐藏状态栏和导航栏,可通过设置系统UI标志实现。 记得在布局文件中添加内容,并使用`show()`方法显示`DialogFragment`。
64 2
|
4月前
|
Android开发
Android面试题之View的invalidate方法和postInvalidate方法有什么区别
本文探讨了Android自定义View中`invalidate()`和`postInvalidate()`的区别。`invalidate()`在UI线程中刷新View,而`postInvalidate()`用于非UI线程,通过消息机制切换到UI线程执行`invalidate()`。源码分析显示,`postInvalidate()`最终调用`ViewRootImpl`的`dispatchInvalidateDelayed`,通过Handler发送消息到UI线程执行刷新。
60 1
|
4月前
|
消息中间件 调度 Android开发
Android经典面试题之View的post方法和Handler的post方法有什么区别?
本文对比了Android开发中`View.post`与`Handler.post`的使用。`View.post`将任务加入视图关联的消息队列,在视图布局后执行,适合视图操作。`Handler.post`更通用,可调度至特定Handler的线程,不仅限于视图任务。选择方法取决于具体需求和上下文。
56 0
|
4月前
|
Android开发 Kotlin
Android经典面试题之Kotlin中Lambda表达式有哪些用法
Kotlin的Lambda表达式是匿名函数的简洁形式,常用于集合操作和高阶函数。基本语法是`{参数 -&gt; 表达式}`。例如,`{a, b -&gt; a + b}`是一个加法lambda。它们可在`map`、`filter`等函数中使用,也可作为参数传递。单参数时可使用`it`关键字,如`list.map { it * 2 }`。类型推断简化了类型声明。
28 0
|
4月前
|
Android开发 Kotlin
Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
**Kotlin中的匿名函数与Lambda表达式概述:** 匿名函数(`fun`关键字,明确返回类型,支持非局部返回)适合复杂逻辑,而Lambda(简洁语法,类型推断)常用于内联操作和高阶函数参数。两者在语法、返回类型和使用场景上有所区别,但都提供无名函数的能力。
35 0
下一篇
无影云桌面