Android 开发必备知识点及面试题汇总(Android+Java,斩获 offer
把多个布局放到一个 activity 里面,现在可以用多 Fragment 来代替,只有在需要的时候才加载
Fragment,提高性能。
Fragment 的好处:
Fragment 可以使你能够将 activity 分离成多个可重用的组件,每个都有它自己的生命周期和 UI。
Fragment 可以轻松得创建动态灵活的 UI 设计,可以适应于不同的屏幕尺寸。从手机到平板电脑。
Fragment 是一个独立的模块,紧紧地与 activity 绑定在一起。可以运行中动态地移除、加入、交换等。
Fragment 提供一个新的方式让你在不同的安卓设备上统一你的 UI。
Fragment 解决 Activity 间的切换不流畅,轻量切换。
Fragment 替代 TabActivity 做导航,性能更好。
Fragment 在 4.2.版本中新增嵌套 fragment 使用方法,能够生成更好的界面效果
3.如何切换 fragement,不重新实例化
正确的切换方式是 add(),切换时 hide(),add()另一个 Fragment;再次切换时,只需 hide()当前, show()另一个
1.Activity 和 Fragment 生命周期有哪些?
Activity——onCreate->onStart->onResume->onPause->onStop->onDestroy
Fragment——onAttach->onCreate->onCreateView->onActivityCreated->onStart->onResume->onPause->onStop->onDestroyView->onDestroy->onDetach
2.广播的两种注册方式及有什么区别
3.内存不足时,怎么保持 Activity 的一些状态,在哪个方法里面做具体操作?
Activity 的 onSaveInstanceState() 和 onRestoreInstanceState()并不是生命周期方法,它们不同于 onCreate()、onPause()等生命周期方法,它们并不一定会被触发。当应用遇到意外情况(如:内存不足、用户直接按 Home 键)由系统销毁一个 Activity,onSaveInstanceState() 会被调用。但是当用户主动去销毁一个 Activity 时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。除非该 activity 是被用户主动销毁的,通常 onSaveInstanceState()只适合用于保存一些临时性的状态,而 onPause()适合用于数据的持久化保存。
4.启动 service 的两种方法?有什么区别?
一种是 startService(),另一种是 bindService()。这两者的区别是第一种方式调用者开启了服务,即会与服务失去联系,两者没有关联。即使访问者退出了,服务仍在运行。如需解除服务必须显式的调用 stopService 方法。主要用于调用者与服务没有交互的情况下,也就是调用者不需要获取服务里的业务方法。比如电话录音。而后者调用者与服务绑定在一起的。当调用者退出的时候,服务也随之退出。用于需要与服务交互。
5.Android 中的 Context, Activity,Appliction 有什么区别?
相同:Activity 和 Application 都是 Context 的子类。
Context 从字面上理解就是上下文的意思,在实际应用中它也确实是起到了管理上下文环境中各个参数和变量的总用,方便我们可以简单的访问到各种资源。
不同:维护的生命周期不同。 Context 维护的是当前的 Activity 的生命周期,Application 维护的是整个项目的生命周期。
使用 context 的时候,小心内存泄露,防止内存泄露,注意一下几个方面:
不要让生命周期长的对象引用 activity context,即保证引用 activity 的对象要与 activity 本身生命周期是一样的。
对于生命周期长的对象,可以使用 application,context。
避免非静态的内部类,尽量使用静态类,避免生命周期问题,注意内部类对外部对象引用导致的生命周期变化。
6.Context 是什么?
它描述的是一个应用程序环境的信息,即上下文。
该类是一个抽象(abstract class)类,Android 提供了该抽象类的具体实现类(ContextIml)。
通过它我们可以获取应用程序的资源和类,也包括一些应用级别操作,例如:启动一个 Activity,发送广播,接受 Intent,信息,等。
7.Service 是否在 main thread 中执行, service 里面是否能执行耗时的操作?
默认情况,如果没有显示的指 servic 所运行的进程, Service 和 activity 是运行在当前 app 所在进
程的 main thread(UI 主线程)里面。
service 里面不能执行耗时的操作(网络请求,拷贝数据库,大文件 )
特殊情况 ,可以在清单文件配置 service 执行所在的进程 ,让 service 在另外的进程中执行 ? 1 2 3 4 5
8.Activity 怎么和 Service 绑定,怎么在 Activity 中启动自己对应的 Service?
Activity 通过 bindService(Intent service, ServiceConnection conn, int flags)跟 Service 进行绑定,当绑定成功的时候 Service 会将代理对象通过回调的形式传给 conn,这样我们就拿到了 Service 提供的服务代理对象。
在 Activity 中可以通过 startService 和 bindService 方法启动 Service。一般情况下如果想获取 Service 的服务对象那么肯定需要通过 bindService()方法,比如音乐播放器,第三方支付等。如果仅仅只是为了开启一个后台任务那么可以使用 startService()方法。
9.说说 Activity、Intent、Service 是什么关系
他们都是 Android 开发中使用频率最高的类。其中 Activity 和 Service 都是 Android 四大组件之一。他俩都是 Context 类的子类 ContextWrapper 的子类,因此他俩可以算是兄弟关系吧。不过兄弟俩各有各自的本领,Activity 负责用户界面的显示和交互,Service 负责后台任务的处理。Activity 和 Service 之间可以通过 Intent 传递数据,因此可以把 Intent 看作是通信使者。
10.请描述一下 BroadcastReceiver
BroadCastReceiver 是 Android 四大组件之一,主要用于接收系统或者 app 发送的广播事件。
广播分两种:有序广播和无序广播。
内部通信实现机制:通过 Android 系统的 Binder 机制实现通信。
无序广播:完全异步,逻辑上可以被任何广播接收者接收到。优点是效率较高。缺点是一个接收者不
能将处理结果传递给下一个接收者,并无法终止广播 intent 的传播。
有序广播:按照被接收者的优先级顺序,在被接收者中依次传播。比如有三个广播接收者 A,B,C,
优先级是 A > B > C。那这个消息先传给 A,再传给 B,最后传给 C。每个接收者有权终止广播,比如 B 终止广播,C 就无法接收到。此外 A 接收到广播后可以对结果对象进行操作,当广播传给 B 时,B 可以从结果对象中取得 A 存入的数据。
在通过 Context.sendOrderedBroadcast(intent, receiverPermission, resultReceiver, scheduler,initialCode, initialData, initialExtras)时我们可以指定 resultReceiver 广播接收者,这个接收者我们可以认为是最终接收者,通常情况下如果比他优先级更高的接收者如果没有终止广播,那么他的 onReceive 会被执行两次,第一次是正常的按照优先级顺序执行,第二次是作为最终接收者接收。
如果比他优先级高的接收者终止了广播,那么他依然能接收到广播
11.为什么要用 ContentProvider?它和 sql 的实现上有什么差别?
ContentProvider 屏蔽了数据存储的细节,内部实现对用户完全透明,用户只需要关心操作数据的 uri 就可以了,ContentProvider 可以实现不同 app 之间共享。
Sql 也有增删改查的方法,但是 sql 只能查询本应用下的数据库。而 ContentProvider 还可以去增删改查本地文件. xml 文件的读取等。
12.说说 ContentProvider、ContentResolver、ContentObserver 之间的关系
a. ContentProvider 内容提供者,用于对外提供数据
b. ContentResolver.notifyChange(uri)发出消息
c. ContentResolver 内容解析者,用于获取内容提供者提供的数据
d. ContentObserver 内容监听器,可以监听数据的改变状态
e. ContentResolver.registerContentObserver()监听消息。
1.onInterceptTouchEvent()和 onTouchEve
nt()的区别
onInterceptTouchEvent()用于拦截触摸事件
onTouchEvent()用于处理触摸事件
2.RemoteView 在哪些功能中使用
APPwidget 和 Notification 中 SurfaceView 和 View 的区别是什么?
SurfaceView 中采用了双缓存技术,在单独的线程中更新界面 View 在 UI 线程中更新界面
4.View 的绘制过程
一个 View 要显示在界面上,需要经历一个 View 树的遍历过程,这个过程又可以分为三个过程,也就是自定义 View 中的三要素:大小,位置,画什么,即 onMesure(),onLayout(),onDraw()。
1.onMesure()确定一个 View 的大小;
2.onLayout()确定 View 在父节点上的位置;
3.onDraw()绘制 View 的内容;
5.如何自定义 ViewGroup
1.指定的 LayoutParams
2.onMeasure 中计算所有 childView 的宽和高,然后根据 childView 的宽和高,计算自己的宽和高。(当然,如果不是 wrap_content,直接使用父 ViewGroup 传入的计算值即可)
3.onLayout 中对所有的 childView 进行布局。
6.View 中 onTouch,onTouchEvent,onClick 的执行顺序
dispatchTouchEvent—->onTouch—->onTouchEvent—–>onClick。在所有 ACTION_UP 事件之后才触发 onClick 点击事件。
1.ListView 卡顿的原因与性能优化,越多越好
重用 converView: 通过复用 converview 来减少不必要的 view 的创建,另外 Infalte 操作会把 xml 文件实例化成相应的 View 实例,属于 IO 操作,是耗时操作。
减少 findViewById()操作: 将 xml 文件中的元素封装成 viewholder 静态类,通过 converview 的 setTag 和 getTag 方法将 view 与相应的 holder 对象绑定在一起,避免不必要的 findviewbyid 操作
避免在 getView 方法中做耗时的操作: 例如加载本地 Image 需要载入内存以及解析 Bitmap ,都是比较耗时的操作,如果用户快速滑动 listview,会因为 getview 逻辑过于复杂耗时而造成滑动卡顿现象。用户滑动时候不要加载图片,待滑动完成再加载,可以使用这个第三方库 glideItem 的布局层次结构尽量简单,避免布局太深或者不必要的重绘
尽量能保证 Adapter 的 hasStableIds() 返回 true 这样在 notifyDataSetChanged() 的时候,如果 item 内容并没有变化,ListView 将不会重新绘制这个 View,达到优化的目的
在一些场景中,ScollView 内会包含多个 ListView,可以把 listview 的高度写死固定下来。 由于 ScollView 在快速滑动过程中需要大量计算每一个 listview 的高度,阻塞了 UI 线程导致卡顿现象出现,如果我们每一个 item 的高度都是均匀的,可以通过计算把 listview 的高度确定下来,避免卡顿现象出现
使用 RecycleView 代替 listview: 每个 item 内容的变动,listview 都需要去调用 notifyDataSetChanged 来更新全部的 item,太浪费性能了。RecycleView 可以实现当个 item 的局部刷新,并且引入了增加和删除的动态效果,在性能上和定制上都有很大的改善
ListView 中元素避免半透明: 半透明绘制需要大量乘法计算,在滑动时不停重绘会造成大量的计算,在比较差的机子上会比较卡。 在设计上能不半透明就不不半透明。实在要弄就把在滑动的时候把半透明设置成不透明,滑动完再重新设置成半透明。
尽量开启硬件加速: 硬件加速提升巨大,避免使用一些不支持的函数导致含泪关闭某个地方的硬件加速。当然这一条不只是对 ListView。
2.如何避免 OOM 问题的出现
使用更加轻量的数据结构 例如,我们可以考虑使用 ArrayMap/SparseArray 而不是 HashMap 等传统数据结构。通常的 HashMap 的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录 Mapping 操作。另外,SparseArray 更加高效,在于他们避免了对 key 与 value 的自动装箱(autoboxing),并且避免了装箱后的解箱。
避免在 Android 里面使用 Enum Android 官方培训课程提到过“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”,具体原理请参考《Android 性能优化典范(三)》,所以请避免在 Android 里面使用到枚举。
减小 Bitmap 对象的内存占用 Bitmap 是一个极容易消耗内存的大胖子,减小创建出来的 Bitmap 的内存占用可谓是重中之重,,通常来说有以下 2 个措施: inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。 decode format:解码格式,选择 ARGB_6666/RBG_545/ARGB_4444/ALPHA_6,存在很大差异
Bitmap 对象的复用 缩小 Bitmap 的同时,也需要提高 BitMap 对象的复用率,避免频繁创建 BitMap 对象,复用的方法有以下 2 个措施 LRUCache : “最近最少使用算法”在 Android 中有极其普遍的应用。ListView 与 GridView 等显示大量图片的控件里,就是使用 LRU 的机制来缓存处理好的 Bitmap,把近期最少使用的数据从缓存中移除,保留使用最频繁的数据, inBitMap 高级特性:利用 inBitmap 的高级特性提高 Android 系统在 Bitmap 分配与释放执行效率。使用 inBitmap 属性可以告知 Bitmap 解码器去尝试使用已经存在的内存区域,新解码的 Bitmap 会尝试去使用之前那张 Bitmap 在 Heap 中所占据的 pixel data 内存区域,而不是去问内存重新申请一块区域来存放 Bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小
使用更小的图片 在涉及给到资源图片时,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用更小的图片。尽量使用更小的图片不仅可以减少内存的使用,还能避免出现大量的 InflationException。假设有一张很大的图片被 XML 文件直接引用,很有可能在初始化视图时会因为内存不足而发生 InflationException,这个问题的根本原因其实是发生了 OOM。
StringBuilder 在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用 StringBuilder 来替代频繁的“+”。
避免在 onDraw 方法里面执行对象的创建 类似 onDraw 等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的 gc,甚至是内存抖动。
避免对象的内存泄露
3.三级缓存的原理
从缓存中加载。
从本地文件中加载(数据库,SD)
从网络加载。
a.加载 bitmap 的时候无需考虑 bitmap 加载过程中出现的 oom(内存溢出)和 android 容器快速
滑动的时候出现的图片错位等现象。(16M)
b. 支持加载网络图片和本地图片。
c. 内存管理使用的 lru 算法(移除里面是有频率最少的对象),更好的管理 bitmap 的内存
1.讲一下 android 中进程的优先级?
前台进程
可见进程
服务进程
后台进程
空进程
2.介绍 Handle 的机制
Handler 通过调用 sendmessage 方法把消息放在消息队列 MessageQueue 中,Looper 负责把消息从消息队列中取出来,重新再交给 Handler 进行处理,三者形成一个循环
通过构建一个消息队列,把所有的 Message 进行统一的管理,当 Message 不用了,并不作为垃圾回收,而是放入消息队列中,供下次 handler 创建消息时候使用,提高了消息对象的复用,减少系统垃圾回收的次数
每一个线程,都会单独对应的一个 looper,这个 looper 通过 ThreadLocal 来创建,保证每个线程只创建一个 looper,looper 初始化后就会调用 looper.loop 创建一个 MessageQueue,这个方法在 UI 线程初始化的时候就会完成,我们不需要手动创建
3.Dalvik 虚拟机与 JVM 有什么区别
Dalvik 基于寄存器,而 JVM 基于栈。基于寄存器的虚拟机对于更大的程序来说,在它们编译的时候,花费的时间更短。
Dalvik 执行.dex 格式的字节码,而 JVM 执行.class 格式的字节码。
4.每个应用程序对应多少个 Dalvik 虚拟机
每一个 Android 应用在底层都会对应一个独立的 Dalvik 虚拟机实例,其代码在虚拟机的解释下得以执行 ,而所有的 Android 应用的线程都对应一个 Linux 线程
5.应用常驻后台,避免被第三方杀掉的方法
Service 设置成 START_STICKY kill 后会被重启(等待 5 秒左右),重传 Intent,保持与重启前一样
通过 startForeground 将进程设置为前台进程, 做前台服务,优先级和前台应用一个级别,除非在系统内存非常缺,否则此进程不会被 kill
双进程 Service: 让 2 个进程互相保护对方,其中一个 Service 被清理后,另外没被清理的进程可以立即重启进程
用 C 编写守护进程(即子进程) : Android 系统中当前进程(Process)fork 出来的子进程,被系统认为是两个不同的进程。当父进程被杀死的时候,子进程仍然可以存活,并不受影响(Android5.0 以上的版本不可行
联系厂商,加入白名单
6.根据自己的理解描述下 Android 数字签名。
所有的应用程序都必须有数字证书,Android 系统不会安装一个没有数字证书的应用程序
Android 程序包使用的数字证书可以是自签名的,不需要一个权威的数字证书机构签名认证
如果要正式发布一个 Android 程序,必须使用一个合适的私钥生成的数字证书来给程序签名,而不能使用 adt 插件或者 ant 工具生成的调试证书来发布。
数字证书都是有有效期的,Android 只是在应用程序安装的时候才会检查证书的有效期。如果程序已经安装在系统中,即使证书过期也不会影响程序的正常功能。
7.Dalvik 基于 JVM 的改进
几个 class 变为一个 dex,constant pool,省内存
Zygote,copy-on-write shared,省内存,省 cpu,省电
基于寄存器的 bytecode,省指令,省 cpu,省电
Trace-based JIT,省 cpu,省电,省内存
8.ARGB_8888 占用内存大小
本题的答案,是 4byte,即 ARGB 各占用 8 个比特来描述。
9.apk 安装卸载的原理
安装过程:复制 apk 安装包到 data/app 目录下,解压并扫描安装包,把 dex 文件(dalvik 字节码)保存到 dalvik-cache 目录,并 data/data 目录下创建对应的应用数据目录。
卸载过程:删除安装过程中在上述三个目录下创建的文件及目录。
10.通过 Intent 传递一些二进制数据的方法有哪些?
使用 Serializable 接口实现序列化,这是 Java 常用的方法。
实现 Parcelable 接口,这里 Android 的部分类比如 Bitmap 类就已经实现了,同时 Parcelable 在 Android AIDL 中交换数据也很常见的。
11.横竖屏切换时 Activity 的生命周期
此时的生命周期跟清单文件里的配置有关系。
不设置 Activity 的 android:configChanges 时,切屏会重新调用各个生命周期默认首先销毁当前 activity,然后重新加载。
设置 Activity android:configChanges=”orientation|keyboardHidden|screenSize”时,切屏不会重新调用各个生命周期,只会执行 onConfigurationChanged 方法
12.Serializable 和 Parcelable 的区别
在使用内存的时候,Parcelable 类比 Serializable 性能高,所以推荐使用 Parcelable 类。
Serializable 在序列化的时候会产生大量的临时变量,从而引起频繁的 GC。
Parcelable 不能使用在要将数据存储在磁盘上的情况。尽管 Serializable 效率低点,但在这
种情况下,还是建议你用 Serializable 。
13.Android 中如何捕获未捕获的异常
自 定 义 一 个 Application , 比 如 叫 MyApplication 继 承 Application 实 现
UncaughtExceptionHandler。
覆写 UncaughtExceptionHandler 的 onCreate 和 uncaughtException 方法。
14.Android 的权限规则
Android 中的 apk 必须签名
基于 UserID 的进程级别的安全机制
默认 apk 生成的数据对外是不可见的
AndroidManifest.xml 中的显式权限声明
15.多线程间通信和多进程之间通信有什么不同,分别怎么实现?
一、进程间的通信方式
**管道( pipe ):**管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
有名管道 (namedpipe): 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
信号量(semophore ): 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
消息队列( messagequeue ): 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
信号 (sinal ): 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
共享内存(shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
套接字(socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
二、线程间的通信方式
1. 锁机制:包括互斥锁、条件变量、读写锁
互斥锁提供了以排他方式防止数据结构被并发修改的方法。
读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁
的保护下进行的。条件变量始终与互斥锁一起使用。
2. 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
3. 信号机制(Signal):类似进程间的信号处理
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
16.说说 LruCache 底层原理
LruCache 使用一个 LinkedHashMap 简单的实现内存的缓存,没有软引用,都是强引用。如果添加的数据大于设置的最大值,就删除最先缓存的数据来调整内存。
maxSize 是通过构造方法初始化的值,他表示这个缓存能缓存的最大值是多少。
size 在添加和移除缓存都被更新值,他通过 safeSizeOf 这个方法更新值。safeSizeOf 默认返回 1,但一般我们会根据 maxSize 重写这个方法,比如认为 maxSize 代表是 KB 的话,那么就以 KB 为单位返回该项所占的内存大小。
除异常外首先会判断 size 是否超过 maxSize,如果超过了就取出最先插入的缓存,如果不为空就 删掉,并把 size 减去该项所占的大小。这个操作将一直循环下去,直到 size 比 maxSize 小或者缓存 空。
最后我想说:有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如 Handler 机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。
还有短短的一个月左右,金三银四得面试旺季就要到了,大家都希望趁着这个机会找到一个心仪的工作,但是不管怎么样,不论是什么样的大小面试,要想不被面试官虐的不要不要的,只有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊~
面试:如果不准备充分的面试,完全是浪费时间,更是对自己的不负责!
最后小编针对上述技术体系收录整理了一份相关的学习文档,特此跟大家分享,里面有阿里、腾讯、字节等大厂的最新面试题,还包含了一些 Android 技术点的知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
Android学习PDF+面试文档+学习笔记+Android思维脑图
【Android 开发核心知识点笔记】
评论