面试总结(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消息机制

详细查看gityuan的Android消息机制

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运行过程:

面试总结(Android基础)_第1张图片
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。

  1. FixThreadPool:只有核心线程,并且数量固定的,也不会被回收,所有线程都活动时,因为队列没有限制大小,新任务会等待执行。优点:更快的响应外界请求。
  2. SingleThreadPool:只有一个核心线程,确保所有的任务都在同一线程中按序完成。因此不需要处理线程同步的问题。
  3. CachedThreadPool:只有非核心线程,最大线程数非常大,所有线程都活动时会为新任务创建新线程, 否则会利用空闲线程(60s 空闲时间,过了就会被回收,所以线程池中有 0 个线程的可能)处理任务。优点:任何任务都会被立即执行(任务队列 SynchronousQuue 相当于一个空集合); 比较适合执行大量的耗时较少的任务。
  4. ScheduledThreadPool:核心线程数固定,非核心线程(闲着没活干会被立即回收数)没有限制。优点:执行定时任务以及有固定周期的重复任务。

Fragment 加载到 Activity 的 2 种方式

Fragment 加载到 Activity 的方式分为静态加载和动态加载,下面我们看看这两种加载方 式:
静态加载:直接在 Activity 布局文件中指定 Fragment。使用指定属性 name 即可,代码 如下所示:


动态加载: 动态加载需要使用到 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、网络和数据库 内存中

你可能感兴趣的:(面试总结(Android基础))