面试总结(Android基础)
Activity的启动模式
Activity的四大启动模式:
- standard:标准模式,每次都会在活动栈中生成一个新的Activity实例。通常我们使用的活动都是标准模式。
- singleTop:栈顶复用,如果Activity实例已经存在栈顶,那么就不会在活动栈中创建新的实例。比较常见的场景就是给通知跳转的Activity设置,因为你肯定不想前台Activity已经是该Activity的情况下,点击通知,又给你再创建一个同样的Activity。
- singleInstance:栈内复用,如果Activity实例在当前栈中已经存在,就会将当前Activity实例上面的其他Activity实例都移除栈。常见于跳转到主界面。
- singleTask:单实例模式,创建一个新的任务栈,这个活动实例独自处在这个活动栈中。
Activity中onStart和onResume的区别?onPause和onStop的区别?
Activity有三类:
- 可见可交互Activity:活跃的Activity,正在和用户交互的Activity。
生命周期:
onCreate->onStart->onResume
- 可见不可交互的Activity:用户可以看到该Activity,但是无法与该也看进行交互。例子:在一个Activity1中启动一个Activity2,背景透明或者为Dialog样式,处在Activity2下面的Activity1就是可见不可交互的Activity。
生命周期:
Activity1 : onCreate->onStart->onResume
Activity2 : onCreate->onStart->onResume
Activity1 : onPause->onStop
- 不可见不可交互Activity:已经被暂停的Activity,比如已经执行了onStop方法。
结论:
onStart执行后,Activity为可见,执行onResume后,Activity为可见可交互。
onPause执行后,Activity为可见不可交互,执行onStop后,Activity为不可见不可交互。
注:可见可交互Activity可以称为前台活跃Activity,可见不可交互的Activity可以成为前台不活跃Activity,不可见不可交互Activity为后台Activity。
屏幕适配
平时如何有使用屏幕适配吗?原理是什么呢?
平时的屏幕适配一般采用的头条的屏幕适配方案。简单来说,以屏幕的一边作为适配,通常是宽。
原理:设备像素px
和设备独立像素dp
之间的关系是
px = dp * density
假设UI给的设计图屏幕宽度基于360dp,那么设备宽的像素点已知,即px,dp也已知,360dp,所以density = px / dp
,之后根据这个修改系统中跟density
相关的知识点即可。
Handler消息机制
Handler机制的作用:
负责跨线程通信,这是因为在主线程不能做耗时操作,而子线程不能更新UI,所以当子线程中进行耗时操作后需要更新UI时,通过Handler将有关UI的操作切换到主线程中执行。
Handler机制的成员:
- Message(消息):需要被传递的消息,消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息;
- MessageQueue(消息队列):负责消息的存储与管理,负责管理有Handler发送过来的Message。读取会自动删除消息,单链表维护,插入和删除上有优势。在MessageQueue.next方法中会无线循环,不断判断是否有消息,有就返回这条消息并移除。另外MessageQueue.enqueueMessage会向消息池中投递消息。
- Handler(消息处理器):负责Message的发送与处理,主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage),按照先进先出执行,内部使用的是单链表结构。
- Looper(消息池):负责关联线程以及消息的分发,在该线程下从MessageQueue获取Message,分发给Handler,Looper创建的时候,会创建一个MessageQueue,调用loop()方法的时候,消息循环开始,其中会不断的调用messageQueue的next()方法,当有消息就处理,否则阻塞在messageQueue的next()方法中。当Looper的quit()被调用的时候会调用messageQueue的quit(),此时next()会返回null,然后loop()方法也就跟着退出。
Handler运行过程:
Handler运行过程- 在主线程创建的时候会创建一个Looper,同时也会在在Looper内部创建一个消息队列。而在创键Handler的时候取出当前线程的Looper,并通过该Looper对象获得消息队列,然后Handler在子线程中通过MessageQueue.enqueueMessage在消息队列中添加一条Message。
- 通过Looper.loop()开启消息循环不断轮询调用MessageQueue.next(),取得对应的Message并且通过Handler.dispatchMessage传递给Handler,最终调用Handler.handlerMessage处理消息。
一个线程能否创建多个 Handler,Handler 跟 Looper 之间的对应关系?
说到这个问题,先了解一下ThreadLocal机制。
Looper中还有一个ThreadLocal。ThreadLocal不是线程,它的作用是可以在每个线程中存储数据。
Handler创建的时候会采用当前线程的Looper来构造消息循环系统。
那么Handler内部如何获取当前线程的Looper呢?
这就要使用ThreadLocal。Looper类中使用ThreadLocal来存储Looper对象。
那么为什么需要ThreadLocal来存储Looper呢?
因为Looper的构造函数是私有的,无法创建Looper进行赋值。只能将Looper的引用存在变量中,而且每个线程都有自己对应的Looper,则需要用到ThreadLocal来存储。因为ThreadLocal可以在不同的线程中互不干扰的存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。
而Looper中又有这样一段代码:
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
从上面代码可以看出,如果获取sThreadLocal.get()不为空,就会抛出异常。代表sThreadLocal.set只会执行一次。由此得出结论:
一个Thread只能有一个Looper,一个MessageQueue,可以有多个Handler。
线程池的相关知识
Android 中的线程池都是直接或间接通过配置 ThreadPoolExecutor 来实现不同 特性的线程池.Android 中最常见的类具有不同特性的线程池分别为 FixThreadPool、CachedhreadPool、SingleThreadPool、ScheduleThreadExecutr。
- FixThreadPool:只有核心线程,并且数量固定的,也不会被回收,所有线程都活动时,因为队列没有限制大小,新任务会等待执行。优点:更快的响应外界请求。
- SingleThreadPool:只有一个核心线程,确保所有的任务都在同一线程中按序完成。因此不需要处理线程同步的问题。
- CachedThreadPool:只有非核心线程,最大线程数非常大,所有线程都活动时会为新任务创建新线程, 否则会利用空闲线程(60s 空闲时间,过了就会被回收,所以线程池中有 0 个线程的可能)处理任务。优点:任何任务都会被立即执行(任务队列 SynchronousQuue 相当于一个空集合); 比较适合执行大量的耗时较少的任务。
- ScheduledThreadPool:核心线程数固定,非核心线程(闲着没活干会被立即回收数)没有限制。优点:执行定时任务以及有固定周期的重复任务。
Fragment 加载到 Activity 的 2 种方式
Fragment 加载到 Activity 的方式分为静态加载和动态加载,下面我们看看这两种加载方 式:
静态加载:直接在 Activity 布局文件中指定 Fragment。使用指定属性 name 即可,代码 如下所示:
<fragment
android:name="com.example.myfragment.MyFragment"
android:id="@+id/myfragment_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
动态加载: 动态加载需要使用到 FragmentManager,这种动态加载的方式在开发中是 非常常见的,示例代码如下:
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginT ransaction();
//将FragmentA从容器中移除掉,减少内存的消耗
fragmentTransaction.remove(fragmentA);
fragmentTransaction.add(R.id.fragment_layout,new FragmentB());
fragmentTransaction.commit();
理解Activity,View,Window三者关系
这个问题真的很不好回答。所以这里先来个算是比较恰当的比喻来形容下它们的关系吧。Activity像一个工匠(控制单元),Window像窗户(承载模型),View像窗花(显示视图)LayoutInflater像剪刀,Xml配置像窗花图纸。
1:Activity构造的时候会初始化一个Window,准确的说是PhoneWindow。
2:这个PhoneWindow有一个“ViewRoot”,这个“ViewRoot”是一个View或者说ViewGroup,是最初始的根视图。
3:“ViewRoot”通过addView方法来一个个的添加View。比如TextView,Button等
4:这些View的事件监听,是由WindowManagerService来接受消息,并且回调Activity函数。比如onClickListener,onKeyDown等。
View的事件分发机制
事件分发本质:就是对MotionEvent事件分发的过程。即当一个MotionEvent产生了以后,系统需要将这个点击事件传递到一个具体的View上。
点击事件的传递顺序:Activity(Window) -> ViewGroup -> View
三个主要方法:
dispatchTouchEvent:进行事件的分发(传递)。返回值是 boolean 类型,受当前onTouchEvent和下级view的dispatchTouchEvent影响
onInterceptTouchEvent:对事件进行拦截。该方法只在ViewGroup中有,View(不包含 ViewGroup)是没有的。一旦拦截,则执行ViewGroup的onTouchEvent,在ViewGroup中处理事件,而不接着分发给View。且只调用一次,所以后面的事件都会交给ViewGroup处理。
onTouchEvent:进行事件处理。
View 的绘制过程,自定义View。
View 的绘制过程
关于View的绘制过程,可以简单的分成三个步骤:
1)measure 过程:
作用:测量View的宽、高
流程:performMeasure() --- measure() --- onMeasure() --- 子View的measure()
备注:在onMeasure() 方法中会对所有的子元素进行measure过程
2)layout 过程:
作用:通过确定View四个顶点的位置,从而确定View的位置
流程:performLayout() --- layout() --- onLayout() ---子View的layout过程
备注:在OnLayout()方法中会对所有子元素进行layout过程
3)draw 过程:
作用:将View绘制在屏幕上
流程:performDraw() --- draw() --- onDraw() --- 子View 的 draw 过程
自定义View
自定义View分为以下几种:
类型 | 定义 |
---|---|
自定义组合控件 | 多个控件组合成为一个新的控件,方便多处复用 |
继承系统View控件 | 继承自TextView等系统控件,在系统控件的基础功能上进行扩展 |
继承View | 不复用系统控件逻辑,继承View进行功能定义 |
继承系统ViewGroup | 继承自LinearLayout等系统控件,在系统控件的基础功能上进行扩展 |
继承ViewViewGroup | 不复用系统控件逻辑,继承ViewGroup进行功能定义 |
详情参考Android自定义View全解
Android有哪些序列化方式?
为了解决Android中内存序列化速度过慢的问题,Android使用了Parcelable
。
对比 | Serializable | Parcelable |
---|---|---|
易用性 | 简单 | 需要实现特定方法 |
效率 | 低 | 高 |
场景 | IO、网络和数据库 | 内存中 |