1.抽象类,接口的区别
从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。接口是设计的结果,抽象类是重构的结果
0、一个类中含有抽象方法,那么这个类必须就是抽象类;接口是个抽象方法集合,并不是类
1、抽象类要被子类继承,接口要被子类实现;一个子类只能继承一个父类,但是可以实现多个接口。
2、接口里面只能对方法进行声明,抽象类还可对方法进行实现。
3、抽象类和接口都不能被直接实例化。如果抽象类要实例化,那么抽象类定义的变量必须指向一个子类对象,这个子类继承了这个抽象类并实现了这个抽象类的所有抽象方法。如果接口要实例化,那么这个接口定义的变量要指向一个子类对象,这个子类必须实现了这个接口所有的方法。
4、抽象类里面的抽象方法必须全部被子类实现,如果子类不能全部实现,那么子类必须也是抽象类。接口里面的方法也必须全部被子类实现,如果子类不能实现那么子类必须是抽象类。
5、抽象类中的方法都要被实现,所以抽象方法不能是静态的static,也不能是私有的private。抽象方法可以有public、protected和default这些修饰符,接口方法默认修饰符且有且只有一个是public。
一、使用new关键字,如 User user=new User();
二、使用clone方法
三、使用反序列化
四、使用反射
五、使用Unsafe
3.equals()和hashCode()
在Java里都是用来对比两个对象是否相等。两个角度区别,一个是性能,一个是可靠性
equals()比较的全面复杂,效率低
hashCode() hash值比较就可以,效率高
equals()相等的两个对象他们的hashCode()肯定相等,equals()可靠
hashCode()相等的两个对象他们的equals()不一定相等,hashCode()不可靠
需要大量并且快速的对比的话,先用hashCode()去对比,如果hashCode()不一样,则表示这两个对象肯定不相等,如果hashCode()相同,此时再对比他们的equals(),如果equals()也相同,则表示这两个对象是真的相同
阿里巴巴开发规范明确规定:
只要重写 equals,就必须重写 hashCode;
因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法;
如果自定义对象做为 Map 的键,那么必须重写 hashCode 和 equals;
String 重写了 hashCode 和 equals 方法,所以我们可以直接使用 String 对象作为 key 来使用;
1.Intent跳转时携带数据
2.类的静态变量
3.Application的全局变量
4.Service服务
5.外部存储来实现通讯(借助 SharedPreference 使用Android数据库 SQLite 使用 File本地文件)
5.Launcher 启动 Activity的过程
在手机桌面应用中点击某一个 icon ,实际上就是通过 startActivity 去打开某一个 Activity 页面。 Android 中的一个 App 就相当于一个进程,所以 startActivity 操作中还需要判断,目标 Activity 的进程是否已经创建,如果没有,则在显示 Activity 之前还需要将进程 Process 提前创建出来。
整个 startActivity 的流程分为 3 大部分,也涉及 3 个进程之间的交互:
ActivityA --> AMS
AMS --> ApplicationThread
ApplicationThread --> ActivityB
ActivityA --> AMS 阶段
Activity 的 startActivity -> Activity 的 startActivityForResult -> Instrumentation.execStartActivity
会通过 ActivityManger.getService 获取 AMS 的实例,然后调用其 startActivity 方法,实际上这里就是通过 AIDL 来调用 AMS 的 startActivity 方法,至此,startActivity 的工作重心成功地从进程 A 转移到了系统进程 AMS 中。
AMS --> ApplicationThread 阶段
ApplicationThread 类,这个类是负责进程间通信的,这里 AMS 最终其实就是调用了 B 进程中的一个 ApplicationThread 引用,从而间接地通知 B 进程进行相应操作。
相比于 startActivity-->AMS,AMS-->ApplicationThread 流程看起来复杂好多了,实际上这里面就干了 2 件事:
1.处理 launchMode 和 Intent 中的 Flag 标志位,并根据处理结果生成一个目标 Activity B 的对象(ActivityRecord)。
2.判断是否需要为目标 Activity B 创建一个新的进程(ProcessRecord)、新的任务栈(TaskRecord)
AMS.startActivity ()
obtainStarter 方法获取了 ActivityStarter 类型的对象,然后调用其 execute 方法,在 execute 方法中,会再次调用其内部的 startActivityMayWait 方法。
ActivityStarter.startActivityMayWait()
ActivityStarter 这个类专门负责一个 Activity 的启动操作。它的主要作用包括解析 Intent、创建 ActivityRecord、可能创建 TaskRecord。目标 Activity 信息的操作由 mSupervisor 来实现,它是 ActivityStackSupervisor 类型
ActivityStarter.startActivityUnchecked()
1 计算启动 Activity 的 Flag 值,不同的 Flag 决定了启动 Activity 最终会被放置到哪一个 Task 集合中
2 处理 Task 和 Activity 的进栈操作。
3 启动栈中顶部的 Activity。
ActivityStack .resumeFocusedStackTopActivityLocked()
不管是目标进程已经存在还是新建目标进程
ActivityStackSupervisor.realStartActivityLocked()
android-28 开始 Activity 的启动交给了事务(Transaction)来完成。
ClientTransaction 的 schedule 方法,transaction 实际上是在创建 ClientTransaction 时传入的 app.thread 对象,也就是 ApplicationThread。
到这为止 startActivity 操作就成功地从 AMS 转移到了另一个进程 B 中的 ApplicationThread中,剩下的就是 AMS 通过进程间通信机制通知 ApplicationThread 执行 ActivityB 的生命周期方法。
ApplicationThread -> Activity 阶段
ApplicationThread.scheduleTransaction()
->ActivityThread.scheduleTransaction()
调用 sendMessage 方法,向 Handler 中发送了一个 EXECUTE_TRANSACTION 的消息,并且 Message 中的 obj 就是启动 Activity 的事务对象。而这个 Handler 的具体实现是 ActivityThread 中的 mH 对象
最终调用了事务的 execute 方法
会遍历事务中的 callback 并执行 execute 方法,LaunchActivityItem 的 execute()
ActivityThread.handleLaunchActivity
这是一个比较重要的方法,Activity 的生命周期方法就是在这个方法中有序执行
图中 1处初始化 Activity 的 WindowManager,每一个 Activity 都会对应一个“窗口”,下一节会详细讲解。
图中 2 处调用 performLaunchActivity 创建并显示 Activity。
图中 3 处通过反射创建目标 Activity 对象。
图中 4 处调用 attach 方法建立 Activity 与 Context 之间的联系,创建 PhoneWindow 对象,并与 Activity 进行关联操作。
图中 5 处通过 Instrumentation 最终调用 Activity 的 onCreate 方法。
总结
首先进程 A 通过 Binder 调用 AMS 的 startActivity 方法。
然后 AMS 通过一系列的计算构造目标 Intent,然后在 ActivityStack 与 ActivityStackSupervisor 中处理 Task 和 Activity 的入栈操作。
最后 AMS 通过 Binder 机制,调用目标进程中 ApplicationThread 的方法来创建并执行 Activity 生命周期方法,实际上 ApplicationThread 是 ActivityThread 的一个内部类,它的执行最终都调用到了 ActivityThread 中的相应方法。
五.栈溢出的情况
1、局部数组过大。当函数内部的数组过大时,有可能导致堆栈溢出。
2、递归调用层次太多。递归函数在运行时会执行压栈操作,当压栈次数太多时,也会导致堆栈溢出。
3、指针或数组越界。
六.多线程同时访问一个共享可变的状态变量
最关键的一点就是需要对于共享的和可变的状态进行访问控制 有以下三种办法:
1.不在线程中共享该状态变量(尽量减少不必要的共享)。
2.共享的变量设置为不可变状态(简单而有效,不可变的对象天然就是多线程安全的,比如String和BigInteger)。
3.访问状态变量时使用同步机制
多线程安全性的定义.
最核心的一点就是正确性,也就是程序的行为结果和预期一致.
当多个线程访问某个类时,不管运行环境采用何种线程调度算法或者这些线程如何交替执行,且不需要在主程序中添加任何额外的协同机制,这个类都能表现出正确的行为,那么这个类就是线程安全的。
如何确保多线程安全呢?简单说就是让对于共享可变的状态变量的访问操作都是原子性的,也就是不可分隔的。
1.i++操作,缓存一致性问题
2.竞态条件 由于“先检查后执行”,也就是先去检查一个值的状态,根据这个状态再去执行响应的动作。但是在多线程中,读取这个值的状态后,该值就可能被其他线程修改了,因而失效。
要解决以上问题,就需要将一组操作组成一个原子性的复合操作。在复合操作没有执行完之前,该操作过程不能被打断。
1.少量共享状态变量,使用原子类。比如AtomicLong的incrementAndGet方法保证自增长是原子性的
2.多个共享状态变量,使用内置锁和同步代码块来实现
注意
- 在设置同步代码块时,应该避免同步控制的滥用,为了提高性能,应该尽可能缩小同步代码块的范围;
- 不要在同步代码块中进行耗时操作,这样对于性能是很大的消耗。
七.Synchronize和ReentrantLock区别
相似点
都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的
区别:
API层面
Synchronized是java语言的关键字,是原生语法层面的互斥,需要jvm实现。
ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成。
synchronized既可以修饰方法,也可以修饰代码块。ReentrantLock必须手动获取与释放锁,而synchronized不需要手动释放和开启锁
等待可中断
等待可中断是指当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。可等待特性对处理执行时间非常长的同步快很有帮助。
公平锁
公平锁是指多个线程在等待同一个锁时,必须按照申请的时间顺序来依次获得锁
Schronized,ReentrantLock默认是非公平锁,但ReentrantLock可以通过带布尔值的构造函数要求使用公平锁。
锁绑定多个条件
ReentrantLock可以同时绑定多个Condition对象,只需多次调用newCondition方法即可。
synchronized中,锁对象的wait()和notify()或notifyAll()方法可以实现一个隐含的条件。但如果要和多于一个的条件关联的时候,就不得不额外添加一个锁。
八.自定义View
自定义 UI 控件有 2 种方式:
1.继承系统提供的成熟控件(比如 LinearLayout、RelativeLayout、ImageView 等);
2.直接继承自系统 View 或者 ViewGroup,并自绘显示内容。
1.继承现有控件
这是一种较简单的实现方式。因为大部分核心工作,比如关于控件大小的测量、控件位置的摆放等相关的计算,在系统中都已经实现并封装好,开发人员只要进行一些扩展,并按照自己的意图显示相应的 UI 元素。比如自定义TitleBar.
自定义属性
在 XML 文件中直接指定 title 的显示内容、字体颜色,需使用自定义属性。
在 res 的 values 目录下的 attrs.xml 文件中(没有就自己新建一个),使用标签自定义属性
declare-styleable
标签代表定义一个自定义属性集合,一般会与自定义控件结合使用;
attr
标签则是某一条具体的属性,name 是属性名称,format 代表属性的格式。
在 XML 布局文件中使用自定义属性
需要先添加命名空间 xmlns:app,然后通过命名空间 app 引用自定义属性,并传入相应的图片资源和字符串内容。
在 CustomTitleBar 中,获取自定义属性的引用值,主要是通过 Context.obtainStyleAttributes 方法获取到自定义属性的集合,然后从这个集合中取出相应的自定义属性。
2.直接继承自 View 或者 ViewGroup 麻烦但是灵活,能实现更加复杂的 UI 界面
如何根据相应的属性将 UI 元素绘制到界面;
自定义控件的大小,宽和高设置多少;
如果是 ViewGroup,如何合理安排其内部子 View 的摆放位置
onDraw onMeasure
onLayout
因此自定义 View 的重点工作其实就是复写并实现这 3 个方法。注意:并不是每个自定义 View 都需要实现这 3 个方法,大多数情况下只需要实现其中 2 个甚至 1 个方法也能满足需求。
onDraw
onDraw 方法接收一个 Canvas 类型的参数。Canvas 可以理解为一个画布,在这块画布上可以绘制各种类型的 UI 元素。
Canvas 中每一个绘制操作都需要传入一个 Paint 对象。Paint 就相当于一个画笔,我们可以通过设置画笔的各种属性,来实现不同绘制效果
Canvas.drawCircle()
onMeasure
自定义 View 为什么需要重新测量.
Android 系统提供了 wrap_content 和 match_parent 属性来规范控件的显示规则。它们分别代表自适应大小和填充父视图的大小,但是这两个属性并没有指定具体的大小,因此我们需要在 onMeasure 方法中过滤出这两种情况,真正的测量出自定义 View 应该显示的宽高大小。
onMeasure() 会传入 2 个参数 widthMeasureSpec 和 heightMeasureSpec。这两个参数是从父视图传递给子 View 的两个参数,看起来很像宽、高,但是它们所表示的不仅仅是宽和高,还有一个非常重要的测量模式。
共有 3 种测量模式。
EXACTLY:表示在 XML 布局文件中宽高使用 match_parent 或者固定大小的宽高; AT_MOST:表示在 XML 布局文件中宽高使用 wrap_content; UNSPECIFIED:父容器没有对当前 View 有任何限制,当前 View 可以取任意尺寸,比如 ListView 中的 item。
为什么 1 个 int 值可以代表 2 种意义呢? 实际上 widthMeasureSpec 和 heightMeasureSpec 都是使用二进制高 2 位表示测量模式,低 30 位表示宽高具体大小。
View 中的 onMeasure
setMeasuredDimension()这个方法传入的值直接决定 View 的宽高,默认为父视图的剩余可用空间
只要复写 onMeasure,过滤出 wrap_content 的情况,并主动调用 setMeasuredDimension 方法设置正确的宽高即可:
ViewGroup 中的 onMeasure
ViewGroup 在测量自己的宽高之前,需要先确定其内部子 View 的所占大小,然后才能确定自己的大小。
LinearLayout
wrap_content的最终宽度由其内部最大的子 View 宽度决定。
FlowLayout
调用 measureChild 方法递归测量子 View;
通过叠加每一行的高度,计算出最终 FlowLayout 的最终高度 totalHeight。
onLayout
要定义 ViewGroup 内部子 View 的显示规则,则需要复写并实现 onLayout 方法。
ViewGroup 中的 onLayout 方法,它是一个抽象方法,也就是说每一个自定义 ViewGroup 都必须主动实现如何排布子 View,具体就是遍历每一个子 View,调用 child.layout(l, t, r, b) 方法来为每个子 View 设置具体的布局位置。四个参数分别代表左上右下的坐标位置
AIDL的使用流程
View 的绘制流程
Layout自适应,跟全屏对绘制是否有影响
View的事件传递机制
Handler原理,sendMessage,postDelay,底层调用的是同一个方法吗
new Handler一定是主线程的吗
HashMap
ANR
优化
leckcanary
单例
volitale
屏幕绘制什么情况下回引起丢帧,每隔16ms都会调用一次绘制吗,调用onDraw屏幕回立刻刷新?系统会发一次信号,并不会每次都刷新,要刷新的时候才去绘制,会丢帧吗
自定义动画,子线程调用invalidate()会刷新??
Rxjava,线程切换的原理,flatMap map的区别
架构设计了解多少,软件设计的架构原则
插件化,65535方法数
发送指令
难题
要问的