Android面试题(上)

一、Activity

(1)Activity的生命周期

  • onCreate() 表示Activity正在被创建做初始化工作。
  • onStart() 表示Activity正在启动,这时Activity可见但不在前台。无法与用户交互。
  • onResume() 表示Activity获取焦点,可见且在前台活动。可以与用户交互。
  • onPause() 表示Activity正在停止,可做数据存储,停止动画等操作。
  • onStop() 表示Activity即将停止,可做取消网络连接、注销广播接收器等。
  • onDestroy() 表示Activity即将销毁,可做释放资源等操作。
  • onRestart() 表示Activity重新启动,由不可见到可见时会调用,

(2)onStart()和onResume()/onPause()和onStop()的区别

  • onStart()与onStop()是从Activity是否可见这个角度调用
  • onResume()和onPause()是从Activity是否显示在前台这个角度来回调

(3)Activity A启动另一个Activity B会回调哪些方法?如果Activity B是完全透明呢?如果启动的是一个Dialog呢?

  • 会调用A 的onPause() -> B 的onCreate() -> onStart() -> onResume() -> A的onStop()
  • 不会调用A 的onStop()方法,Dialog同理。

(4)谈谈onSaveInstanceState()方法?何时会调用?

  • 当非人为终止Activity时会调用onSavaInstanceState()来保存状态。
  • 该方法在onStop()方法之前调用,与onPause()没有时序关系。

(5)onSaveInstanceState()与onPause()的区别?

  • onSaveInstanceState()适用于非人为终止Activity时临时性状态的保存。
  • onPause() 适用于对数据的持久化保存,停止动画等。

(6)说下Activity的四种启动模式?

  • standard标准模式:每次启动一个Activity都会创建一个新的实例,并放入栈顶位置。
  • singleTop栈顶复用模式:如果启动的Activity已经位于任务栈的栈顶,就不会重新创建实例,而是调用onNewIntent(intent)方法。反之创建新的实例加入栈中。
  • singleTask栈内复用模式:只要该Activity在一个任务栈中存在,就不会重新创建新的实例,并把栈中在其之上的其他Activity Destroy掉,调用onNewIntent(intent)方法。如果不存在,创建新的实例并入栈。
  • singleInstance单实例模式:Activity只能单独位于一个任务栈中,并且这个任务栈只存在这一个实例。

(7)谈谈singleTop和singleTask的区别以及应用场景

  • singleTop允许同个Activity在栈中可以有多个实例,即可以重复创建;为防止快速点击时多次startActivity,可以将目标Activity设置为singleTop。
  • singleTask同个Activity在栈中只有一个实例,即不存在重复创建;常用于主页和登陆页

(8)了解哪些Activity启动模式的标记位?

  • Intent.FLAG_ACTIVITY_NEW_TASK:使用一个新的Task来启动Activity
  • Intent.FLAG_ACTIVITY_SINGLE_TOP:类似singleTop
  • Intent.FLAG_ACTIVITY_CLEAR_TOP:类似singleTask
  • Intent.FLAG_ACTIVITY_NO_HISTORY:使用这种模式启动Activity,当该Activity启动其他Activity后,该Activity就消失了,不会保留在Task栈中。

(9)onNewIntent()调用时机?

  • 启动模式为singleTop或singleTask的Activity会出现回调onNewIntent()的情况

(10)如何避免配置改变时Activity重建?

  • 在AndroidManifest.xml中对应的Activity中设置android:configChanges="orientation|keyboardHidden|screenSize"。此时发生屏幕旋转,该Activity不会被系统杀死和重建,会调用onConfigurationChanged方法。

