1、继承Thread跟实现Runnable创建线程的区别?
Thread: 多线程分别完成自己的任务,资源不共享
Runnable: 多线程共同完成自己的任务,资源共享
2、ContentProvider无法获取上下文对象,因为此时App不一定初始化完成。
3、自定义键盘用到KeyboardView、Keyboard
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:background="#999999"
android:focusable=“true”
android:focusableInTouchMode=“true”
android:keyBackground="@drawable/key_background_selector" // 每个按键的背景
android:keyPreviewHeight=“64dip”
android:keyPreviewLayout="@layout/key_preview_layout" // 该布局最外层view要设置背景,否则会出错
android:keyTextColor="@android:color/black"
android:keyTextSize=“24sp”
android:labelTextSize=“18sp”
android:paddingBottom=“8dip”
android:paddingTop=“8dip”
android:shadowColor="#FFFFFF" // 不设置shadowColor跟shadowRadius,文字会变模糊
android:shadowRadius=“0.0”
android:visibility=“visible” />
在Activity中调用:
Keyboard keyboard = new Keyboard(R.xml.keyboard); // 把xml编辑的键盘布局渲染出来
keyboardView.setKeyboard(keyboard); // 将键盘赋值给键盘视图
keyboardView.setOnKeyboardActionListener(); // 必须设置这个监听,否则会报错
键盘xml文件
// 每个键宽度 %p 相对于父控件百分比
4、ViewStub应用,可以加载一些不常用的布局,当调用viewStub的inflate方法时,才渲染该布局:
ViewStub的宽高为0,默认不可见,当调用inflate方法时,会把自己从视图树中移除,再把懒加载的视图加载进来。inflate方法不可重复调用,否则会抛异常。
// 我们要渲染的布局
private void replaceSelfWithView(View view, ViewGroup parent) {
final int index = parent.indexOfChild(this);
// 把自己移除
parent.removeViewInLayout(this);
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
// 把要渲染的布局添加到视图树中
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
}
5、merge标签,告诉系统用merge标签包裹的布局,会直接添加到父控件,而不会增加多一层渲染,在merge标签设置的元素属性都是没效的,只会被include进来的父布局影响
6、RelativeLayout与LinearLayout比较:
(1) RelativeLayout会让子view调用两次onMeasure,LinearLayout有weight时,也会调用子view两次onMeasure
(2) RelativeLayout的子view如果高度和RelativeLayout不同,则会引发效率问题,当子view很复杂时,这个问题会更加严重。如果可以,尽量使用padding代替margin
(3) 在不影响层级深度情况下,使用LinearLayout和FrameLayout而不是RelativeLayout
(4) 提高绘制性能的使用方式:
如果他们位于整个view的顶端,位于底层的view将会进行大量的measure操作,大大降低程序性能。应尽量将RelativeLayout和LinearLayout置于view树底层,并减少嵌套
7、自定义一个弹幕View:
(1) 自定义ViewGroup继承RelativeLayout,或者其他常用布局
(2) 创建弹幕item,只显示文字的话一般是TextView。
(3) 关键的一步,在添加TextView到弹幕ViewGroup中时,给TextView一些随机的margin,能实现到多行弹幕显示效果
(4) 当开启弹幕时,给每一项TextView设置平移动画,动画的速度与时间我们在一个范围内给定一个随机数,就能达到弹幕出现速度快慢的视差
8、注解的RetentionPolicy属性:
(1) SOURCE:在原文件中有效,被编译器丢弃
(2) CLASS:在class文件有效,可能会被虚拟机忽略
(3) RUNTIME:在运行是有效
9、并行与并发:
(1) 并发是两个任务可以在重叠的时间段内启动,运行和完成
(2) 并行是任务在同一时间运行,例如,在多核处理器上
10、synchronized关键字:
(1) 作用于方法时,锁住的是对象的实例(this)
(2) 作用于静态方法,锁住的是Class实例,又因为Class的相关数据存储在永久带,因此静态方法锁相当于类的一个全局锁
(3) 作用于一个对象实例时,锁住的是对应的代码块
11、线程池阻塞队列:
(1) SynchronousQueue:(同步队列)这个队列接收到任务的时候,会直接提交给线程处理,而不保留它(名字定义为 同步队列)。但有一种情况,假设所有线程都在工作怎么办?
这种情况下,SynchronousQueue就会新建一个线程来处理这个任务。所以为了保证不出现(线程数达到了maximumPoolSize而不能新建线程)的错误,使用这个类型队列的时候,
maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大,去规避这个使用风险。
(2) LinkedBlockingQueue(链表阻塞队列):这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,
则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize
(3) ArrayBlockingQueue(数组阻塞队列):可以限定队列的长度(既然是数组,那么就限定了大小),接收到任务的时候,如果没有达到corePoolSize的值,则新建线程
(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误
(4) DelayQueue(延迟队列):队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务
12、dpi是软件参考了物理像素密度后,人为指定的一个值(1080720,dpi=320;19201080,dpi=480),dp=dpi/160
13、hash函数:当前存储的数组容量超过当前阈值(容量 * 哈希因子),就会对数组进行扩容
14、apk签名:
(1) 签名方法:将对应权限的签名文件platform.pk8、platform.x509.pem, 签名工具 signapk.jar, 以及需要签名的apk(假设 old.apk) 放到同一目录下,打开linux终端(windows cmd也可以),进入该目录,进行重新签名:
java -jar signapk.jar platform.x509.pem platform.pk8 old.apk new.apk
15、Android两种虚拟机的区别与联系:
(1)Java虚拟机基于栈。基于栈的机器必须使用指令来载入和操作栈上数据,所需指令更多
dalvik虚拟机是基于寄存器的
(2)Java虚拟机运行的是Java字节码。Java类会被编译成一个或多个字节码.class文件,打包到jar文件中,Java虚拟机从相应的.class文件和.jar文件中获取响应的字节码
dalvik运行的是自定义的.dex字节码格式。Java类被编译成.class文件后,会通过一个dx工具将所有的.class文件转换成一个.dex文件,然后dalvik虚拟机会从其中读取指令和数据,
数据的访问通过寄存器间直接传递,这样的访问方式比基于栈方式要快得多。
16、OOM是在虚拟机栈(VM Stack)报出的,线程一直申请栈,直到内存不足时,OOM
17、GC:
(1)方法区 (2)堆区 (3)虚拟机栈 (4)本地方法栈 (5)程序计数器
有3个是不需要垃圾回收的,虚拟机栈、本地方法栈、程序计算器
18、Activity --> PhoneWindow --> DecorView --> ViewGroup
19、类加载过程:加载–>验证–>准备–>解析–>初始化
20、https://github.com/getActivity/AndroidProject android基础架构
21、compileSdkVersion,minSdkVersion,targetSdkVersion 的区别和比较:
(1)compileSdkVersion只是我们编译app时用的sdk版本,不涉及到运行时的行为。最理想的情况是把compileSdkVersion设置到最高,因为使用新的编译检查可以避免弃用的API。
(2)minSdkVersion是程序运行的最低要求sdk,手机系统如果低于这个sdk版本是安装不上去的。
(3)targetSdkVersion主要是提供向前兼容的作用。如果手机系统是4.0或5.0,编译包时用的compileSdk6.0,但是表现形式得按照targetSdk。系统升级时,涉及到兼容的代码,源码会通过targetSdk来判断是否使用新逻辑。
(4)buildToolsVersion是构建工具的版本号是多少,规则是可以用高的构建工具来构建低版本sdk的工程。
22、implementation、api、compile区别
(1)api等同于compile,没区别。
(2)使用了implementation编译的依赖,对该项目有依赖的项目将无法访问到使用该命令编译的依赖中的任何程序,也就是将该依赖隐藏在内部,而不对外部公开。
23、mvp分别代表:
M:model,模型层,负责数据请求,业务处理;
V:view层,我们看到的界面,UI布局;
P:presenter,将V与M分离,降低耦合,所有的交互都发生在presenter。
24、onNewIntent什么时候调用?
(1)activity的launchMode为singleTop,并且该activity在栈顶时,再次启动就会调用;
(2)activity的launchMode为singleTask,如果task中已经有该activity实例,再次启动时调用;
(3)activity的launchMode为singleInstance,启动过该activity,并且有该activity实例,就会调用;
(4)activity的launchMode为standard,增加了FLAG_ACTIVITY_CLEAR_TOP 和FLAG_ACTIVITY_SINGLE_TOP,那么如果有曾经启动过该activity,则会调用onNewIntent。
25、在xml中写的fragment是通过instantiate方法创建的,该方法里是通过反射实例化fragment对象。
fragment的生命周期跟随FragmentActivity
26、SharedPreference的apply和commit区别
apply是异步方法,commit是同步方法
27、项目难点、亮点
(1)RN列表支持加载更多功能;
(2)游戏下载兼容HTTPS、支持断点续传;
(3)结合原生解决的启动白屏问题;
28、js与原生交互
必须设置settings.setJavaScriptEnabled(true);
(1)js调用原生:addJavascriptInterface(object, “android”);第一个参数是包含js方法的对象,第二个参数是与js约定的方法名
(2)原生调用js:loadUrl(“javascript:方法名()”)
evaluateJavascript(“方法名()”, cb) 可以拿到js端的返回值
29、AMS通过调用ApplicationThread里的方法,发送消息,执行ActivityThread相关方法,进而控制Activity实例的状态变化
30、多线程的3个属性:原子性、可见性、有序性
(1)原子性:线程的多个操作是一个整体,不能被分割,要么不执行,要么就全部执行,中间不能被打断;
(2)可见性:一个线程修改后的结果,其他线程能够立马知道;
(3)有序性:为了提高效率,Java的编译器和处理器可以对指令进行重新排序,重新排序会影响多线程并发的正确性,有序性就是保证不进行重新排序(保证线程操作的执行顺序)
31、volatile与synchronized区别
volatile:(1)保证可见性和有序性,如果一个变量被volatile修饰,一个线程修改了这个变量后,其他线程是立马可知的。
(2)能禁止指令重新排序,在指令重排优化时,在volatile变量之前的指令不能在volatile之后执行,同理…
(3)只能作用于变量
synchronized:被s修饰的代码能防止被多个线程同时执行,保证了在同一时刻只有一个线程执行同步代码块,相当于是单线程操作,线程的原子性、可见性、有序性都保证了
32、常用设计模式
(1)单例模式:InputMethodManager、Runtime
(2)builder模式
(3)原型模式:通过实现clone复制一个对象,Intent、Okhttp
(4)策略模式:根据需求,传入不同的实现类,做不同操作,RecyclerView.LayoutManager、Animation插值器
(5)观察者模式
(6)代理模式
(7)外观模式:ContextImpl,里面封装了AMS、PMS一些系统的操作,让使用者不需要关心具体实现
(8)责任链模式:事件分发
33、android9.0新特性
(1)非Activity场景启动Activity,intent需要加上FLAG_ACTIVITY_NEW_TASK
(2)前台服务需要添加权限FOREGROUND_SERVICE
(3)限制静态广播的接收
(4)官方的@hide方法不能通过反射调用
34、为什么android进程间通信用Binder而不是socket一些相对传统的方式?
(1)性能方面:Binder的数据拷贝只需要一次,而管道、消息队列、socket都需要两次,内存共享方式一次内存拷贝都不需要,但实现方式复杂;
(2)安全方面:传统进程通信方式没有对通信双方身份进行严格验证,比如socket通信的IP是用户手动输入,很容易进行伪造,而binder机制从本身就支持通信双方做身份验证,大大提高安全性。
传统Linux IPC无法获取对方进程可靠的UID/PID,进而无法确认对方身份,而android作为一个开发的开源体系,拥有非常多开发平台,app来源广泛,因而手机安全变得尤为重要,
无任何保护措施,完全由上层协议来确保。android为每个应用都分配自己的UID,故进程UID是识别进程身份的重要标识。
(3)语言层面:android是基于Java语言(面向对象语言),Binder恰恰符合面向对象思想,Binder对象是一个跨进程引用的对象,它的实例位于一个进程中,但它的引用是遍布系统的各个进程。
35、IPC原理:
每个android进程只能运行在自己的进程所拥有的虚拟地址空间(用户空间+内核空间),对于用户空间不同进程彼此是不能共享的,内核空间可共享,进程间通信恰恰是通过进程间可共享
的内核空间来完成底层的通信工作。
Binder通信的4个角色:
(1)Client进程
(2)Server进程
(3)ServiceManager:将字符串形式的Binder名字转化成Client中对该Binder的引用,使得Client能够通过Binder名字获取对Server中Binder实例的引用;
(4)Binder驱动:负责进程间Binder通信的建立,Binder在进程间的传递,Binder引用计数管理,数据包在进程之间传递与交互等一系列底层支持。
前面3个位于用户空间,Binder驱动位于内核空间
36、常用排序:
(1)选择排序:两层for循环嵌套,外面的for循环是数组长度,里面for循环是找出后面元素的最小值跟当前下标相比,小的排前面(看需求)
每次循环会选出一个最小(大)值放在已排列表末尾
(2)插入排序:遍历数组,每次两两比较,小的排前面
(3)冒泡排序:比较两个相邻的元素,将值大的元素交换至右端,第一趟比较完成,右边的值是最大的,第二趟比较最后的值不用参与,以此类推,最终排序完成。
(4)快速排序:挖坑填坑
37、混淆时,没引用到的代码、类不会打包进去
38、RecycleView与ListView的主要区别:
(1)Adapter不一样
(2)ListView需要自定义ViewHolder与复用convertView,RecycleView复用item全部搞定
(3)ListView布局单一,只有一个纵向的,RecycleView布局丰富,可通过不同LayoutManager设定不同布局,有网格、瀑布流
(4)ListView有setEmptyView()、addHeaderView()、addFooterView(),而RecycleView则需要item类型判断
(5)RecycleView实现了局部刷新,ListView需要自己实现,可通过拿到该item的view,再处理刷新问题
(6)动画,RecycleView封装好API实现自己的动画,通过setItemAnimator自定义对不同item时的动画,想insert、move、change
(7)嵌套滑动机制(RecycleView实现NestedScrollingChild与NestedScrollingParent)
(8)触摸,ItemTouchHelper.attachToRecycleView回调到这三个方法getMovementFlags、onMove、onSwiped
39、自定义ViewGroup:
onMeasure:根据容器设定的宽高模式,测量出容器的实际宽高。这一步只关注测量,而且只负责容器宽高。最后调用setMeasuredDimension方法。
onLayout:根据每个孩子的宽高,布局他们的位置。调用child.layout方法。
40、常用数据结构:
(1)ArrayList:数组结构的集合,查询快、增删慢
(2)LinkedList:链表结构的集合,增删快、查询慢
(3)Vector:跟ArrayList差不多,但是线程安全
(4)HashMap:底层实现是数组+单链表,无序的,线程不安全
(5)LinkedHashMap:继承HashMap,存取有序,在HashMap的基础上多了一个双向列表来维持顺序,线程不安全。根据元素添加或者访问顺序进行排序
(6)TreeMap:红黑树结构,可通过传入比较器进行排序
(7)HashSet:基于HashMap,存放的是HashMap的key,所以元素不能重复
(8)TreeSet:基于TreeMap,存放的是TreeMap的key,可以通过比较器实现自定义排序
(9)SparseArray:底层实现是数组+数组,以key-value形式存放,key只能是int类型。一个数组存放key,另外一个数组存放value
mKeys[i] = key;mValues[i] = value;
(10)ArrayMap:数据结构跟SparseArray一样,key跟value可以是任意类型。一个数组存放key的hash值,另外一个数组存放key跟value
mHashes[index] = hash;mArray[index<<1] = key; mArray[(index<<1)+1] = value;
41、进程类型:
(1)前台进程
(2)可见进程
(3)服务进程
(4)后台进程
(5)空进程
进程保活方式:启动一个1像素的Activity;将服务设置成前台应用
42、堆的数据结构:堆是一种树,由它实现的优先级队列的插入与删除的时间复杂度都是O(logn)
43、优先队列里面实现了比较器,进而控制队列中元素的顺序
44、屏幕适配方案:
(1)百分比适配
- 以某一分辨率为基准,生成所有分辨率对应像素列表
- 将生成的像素列表存放在res目录下对应的values文件下
- 根据UI设计师给出的设计图上的尺寸,找到对应像素的单位
缺点:
- 如果某个分辨率缺失,将无法完成该屏幕的适配
- 过多的分辨率像素描述xml将增加软件包大小和维护难度
45、transient关键字可保证对象不被序列化
46、堆、栈、方法区
堆:主要存放对象实例及数组,所有new出来的对象
栈:存放基本数据类型及对象引用地址。每个方法被执行时会产生一个栈帧,方法执行时栈帧入栈,方法执行完毕栈帧出栈
方法区:存放类的信息(类名、修饰符)、静态变量、常量、构造函数、类中的变量和方法
47、android6.0、7.0、8.0、9.0版本适配
48、横竖屏切换activity的生命周期
onPause、onSaveInstanceState(保存状态)、onStop、onDestroy、onCreate、onStart、onRestoreInstanceState(恢复状态)、onResume
当按了home键时,会调用onSaveInstanceState,就是调用onStop之前没有调用finish,就会调用onSaveInstanceState方法
49、http请求中Connection: Keep-Alive,用于建立长连接,避免了连接建立与释放的开销。服务器通过客户端传过来的Content-Length来判断数据是否全部接收,当服务器明确知道返回内容的长度,可设置Content-Length来控制请求的结束,当服务器并不知道请求结果的长度时,会通过Transfer-Encoding: chunked告诉客户端返回的内容是分块的,最后,当浏览器收到长度为0的chunked时,就知道请求内容已全部接收
50、静态代理与动态代理
51、反射为什么比直接调用方法慢?
52、为什么主线程的Looper.loop()不会引起ANR?
53、进程与线程的区别
54、线程池的好处
55、LruCache的Lru算法是怎样的原理?
里面是使用了LinkedHashMap作为存储容器,存储空间满了后,会移除使用得最少的元素。
// 创建LinkedHashMap实例
new LinkedHashMap(
0, // 初始容量
0.75f,// 初始负载因子
true);// true为使用访问顺序,false使用插入顺序
每次put一个元素时,会把它放在链表的尾端,get一个元素时,会取出来再放到链表尾端。调用LruCache的put方法存储元素时,会先判断当前容量有没超过预定的存储空间,超过了就会删除最老的元素,直到占用空间不大于预定的值,源码调用了以下方法
Map.Entry toEvict = map.eldest();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
public Entry eldest() {
LinkedEntry eldest = header.nxt;
return eldest != header ? eldest : null;
}
就是把链表头的下一个元素删除,即存储的第一个元素。我们每次调用get方法后都会把元素移到链表尾端,使用得较少的元素位置会靠前,容量不足时会优先被删除。
56、进程间通信
57、RecyclerView复用机制
58、线程同步有哪几种?
59、线程join方法
当前线程等待调用join方法的线程执行完毕
60、根activity启动流程
应用内activity启动
61、onTouch、onTouchEvent、onClick的执行顺序
如果有设置onTouchListener,会先执行onTouch方法,onTouch方法没有处理,返回了false;就会执行onTouchEvent方法,而onClick是在onTouchEvent中执行的,如果onTouchEvent把触摸事件消费了,就不会执行到onClick方法。
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
// 执行onTouch方法
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 判断有没设置触摸监听,并且是否有消费掉触摸事件
if (!result && onTouchEvent(event)) {
result = true;
}
// onTouchEvent中会performClick执行点击操作,前面会有一系列的校验
62、属性动画配合贝赛尔曲线可实现添加购物车效果、qq消息粘性红点效果
githup地址
63、View的绘制流程:
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
// 绘制背景
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
// 如果是ViewGroup,没设置背景,就不会调用onDraw方法
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
// 通知孩子,绘制自己
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
对于最后一步的绘制,如果是ViewGroup,一般是在dispatchDraw中进行绘制,View的话,就是在onDraw方法
64、和AMS相关的操作都是异步的,因为AMS最终会调用ActivityThread里面的Handler来处理,所以startActivity也是异步的
65、补间动画与属性动画