(11)优先级低的Activity在内存不足被回收后怎样做可以恢复到销毁前状态?

  • 低优先级的Activity在内存不足被回收后重新启动会引发Activity重建,会调用onRestoreInstanceState方法,并将onSaveInstanceState方法保存的Bunble对象作为参数传到onRestoreInstanceState 和 onCreate方法中。因此可通过onRestoreInstanceState(Bundle savedInstanceState)和onCreate((Bundle savedInstanceState)来判断Activity是否被重建,并取出数据进行恢复。在onCreate中需要判断savedInstanceState是否为空。

(12)如何启动其他应用的Activity?

  • 在保证有权限访问的情况下,通过隐式Intent进行目标Activity的IntentFilter匹配

(13)如何修改activity进入和退出动画
通过调用overridePendingTransition() 可以实时修改Activity的切换动画。该函数必须在调用startActivity()或者finish()后立即调用,且只有效一次。

(14)Activity创建和Dialog创建过程的异同?

  • 创建WindowDialog。和Activity类似,同样是通过PolicyManager.makeNewWindow()来实现。
  • 初始化DecorView并将Dialog的视图添加到DecorView中去。和Activity类似,同样是通过Window.setContentView() 来实现。
  • 将DecorView添加到Window中显示。和Activity一样,都是在自身要出现在前台时才会将添加Window。
  • Dialog.show()方法:完成DecorView的显示。
  • WindowManager.remoteViewImmediate()方法:当Dialog被dismiss时移除DecorView。

(15)Activity的启动过程?

  • execStartActivity调用ActivityManager.getService().startActivity方法,基于Binder机制,通过调用代理对象IActivityManager的方法,使得系统服务ActivityManagerService对应的startActivity方法被调用。
  • ActivityManagerService.startActivity方法经过重重方法会通过app.thread调用scheduleLaunchActivity方法,通过Binder机制,会使ApplicationThread.scheduleLaunchActivity方法被调用。
  • 基于Binder机制,实现了一次进程间的通信,将启动Activity的操作交给了ApplicationThread类。
  • scheduleLaunchActivity方法将启动Activity的消息发送并交由Handler H处理。Handler H对消息的处理会调用handleLaunchActivity()->performLaunchActivity()得以完成Activity对象的创建和启动。

二、Fragment

(1)谈一谈Fragment的生命周期?

  • Fragment生命周期方法:onAttach() -> onCreate() -> onCreateView() -> onActivityCreated() -> onStart() -> onResume() -> onPause() -> onStop() -> onDestroyView() -> onDestroy() -> onDetach()
  • onAttach() 当Fragment和Activity建立关联时调用
  • onCreateView() 当Fragment创建视图时调用
  • onActivityCreated() 当与Fragment相关联的Activity完成onCreate()之后调用
  • onDestroyView() 在Fragment中的布局被移除时调用
  • onDetach() 当Fragment和Activity解除关联时调用

(2)Activity和Fragment的异同?

  • 相同点:它们都可包含布局、可有自己的生命周期,Fragment可看似迷你活动。
  • 不同点:Fragment是依附在Activity上的,多了些和宿主Activity相关的生命周期方法,如onAttach()、onCreateView()、onActivityCreated()、onDestroyView()、onDetach();另外,Fragment的生命周期方法是由宿主Activity而不是操作系统调用的。

(3)Activity和Fragment的关系?

  • 它可作为Activity界面的组成部分,可在Activity运行中实现动态地加入、移除和交换。
  • 一个Activity中可同时出现多个Fragment,一个Fragment也可在多个Activity中使用。
  • Activity的FragmentManager负责调用队列中Fragment的生命周期方法,只要Fragment的状态与Activity的状态保持了同步,宿主Activity的FragmentManager便会继续调用其他生命周期方法以继续保持Fragment与Activity的状态一致。

(4)何时会考虑使用Fragment?

  • 用两个Fragment封装两个界面模块,这样只使一套代码就能适配两种设备,达到两种界面效果
  • 单一场景切换时使用Fragment更轻量化,如ViewPager和Fragment搭配使用

(5)Fragment与Activity之间通信

  • Fragment中设置回调接口,将事件处理交给实现接口的Activity实现。
  • 如果你Activity中包含自己管理的Fragment的引用。可以通过引用直接访问所有的Fragment的public方法
  • 在Fragment中可以通过getActivity得到当前绑定的Activity的实例,然后进行操作。

三、Service

(1)谈一谈Service的生命周期?

  • onCreate():服务第一次被创建时调用
  • onStartCommand():服务启动时调用
  • onBind():服务被绑定时调用
  • onUnBind():服务被解绑时调用
  • onDestroy():服务停止时调用

(2)Service的两种启动方式?区别在哪?

  • 第一种:startService()方法可以启动一个Service,并回调服务中的onStartCommand()方法。如果该服务之前还没创建,那么回调的顺序是onCreate() -> onStartCommand();如果服务是开启状态,在次调用startService()不会回调onCreate()方法。服务启动了之后会一直保持运行状,直到 stopService() 或 stopSelf() 方法被调用,服务停止并回调onDestroy()(无论调用多少次startService()方法,只需调用一次stopService()或stopSelf()方法,服务就会停止了)。
  • 第二种:bindService()方法可以绑定一个Service,并回调服务中的onBind()方法。如果该服务之前还没创建,那么回调的顺序是onCreate() -> onBind();如果服务是开启状态,在次调用startService()不会回调onCreate()方法。调用方可以获取到onBind()方法里返回的IBinder对象的实例,从而实现和服务进行通信。只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态,直到调用了 unbindService()方法服务会停止,回调顺序onUnBind() -> onDestroy()。

(3)一个Activty先start一个Service后,再bind时会回调什么方法?此时如何做才能回调Service的onDestroy()方法?

  • 这两种启动方法并不冲突,startService()启动Service之后,再bindService()绑定,此时只会回调onBind()方法
  • 需要同时调用 stopService()和 unbindService()方法才能让服务销毁掉。

(4)Service如何和Activity进行通信?

  • 通过bindService()可以实现Activity调用Service中的方法。
  • 通过广播实现Service向Activity发送消息。

(5)用过哪些系统Service?

  • WINDOW_SERVICE 管理打开的窗口程序
  • LAYOUT_INFLATER_SERVICE 取得XML里定义的View
  • ACTIVITY_SERVICE 管理应用程序的系统状态
  • POWER_SERVICE 电源服务
  • ALARM_SERVICE 闹钟服务
  • NOTIFICATION_SERVICE 状态栏服务
  • KEYAUARD_SERVICE 键盘锁服务

(6)是否能在Service进行耗时操作?如果非要可以怎么做?

  • Service默认并不会运行在子线程中,也不运行在一个独立的进程中,它同样执行在主线程中(UI线程)。
  • 手动打开一个子线程,否则有可能出现主线程被阻塞(ANR)的情况。

(7)前台服务是什么?和普通服务的不同?如何去开启一个前台服务?

  • 前台服务的服务状态可以被用户看到。它和普通服务最大的区别是,前者会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,且当系统内存不足服务被杀死时,通知会被移除。
  • 创建一个Notification实例,调用startForeground()方法,不需要NotificationManager将通知显示出来。

(8)是否了解ActivityManagerService,谈谈它发挥什么作用?

  • ActivityManagerService是Android中最核心的服务 , 主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作。

(9)如何保证Service不被杀死?

  • 在Service的onStartCommand()中设置flages值为START_STICKY,使得Service被杀死后尝试再次启动Service
  • 提升Service优先级,比如设置为一个前台服务
  • 在Activity的onDestroy()通过发送广播,并在广播接收器的onReceive()中启动Service。

(10)Service启动过程

  • startService调用startServiceCommon方法内部ActivityManager.getService()是IActivityManager的代理对象,调用代理对象的startService方法,会向系统服务ActivityManagerService发起请求,基于Binder机制,调用ActivityManagerService的startService方法。
  • ActivityManagerService.startService方法经过重重方法会通过app.thread调用scheduleCreateService方法,通过Binder机制,会使ApplicationThread.scheduleCreateService方法被调用。
  • scheduleCreateService方法将启动Service的消息发送并交由Handler H处理。Handler H对消息的处理会调用handleCreateService启动Service。

四、BroadcastReceiver

(1)广播有几种形式?什么特点?

  • 普通广播:一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因此它们接收的先后是随机的。
  • 有序广播:sendOrderedBroadcast一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递,所以此时的广播接收器是有先后顺序的,且优先级(priority)高的广播接收器会先收到广播消息。有序广播可以被接收器截断使得后面的接收器无法收到它。
  • 本地广播:LocalBroadcastManager发出的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收本应用程序发出的广播。
  • 粘性广播:sendStickyBroadcast这种广播会一直滞留,当有匹配该广播的接收器被注册后,该接收器就会收到此条广播。

(2)广播的两种注册形式?区别在哪?

  • 静态注册:无需担忧广播接收器是否被关闭,只要设备是开启状态,广播接收器也是打开着的。
  • 动态注册:广播接收器必须要在程序启动之后才能接收到广播。当用来注册广播的Activity关掉后,广播也就失效了

(3)广播的启动过程
registerReceiver调用registerReceiverInternal方法内部ActivityManager.getService()是IActivityManager的代理对象,调用代理对象的registerReceiver方法,会向系统服务ActivityManagerService发起请求,基于Binder机制,调用ActivityManagerService的registerReceiver方法。

五、ContentProvider & 数据存储

(1)ContentProvider了解多少?

  • 通过ContentResolver覆盖增删改查方法实现对数据库的访问。
  • ContentProvider可以让不同应用程序之间进行数据共享,它还可以选择只对哪一部分数据进行共享,从而保证程序中的隐私数据不会有泄漏风险。

(2)Android中提供哪些数据持久存储的方法?

  • File 文件存储:Context类中提供了openFileInput()和openFileOutput()方法来打开数据文件里的文件IO流。
  • SharedPreferences存储:一种轻型的数据存储方式,常用来存储一些简单的配置信息,基于XML文件存储key-value键值对数据。
  • SQLite数据库存储:一款轻量级的关系型数据库,它的运算速度非常快,占用资源很少,在存储大量复杂的关系型数据的时可以使用。
  • ContentProvider:四大组件之一,用于数据的存储和共享,不仅可以让不同应用程序之间进行数据共享,还可以选择只对哪一部分数据进行共享,可保证程序中的隐私数据不会有泄漏风险。

(3)Java中的I/O流读写怎么做?

//填入文件名和操作模式
FileOutputStream fos = openFileOutput("a.txt", MODE_PRIVATE);
//通过write()函数写入数据。
fos.write(string.getBytes());
fos.close();

FileInputStream fis = openFileInput("a.txt");
StringBuilder sb = new StringBuilder();
byte[] buffer = new byte[1024];
int len = 0;
//读取字节
while ((len = fis.read(buffer)) != -1) {
    sb.append(new String(buffer, 0, len));
}
string = sb.toString();
fis.close();

(4)SharePreferences适用情形?使用中需要注意什么?

  • SharePreferences是一种轻型的数据存储方式,适用于存储一些简单的配置信息,如int、string、boolean、float和long。由于系统对SharedPreferences的读/写有一定的缓存策略,即在内存中有一份该文件的缓存,因此在多进程模式下,其读/写会变得不可靠,甚至丢失数据。

(5)SharePreferences的注意事项

  • SP文件不宜过大,如果SP文件需要存储的内容过多,可以根据不同的功能划分成多个文件;
  • 如果可以的话尽可能早的调用getSharedPreferences,这样在调用put和get操作时,文件已经被读取到内存中了;
  • 不要多次调用edit(), 应该调用一次edit(),因为每次调用edit()都会新建一个Editor;
  • 不要多次调用commit()或apply(),如果多次存入值,应该在最后一次调用。

(6)SharePreferences的优缺点

  • SharedPreferences对象本身只能获取数据,而不支持存储和修改,存储和修改是通过SharedPreferences.edit()获取的内部类接口Editor对象实现。
  • SharedPreferences对象与SQLite数据库相比显得格外轻量级,免去了创建数据库,创建表,写SQL语句等诸多操作,相对而言更加方便,简洁。
  • 只能存储boolean,int,float,long和String五种简单的数据类型。无法进行条件查询等。

(7)SharedPreferences源码实现

  • 通过getSharedPreferences方法获取file文件并返回SharedPreferences实例对象
  • SharedPreferencesImpl构造方法备份file
  • startLoadFromDisk方法开启一个线程从磁盘读取数据,将xml文件转成map。
  • 修改值只会将其存到mModified的map中去,所以在编辑器中所做的所有更改都会批处理,直到我们调用commit或apply才会设置到mMap和xml文件中。

(8)了解SQLite中的事务处理吗?是如何做的?

  • SQLite在做CRDU操作时都默认开启了事务,然后把SQL语句翻译成对应的SQLiteStatement并调用其相应的CRUD方法,此时整个操作还是在rollback journal这个临时文件上进行,只有操作顺利完成才会更新.db数据库,否则会被回滚。

(9)使用SQLite做批量操作有什么好的方法吗?

  • 使用SQLiteDatabase的beginTransaction()方法开启一个事务,将批量操作SQL语句转化成SQLiteStatement并进行批量操作,结束后endTransaction()

(10)如果现在要删除SQLite中表的一个字段如何做?

  • SQLite数据库只允许增加表字段而不允许修改和删除表字段,只能采取复制表思想,即创建一个新表保留原表想要的字段、再将原表删除。

(11)使用SQLite时会有哪些优化操作?

  • 使用事务做批量操作。
  • 及时关闭Cursor,避免内存泄漏
  • 耗时操作异步化:数据库的操作属于本地IO,通常比较耗时,建议将这些耗时操作放入异步线程中处理
  • ContentValues的容量调整:ContentValues内部采用HashMap来存储Key-Value数据,ContentValues初始容量为8,扩容时翻倍。因此建议对ContentValues填入的内容进行估量,设置合理的初始化容量,减少不必要的内部扩容操作
  • 使用索引加快检索速度:对于查询操作量级较大、业务对查询要求较高的推荐使用索引

(12)SQLite创建源码解析

  • SQLiteOpenHelper主要完成SQLiteDatabase 的创建、关闭、升/降级、配置等相关辅助管理
  • SQLiteDatabase.openDatabase经过层层调用创建当前SQLiteDatabase的数据库连接池SQLiteConnectionPoo
  • SQLiteConnectionPoo中通过setupIdleConnectionHandler超时管理的Handler

六、集合

(1)ArrayList集合

  • ArrayList是基于数组实现的list类,底层的数据结构使用的是数组结构,数据也是有序的,按照插入的顺序来排序。
  • ArrayList容量可实现动态增长其实是对数组的动态扩充,不指定的话长度默认为10,默认每次扩容为原来的1.5倍加一。
  • 增加元素可能会进行扩容,而删除元素却不会进行缩容。合理使用减少扩容的次数,提高性能,list.ensureCapacity();扩充原有的数组
  • 修改或查询数据相对快。添加或删除数据相对慢,因为需要移动大量的数据。ArrayList是线程不安全的。

(2)LinkedList集合

  • LinkedList是基于链表实现的双向链表数据结构。它每一个节点都包含三方面的内容:节点本身的数据、前一个节点的信息、下一个节点的信息。
  • 增加或删除数据相对快。修改或查询数据相对慢,因为需要遍历查找
  • 单向链表:单向链表就是通过每个结点的指针指向下一个结点从而链接起来的结构,最后一个节点的next指向null。
  • 单向循环链表:单向循环链表和单向列表的不同是,最后一个节点的next不是指向null,而是指向head节点。
  • 双向链表:双向链表是包含两个指针的,pre指向前一个节点,next指向后一个节点,但是第一个节点head的pre指向null,最后一个节点的next指向null。
  • 双向循环链表:双向循环链表和双向链表的不同在于,第一个节点的pre指向最后一个节点,最后一个节点的next指向第一个节点。

(3)Vector集合

  • Vector底层结构是数组,比ArrayList开销更大,访问更慢;默认初始容量为10,默认每次扩容为原来的2倍。
  • synchronized实现同步,保证线程安全,Vector是线程安全的ArrayList。

(4)HashSet集合

  • HashSet无序不允许集合元素的重复,可以存储null。底层数据结构是哈希表。根据hashCode来决定存储位置的。非线程安全,存取速度快。
  • 通过hashCode和equals来保证元素的唯一性。如果元素的hashCode相同才会判断equals是否为true。如果hashCoed的值不同,则不会调用equals。

(5)TreeSet

  • TreeSet不允许集合元素的重复。底层数据结构是二叉树,线程不安全。可以对Set集合中的元素进行排序
  • TreeSet的排序让元素自身具备比较性,元素需要实现comparable接口,覆盖compareTo方法,如果两个对象通过compareTo()方法比较相等,那么新的元素将无法添加到TreeSet集合中。
  • 当元素本身不具备比较性,或者具备的比较性不是所需的,就需要让集合自身具备比较性,实现Comparator接口,实现compare方法。将实现类传入TreeSet。

(6)LinkedHashSet集合

  • LinkedHashSet不允许集合元素的重复。集合也是根据元素的hashCode值来决定元素的存储位置,但和hashSet不同的是它同时使用链表维护元素的次序,这样使得元素看起来是以插入的顺序保存的。linkedHashSet按元素的添加顺序来访问集合里的元素。(LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet的性能,但是在遍历Set里面的全部元素将有很好的性能)。

(7)TreeMap集合

  • TreeMap是平衡排序二叉树(红黑树)结构,按自然排序或比较器存入元素以保证元素顺序。TreeMap的键保唯一性,取决于比较方法和比较器。非线程安全。

(8)HashMap

  • HashMap是链表散列的数据结构,即数组和链表的结合体,非线程安全,不保证存取顺序,允许null键或者null值。HashMap的键保持唯一性,取决于hashCode以及equals方法。效率较高。
  • 初始容量为16,扩容时是当前容量翻倍。

(9)HashTable

  • HashTable时哈希表结构,不保证存取顺序,不允许null键或者null值,线程安全,效率较低,已经被HashMap替代
  • 初始容量为11,扩容为原来的2倍加1

(10)LinkedHashMap

  • LinkedHashMap是带双向链表的哈希表结构,保持存取顺序,允许null键和null值,非线程安全。
  • 内部通过Entry维护了一个双向链表,负责维护Map的迭代顺序。

(11)数组、集合、泛型的关系,并比较

  • 数组元素可以是基本类型,也可以是对象;数组长度限定;数组只能存储一种类型的数据元素
  • 集合元素只能是对象;集合长度可变;集合可存储不同种的数据元素
  • 泛型相比与集合的好处在于它安全简单。提供编译时的强类型检查,而不用等到运行;可避免类类型强制转换

(12)ArrayList和LinkList的区别?

  • ArrayList底层结构是数组,可用索引实现快速查找和修改;容量可实现动态增长。
  • LinkedList底层结构是双向循环链表,增删速度快。

**(13)ArrayList和Vector的区别?

  • ArrayList非线程安全,默认初始容量为10,每次扩容为原来的1.5倍。
  • Vector使用了synchronized关键字,是线程安全的,比ArrayList开销更大,访问更慢;默认初始容量为10,默认每次扩容为原来的2倍。

(14)HashSet和TreeSet的区别?

  • HashSet底层采用HashMap来保存元素;不能保证元素的排列顺序;有良好的存取和查找性能;不保证元素的顺序,没有重复元素的集合,而且HashSet允许使用 null 元素。
  • TreeSet采用红黑树的数据结构来存储集合元素;根据元素实际值的大小进行排序;
  • 前者compareTo()比较两个元素之间大小关系,后者compare()比较两个元素之间大小关系。

(15)HashMap和Hashtable的区别?

  • HashMap非线程安全;允许存在一个为null的key和任意个为null的value;采用链表散列的数据结构,即数组和链表的结合;初始容量为16,填充因子默认为0.75,扩容时是当前容量翻倍。
  • Hashtable线程安全,开销比HashMap大,不允许使用null作为key和value;底层基于哈希表结构;初始容量为11,填充因子默认为0.75,扩容时是容量翻倍+1。

(15)HashMap在put、get元素的过程?体现了什么数据结构?

  • 向HashMap中put元素时,首先判断key是否为空,为空则直接调用putForNullKey(),不为空则计算key的hash值得到该元素在数组中的下标值;如果数组在该位置处没有元素,就直接保存;如果有,还要比较是否存在相同的key,存在的话就覆盖原来key的value,否则将该元素保存在链头,先保存的在链尾。
  • 从HashMap中get元素时,计算key的hash值找到在数组中的对应的下标值,返回该key对应的value即可,如果有冲突就遍历该位置链表寻找key相同的元素并返回对应的value。

(16)HashMap是有序的吗?如何实现有序?

  • HashMap是无序的,而LinkedHashMap是有序的HashMap,默认为插入顺序,还可以是访问顺序,基本原理是其内部通过Entry维护了一个双向链表,负责维护Map的迭代顺序。

(17)HashMap是如何扩容的?如何避免扩容?

  • HashMap几个默认值,初始容量为16、填充因子默认为0.75、扩容时容量翻倍。也就是说当HashMap中元素个数超过160.75=12时会把数组的大小扩展为216=32,然后重新计算每个元素在数组中的位置。
  • 由于每次扩容还需要重新计算元素Hash值,损耗性能,所以建议在使用HashMap时,最好先估算Map的大小,设置初始值,避免频繁扩容。

(18)hashCode()的作用,与equal()有什么区别?

  • equals()比较两个对象的地址值是否相等 ;hashCode()得到的是对象的存储位置,可能不同对象会得到相同值
  • 有两个对象,若equals()相等,则hashcode()一定相等;hashcode()不等,则equals()一定不相等;hashcode()相等,equals()可能相等、可能不等。
  • 使用equals()比较两个对象是否相等效率较低,最快办法是先用hashCode()比较,如果hashCode()不相等,则这两个对象肯定不相等;如果hashCode()相等,此时再用equal()比较,如果equal()也相等,则这两个对象的确相等。

七、Hander

(1)谈谈消息机制Hander?有哪些要素?流程是怎样的?

  • 负责跨线程通信,这是因为在主线程不能做耗时操作,而子线程不能更新UI,所以当子线程中进行耗时操作后需要更新UI时,通过Handler将有关UI的操作切换到主线程中执行。

  • Handler:负责发送(Handler.sendMessage())和处理(Handler.handleMessage())Message消息。按照先进先出执行,内部使用的是单链表的结构。

  • Message:需要被传递的消息,消息中包含了消息ID、数据、处理消息对象等数据。

  • MessageQueue:负责存储和管理Handler发送过来的Message,内部通过单链表维护消息队列,插入和删除上有优势。在next方法中从消息队列MessageQueue中阻塞式地取出一个Message。

  • Looper:负责关联线程以及消息的分发,在该线程下从MessageQueue获取Message,分发给Handler,Looper创建的时候会创建一个 MessageQueue,调用loop()方法的时候消息循环开始,其中会不断调用messageQueue的next()方法,当有消息就处理,否则阻塞在messageQueue的next()方法中。当Looper的quit()被调用的时候会调用messageQueue的quit(),此时next()会返回null,然后loop()方法也就跟着退出。

  • 在主线程创建的时候会创建一个Looper,同时也会在在Looper内部创建一个消息队列。而在创键Handler的时候取出当前线程的Looper,并通过该Looper对象获得消息队列,然后Handler在子线程中通过MessageQueue.enqueueMessage在消息队列中添加一条Message。通过Looper.loop() 开启消息循环不断轮询调用 MessageQueue.next(),取得对应的Message并且通过Handler.dispatchMessage传递给Handler,最终调用Handler.handlerMessage处理消息。

(2)Message可以如何创建?哪种效果更好,为什么?

  • Message msg = new Message();
  • Message msg = Message.obtain();
  • Message msg = handler.obtainMessage();
  • 后两种方法都是从整个Messge池中返回一个新的Message实例,能有效避免重复Message创建对象,因此更鼓励这种方式创建Message

(3)Looper死循环为什么不会导致应用卡死?

  • 主线程的主要方法就是消息循环,一旦退出消息循环,那么你的应用也就退出了,Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。
  • 导致ANR的不是主线程阻塞,而是主线程的Looper消息处理过程发生了任务阻塞,无法响应手势操作,不能及时刷新UI。虽然主线程在没有消息可处理的时候是阻塞的,但是只要保证有消息的时候能够立刻处理,程序是不会无响应的。

(4)为什么系统不建议在子线程访问UI?

  • UI控件不是安全的线程,在多线程中并发访问可能会导致UI控件处于不可预期的状态。而不对UI控件的访问加上锁机制的原因上锁会让UI控件变得复杂和低效、上锁后会阻塞某些进程的执行。

(5)一个Thread能否创建多个Handler,创建多个Looper,Handler跟Looper之间的对应关系?

  • 一个Thread只能有一个Looper,一个MessageQueen,可以有多个Handler
    MessageQueue可以处理来自多个Handler的Message
  • 一个Looper可以关联多个Handler,一个Handler只能关联一个Looper。

(6)可以在子线程直接new一个Handler吗?那该怎么做?

  • 不可以,因为在主线程中内部包含一个Looper,直接创建Handler会自动关联Looper,处理消息。
  • 由于子线程的Looper需要手动去创建,所以要在子线程创建Handler要先创建Looper,并开启Looper循环

(7)使用Hanlder的postDealy()后消息队列会发生什么变化?
这个Message并不是先等待一定时间再放入到MessageQueue中,而是直接进入并阻塞当前线程,到时间就唤醒它。如果此时要加入新消息,该消息队列的队头跟delay时间相比更长,则插入到头部,按照触发时间进行排序,队头的时间最小、队尾的时间最大

(8)Handler 引起的内存泄露原因以及最佳解决方案

  • Handler允许我们发送延时消息,如果在延时期间用户关闭了Activity,那么该Activity会泄露。 这个泄露是因为Message会持有Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity会被Handler持有,这样最终就导致Activity泄露。
  • 将Handler定义成静态的内部类,在内部持有Activity的弱引用,并在Acitivity的onDestroy()中调用handler.removeCallbacksAndMessages(null)及时移除所有消息

(9)Android中还了解哪些方便线程切换的类?

  • AsyncTask:一种轻量级的异步任务类。底层封装了线程池和Handler,便于执行后台任务以及在子线程中进行UI操作。
  • HandlerThread:一种具有消息循环的线程,其内部可使用Handler。
  • IntentService:是一种异步、会自动停止的服务,内部采用HandlerThread。

八、线程

(1)什么是线程

  • 线程就是进程中运行的多个子任务,是操作系统调用的最小单元。

(2)线程的状态

  • 新建状态(NEW):new出来,还没有调用start
  • 可运行状态(RUNNABLE):调用start进入可运行状态,可能运行也可能没有运行,取决于操作系统的调度
  • 阻塞状态(BLOCKED):被锁阻塞,暂时不活动,阻塞状态是线程阻塞在进入
  • 等待状态(WAITING):不活动,不运行任何代码,等待线程调度器调度,wait、sleep用来暂停当前线程的执行。
  • 超时等待(TIMED_WAITING):在指定时间自行返回
  • 终止状态(TERMINATED):包括正常终止和异常终止

(3)实现多线程方式

  • Thread:继承Thread,重写run方法。在构造方法中实现Runnable,重写Runnable的run方法。
  • AsyncTask:继承AsyncTask,重写AsyncTask的五个核心方法。
  • IntentService:继承IntentService,重写onHandleIntent方法。
  • ThreadHandler:创建HandlerThread实例,启动HandlerThread,在Handler中关联HandlerThread的Looper。

(4)线程之间是如何通信的?

  • 通过Handler机制
  • runOnUiThread(Runnable r)
  • View.post(Runnable r)
  • 共享变量
  • 广播

(5)如何将一个Thread线程变成Looper线程?Looper线程有哪些特点?

  • 通过Looper.prepare()可将一个Thread线程转换成Looper线程。
  • Looper线程和普通Thread不同,它通过MessageQueue来存放消息和事件、Looper.loop()进行消息轮询。

(6)这里的ThreadLocal有什么作用?

  • ThreadLocal类可实现线程本地存储的功能,把共享数据的可见范围限制在同一个线程之内,无须同步就能保证线程之间不出现数据争用的问题,这里可理解为ThreadLocal帮助Handler找到本线程的Looper。

(7)请说出与线程同步以及线程调度相关的方法

  • wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁。
  • sleep():使一个正在运行的线程处于睡眠状态,调用此方法要处理InterruptedException异常。
  • notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关。
  • notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

(8)Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别?

  • sleep()方法是线程类的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会让给其他线程,但是对象的锁依然保持,因此在休眠结束后会自动回复
  • wait()是object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池,只有调用对象的notify()方法时太能唤醒等待池中的线程进入等锁池,如果线程重新获得对象的锁就可以进入就绪状态。

(9)为什么线程通信的方法wait(), notify()和notifyAll()被定义在Object类里?
Java的每个对象中都有一个锁(monitor,也可以成为监视器) 并且wait(),notify()等方法用于等待对象的锁或者通知其他线程对象的监视器可用。在Java的线程中并没有可供任何对象使用的锁和同步器。这就是为什么这些方法是Object类的一部分,这样Java的每一个类都有用于线程间通信的基本方法

(10)如何确保线程安全?

  • synchronized关键字
  • Lock关键字

(11)什么是死锁(Deadlock)?如何分析和避免死锁?

  • 线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。
  • 我们需要查看Java应用程序的线程转储。我们需要找出那些状态为BLOCKED的线程和他们等待的资源。每个资源都有一个唯一的id,用这个id我们可以找出哪些线程已经拥有了它的对象锁。
  • 避免嵌套锁,只在需要的地方使用锁和避免无限期等待是避免死锁的通常办法

**(12)什么是线程调度器和时间分片?

  • 线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。
  • 时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。

(13)什么是线程池?线程池的优点?如何创建一个Java线程池?线程池的种类?

  • 线程池的工作原理:线程池可以减少创建和销毁线程的次数,从而减少系统资源的消耗,当一个任务提交到线程池时,首先判断核心线程池中的线程是否已经满了,如果没满,则创建一个核心线程执行任务,否则进入下一步判断工作队列是否已满,没有满则加入工作队列,否则执行下一步判断线程数是否达到了最大值,如果不是,则创建非核心线程执行任务,否则执行饱和策略,默认抛出异常。
  • 重用线程池中的线程,避免线程的创建和销毁带来的性能消耗;有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致阻塞现象;
  • 通过ThreadPoolExecutor构造方法创建线程池参数:
    corePoolSize:核心池的大小
    maximumPoolSize:线程池最大线程数
    keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止
    Unit:参数keepAliveTime的时间单位
    workQueue:一个阻塞队列,用来存储等待执行的任务
    threadFactory:线程工厂,主要用来创建线程
    handler:表示当拒绝处理任务时的策略
  • FixedThreadPool:可重用固定线程数的线程池,只有核心线程,没有非核心线程,核心线程不会被回收,有任务时,有空闲的核心线程就用核心线程执行,没有则加入队列排队。
  • SingleThreadExecutor:单线程线程池,只有一个核心线程,没有非核心线程,当任务到达时,如果没有运行线程,则创建一个线程执行,如果正在运行则加入队列等待,可以保证所有任务在一个线程中按照顺序执行。
  • CachedThreadPool:按需创建的线程池,没有核心线程,非核心线程有Integer.MAX_VALUE个。
  • ScheduledThreadPoolExecutor:继承自ThreadPoolExecutor,用于延时执行任务或定期执行任务,核心线程数固定,线程总数为Integer.MAX_VALUE

(14)什么是线程安全?保障线程安全有哪些手段?

  • 线程安全就是多线程访问同一代码,不会产生不确定结果。
  • 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作(synchronized)
  • 可见性:一个线程对主内存的修改可以及时地被其他线程看到(synchronized,volatile)
  • 有序性:程序代码按照指令顺序执行

(15)ReentrantLock和synchronized的区别?

  • synchronized执行时如果抛出异常交给jvm处理,会释放锁。
  • ReentrantLock 可以用来替换synchronized ReentrantLock手动开锁,必须要必须要必须要手动释放锁,因为要手动释放锁,所以报异常时并不会释放锁。
  • ReentrantLock可以使用tryLock尝试拿下锁,根据是否拿到锁进行不同的业务处理,也可以指定允许等待锁的时间。
  • ReentrantLock默认构造器为非公平锁,构造器传true可以指定为公平锁。
  • ReentrantLock还可以打断拿锁,尝试拿锁,指定线程在哪个条件上等待

(16)synchronized和volatile的区别?

  • synchronized能保证操作的原子性,而volatile不可以,假设线程A和线程B同时读取到变量a值,A修改a后将值更新到主内存,同时B也修改a值会覆盖A的修改操作
  • synchronized可修饰变量、方法和类,而volatile只能修饰变量
  • synchronized可能会造成线程阻塞,而volatile不会造成线程的阻塞

(17)synchronized同步代码块还有同步方法本质上锁住的是谁?为什么?

  • 本质上锁住的是对象。在java虚拟机中,每个对象和类在逻辑上都和一个监视器相关联,synchronized本质上是对一个对象监视器的获取。当执行同步代码块或同步方法时,执行方法的线程必须先获得该对象的监视器,才能进入同步代码块或同步方法;而没有获取到的线程将会进入阻塞队列,直到成功获取对象监视器的线程执行结束并释放锁后,才会唤醒阻塞队列的线程,使其重新尝试对对象监视器的获取。

(18)线程和进程有什么区别?

  • 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
  • 资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。
  • 健壮性:一个进程崩溃后,在保护模式下不会对其他进程产生影响,而线程间共享地址空间,线程有自己的堆栈和局部变量,一个线程崩溃会导致整个进程崩溃掉。
  • 资源占用:进程切换时,消耗的资源大,效率不高。所以涉及到频繁的切换时,使用线程要好于进程。
  • 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
  • 进程是操作系统分配和管理资源的单位,线程是CPU调度和管理的单位,是CPU调度的最小单元。

九、AsyncTask、HandlerThread、IntentService

(1)AsyncTask相比Handler有什么优点?不足呢?
Handler的缺点:代码相对臃肿;多任务同时执行时不易精确控制线程。
AsyncTask的优点:创建异步任务更简单,直接继承AsyncTask便可实现后台异步任务的执行和进度的回调更新UI,而无需编写任务线程和Handler实例就能完成相同的任务。

(2)使用AsyncTask需要注意什么?

  • 不要直接调用onPreExecute()、doInBackground()、onProgressUpdate()、onPostExecute()和onCancelled()方法
  • 开始和结束异步任务的方法必须在主线程调用,一个异步对象只能调用一次execute()方法,

(3)AsyncTask中的五个核心方法

  • onPreExecute():运行在主线程,在异步任务执行之前被调用。可用于进行一些界面上的初始化操作。
  • doInBackground():运行在子线程,用于处理所有耗时任务,更新UI需调用 publishProgress()方法。任务一旦完成就通过return语句将任务的执行结果返回。
  • onProgressUpdate():运行在主线程,在后台任务中调用publishProgress()之后该方法会被调用,可利用方法中携带的参数如Progress来对UI进行相应地更新。
  • onPostExecute():运行在主线程,在异步任务执行完毕并通过return语句返回时被调用,可利用方法中返回的数据来进行一些UI操作。
  • onCancelled():运行在主线程,当异步任务被取消时调用,用于界面取消的更新。

(4)AsyncTask中使用的线程池大小?

  • SerialExecutor:用于任务的排队,默认是串行的线程池,在3.0以前核心线程数为5、线程池大小为128,而3.0以后变为同一时间只能处理一个任务
  • THREAD_POOL_EXECUTOR:用于真正执行任务。

(5)HandlerThread有什么特点?

  • HandlerThread是一个线程类,它继承自Thread。与普通Thread不同,HandlerThread具有消息循环的效果,这是因为它内部HandlerThread.run()方法中有Looper,能通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环。

(6)快速实现子线程使用Handler

  • 实例化一个HandlerThread对象,参数是该线程名称;
  • 通过handlerThread.start()启动线程;
  • 实例化一个Handler并传入handlerThread中looper对象,使得与HandlerThread绑定;
  • 利用handler即可执行异步任务;
  • 当不需要HandlerThread时,通过quit()或quitSafely()方法来终止线程的执行。

(7)IntentService的工作原理?

  • 在IntentService.onCreate()方法中实例化一个HandlerThread,利用其内部的Looper会实例化一个ServiceHandler对象;
  • 任务请求的Intent会被封装到Message并通过ServiceHandler发送给Looper的MessageQueue,最终在HandlerThread中执行;
  • 在ServiceHandler.handleMessage()中会调用IntentService.onHandleIntent()和stopSelf(),可在onHandleIntent方法中处理后台任务的逻辑,stopSelf方法终止服务。

(8)IntentService的特点?

  • 相比于线程:由于是服务,优先级比线程高,更不容易被系统杀死。因此较适合执行一些高优先级的后台任务。
  • 相比于普通Service:可自动创建子线程来执行任务,且任务执行完毕后自动退出。

(9)为何不用bindService方式创建IntentService?

  • 当有Intent任务请求时会把Intent封装到Message,然后ServiceHandler会把消息发送出,而发送消息是在onStartCommand()完成的,只能通过startService()才可走该生命周期方法,因此不能通过bindService创建IntentService。

十、序列化

(1)什么是序列化?
序列化表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地,还可以实现跨进程传输。

(2)Serializable接口和Parcelable接口的区别?为何推荐使用后者?

  • Serializable:Java序列化接口,将一个对象转化成可存储或可传输的状态,操作简单、效率低、开销大,ObjectOutputStream和ObjectInputStream过程都需要大量的I/O操作。适合将对象序列化到存储设备或将对象序列化后通过网络设备传输。
  • Serializable的实现,只需要实现Serializable即可。这只是给对象打了一个标记,系统会自动将其序列化。
  • Parcelable:Android序列化接口,将一个对象进行分解,且分解后的每一个部分都是传递可支持的类型。操作比较麻烦、效率高。主要用在内存的序列化。
  • Parcelabel 的实现,需要在类中添加一个静态成员变量CREATOR,这个变量需要继承 Parcelable.Creator 接口。

你可能感兴趣的:(Android面试题(上))