参考解答:在正常情况下,Activity的常用生命周期就只有如下7个
onCreate():表示Activity正在被创建,常用来初始化工作,比如调用setContentView加载界面布局资源,初始化Activity所需数据等;
onRestart():表示Activity正在重新启动,一般情况下,当前Acitivty从不可见重新变为可见时,OnRestart就会被调用;
onStart():表示Activity正在被启动,此时Activity可见但不在前台,还处于后台,无法与用户交互;
onResume():表示Activity获得焦点,此时Activity可见且在前台并开始活动,这是与onStart的区别所在;
onPause():表示Activity正在停止,此时可做一些存储数据、停止动画等工作,但是不能太耗时,因为这会影响到新Activity的显示,onPause必须先执行完,新Activity的onResume才会执行;
onStop():表示Activity即将停止,可以做一些稍微重量级的回收工作,比如注销广播接收器、关闭网络连接等,同样不能太耗时;
onDestroy():表示Activity即将被销毁,这是Activity生命周期中的最后一个回调,常做回收工作、资源释放;
延伸:从整个生命周期来看,onCreate和onDestroy是配对的,分别标识着Activity的创建和销毁,并且只可能有一次调用; 从Activity是否可见来说,onStart和onStop是配对的,这两个方法可能被调用多次; 从Activity是否在前台来说,onResume和onPause是配对的,这两个方法可能被调用多次; 除了这种区别,在实际使用中没有其他明显区别;
参考解答:Activity A 启动另一个Activity B,回调如下
Activity A 的onPause() → Activity B的onCreate() → onStart() → onResume() → Activity A的onStop();
如果B是透明主题又或则是个DialogActivity,则不会回调A的onStop;
参考解答:发生条件:异常情况下(系统配置发生改变时导致Activity被杀死并重新创建、资源内存不足导致低优先级的Activity被杀死)
系统会调用onSaveInstanceState来保存当前Activity的状态,此方法调用在onStop之前,与onPause没有既定的时序关系;
当Activity被重建后,系统会调用onRestoreInstanceState,并且把onSave(简称)方法所保存的Bundle对象同时传参给onRestore(简称)和onCreate(),因此可以通过这两个方法判断Activity是否被重建,调用在onStart之后;
推荐文章:
- 官方文档
参考回答:
standard标准模式:每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否已经存在,此模式的Activity默认会进入启动它的Activity所属的任务栈中;
singleTop栈顶复用模式:如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时会回调onNewIntent方法,如果新Activity实例已经存在但不在栈顶,那么Activity依然会被重新创建;
singleTask栈内复用模式:只要Activity在一个任务栈中存在,那么多次启动此Activity都不会重新创建实例,并回调onNewIntent方法,此模式启动Activity A,系统首先会寻找是否存在A想要的任务栈,如果不存在,就会重新创建一个任务栈,然后把创建好A的实例放到栈中;
singleInstance单实例模式:这是一种加强的singleTask模式,具有此种模式的Activity只能单独地位于一个任务栈中,且此任务栈中只有唯一一个实例;
推荐文章:
- 官方文档
参考回答:
FLAG_ACTIVITY_NEW_TASK : 对应singleTask启动模式,其效果和在XML中指定该启动模式相同;
FLAG_ACTIVITY_SINGLE_TOP : 对应singleTop启动模式,其效果和在XML中指定该启动模式相同;
FLAG_ACTIVITY_CLEAR_TOP : 具有此标记位的Activity,当它启动时,在同一个任务栈中所有位于它上面的Activity都要出栈。这个标记位一般会和singleTask模式一起出现,在这种情况下,被启动Activity的实例如果已经存在,那么系统就会回调onNewIntent。如果被启动的Activity采用standard模式启动,那么它以及连同它之上的Activity都要出栈,系统会创建新的Activity实例并放入栈中;
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS : 具有这个标记的 Activity 不会出现在历史 Activity 列表中;
推荐文章:
- 官方文档
参考回答:
Activity在创建时会调用 attach() 方法初始化一个PhoneWindow(继承于Window),每一个Activity都包含了唯一一个PhoneWindow
Activity通过setContentView实际上是调用的 getWindow().setContentView将View设置到PhoneWindow上,而PhoneWindow内部是通过 WindowManager 的addView、removeView、updateViewLayout这三个方法来管理View,WindowManager本质是接口,最终由WindowManagerImpl实现
延伸
WindowManager为每个Window创建Surface对象,然后应用就可以通过这个Surface来绘制任何它想要绘制的东西。而对于WindowManager来说,这只不过是一块矩形区域而已
- Surface其实就是一个持有像素点矩阵的对象,这个像素点矩阵是组成显示在屏幕的图像的一部分。我们看到显示的每个Window(包括对话框、全屏的Activity、状态栏等)都有他自己绘制的Surface。而最终的显示可能存在Window之间遮挡的问题,此时就是通过SurfaceFlinger对象渲染最终的显示,使他们以正确的Z-order显示出来。一般Surface拥有一个或多个缓存(一般2个),通过双缓存来刷新,这样就可以一边绘制一边加新缓存。
View是Window里面用于交互的UI元素。Window只attach一个View Tree(组合模式),当Window需要重绘(如,当View调用invalidate)时,最终转为Window的Surface,Surface被锁住(locked)并返回Canvas对象,此时View拿到Canvas对象来绘制自己。当所有View绘制完成后,Surface解锁(unlock),并且post到绘制缓存用于绘制,通过Surface Flinger来组织各个Window,显示最终的整个屏幕
推荐文章:
- Activity、View、Window的理解一篇文章就够了
参考回答:
不设置Activity的android:configChanges时,切屏会销毁当前Activity,然后重新加载调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次; onPause() →onStop()→onDestory()→onCreate()→onStart()→onResume()
设置Activity的android:configChanges=“orientation”,经过机型测试
在Android5.1 即API 23级别下,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次
在Android9 即API 28级别下,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法
后经官方查正,原话如下
- 如果您的应用面向Android 3.2即API 级别 13或更高级别(按照 minSdkVersion 和 targetSdkVersion 属性所声明的级别),则还应声明 “screenSize” 配置,因为当设备在横向与纵向之间切换时,该配置也会发生变化。即便是在 Android 3.2 或更高版本的设备上运行,此配置变更也不会重新启动 Activity
设置Activity的android:configChanges="orientation|keyboardHidden|screenSize"时,机型测试通过,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法;
推荐文章:
- Android 横竖屏切换加载不同的布局
参考回答:
在保证有权限访问的情况下,通过隐式Intent进行目标Activity的IntentFilter匹配,原则是:
一个intent只有同时匹配某个Activity的intent-filter中的action、category、data才算完全匹配,才能启动该Activity;
一个Activity可以有多个 intent-filter,一个 intent只要成功匹配任意一组 intent-filter,就可以启动该Activity;
推荐文章:
- action、category、data的具体匹配规则
参考回答:
⾸先还是得当前系统中有没有拥有这个 Application 的进程。如果没有,则需要处理 APP 的启动过 程。在经过创建进程、绑定 Application 步骤后,才真正开始启动 Activity 的⽅法。 startActivity() ⽅ 法最终还是调⽤的 startActivityForResult()。
在 startActivityForResult() 中,真正去打开 Activity 的实现是在 Instrumentation 的 execStartActivivity() ⽅法中。
在 execStartActivity() 中采⽤ checkStartActivityResult() 检查在 manifest 中是否已经注册,如果没 有注册则抛出异常。否则把打开 Activity 的任务交给 ActivityThread 的内部类 ApplicationThread, 该类实现了 IApplicationThread 接⼝。这个类完全搞定了 onCreate()、onStart() 等 Activity 的⽣命 周期回调⽅法。
在 ApplicationThread 类中,有⼀个⽅法叫 scheduleLaunchActivity(),它可以构造⼀个 Activity 记 录,然后发送⼀个消息给事先定义好的 Handler。 这个 Handler 负责根据 LAUNCH_ACTIVITY 的类型来做不同的 Activity 启动⽅式。其中有⼀个᯿要的 ⽅法 handleLaunchActivity() 。
在 handleLaunchActivity() 中,会把启动 Activity 交给 performLaunchActivity() ⽅法。 在 performLaunchActivity() ⽅法中,⾸先从 Intent 中解析出⽬标 Activity 的启动参数,然后⽤ ClassLoader 将⽬标 Activity 的类通过类名加载出来并⽤ newInstance() 来实例化⼀个对象。 创建完毕后, 开始调⽤ Activity 的 onCreate() ⽅法,⾄此,Activity 被成功启动。
推荐文章:
- Android四大组件启动机制之Activity启动过程
参考回答:
Fragment从创建到销毁整个生命周期中涉及到的方法依次为:onAttach()→onCreate()→ onCreateView()→onActivityCreated()→onStart()→onResume()→onPause()→onStop()→onDestroyView()→onDestroy()→onDetach(),其中和Activity有不少名称相同作用相似的方法,而不同的方法有:
onAttach():当Fragment和Activity建立关联时调用;
onCreateView():当fragment创建视图调用,在onCreate之后;
onActivityCreated():当与Fragment相关联的Activity完成onCreate()之后调用;
onDestroyView():在Fragment中的布局被移除时调用;
onDetach():当Fragment和Activity解除关联时调用;
推荐文章:
- Android之Fragment优点
参考回答:
相似点:都可包含布局、可有自己的生命周期
不同点:
Fragment相比较于Activity多出4个回调周期,在控制操作上更灵活;
Fragment可以在XML文件中直接进行写入,也可以在Activity中动态添加;
Fragment可以使用show()/hide()或者replace()随时对Fragment进行切换,并且切换的时候不会出现明显的效果,用户体验会好;Activity虽然也可以进行切换,但是Activity之间切换会有明显的翻页或者其他的效果,在小部分内容的切换上给用户的感觉不是很好;
参考回答:
add不会重新初始化fragment,replace每次都会。所以如果在fragment生命周期内获取获取数据,使用replace会重复获取;
添加相同的fragment时,replace不会有任何变化,add会报IllegalStateException异常;
replace先remove掉相同id的所有fragment,然后在add当前的这个fragment,而add是覆盖前一个fragment。所以如果使用add一般会伴随hide()和show(),避免布局重叠;
使用add,如果应用放在后台,或以其他方式被系统销毁,再打开时,hide()中引用的fragment会销毁,所以依然会出现布局重叠bug,可以使用replace或使用add时,添加一个tag参数;
参考回答:
getFragmentManager()所得到的是所在fragment 的父容器的管理器, getChildFragmentManager()所得到的是在fragment 里面子容器的管理器, 如果是fragment嵌套fragment,那么就需要利用getChildFragmentManager();
因为Fragment是3.0 Android系统API版本才出现的组件,所以3.0以上系统可以直接调用getFragmentManager()来获取FragmentManager()对象,而3.0以下则需要调用getSupportFragmentManager() 来间接获取;
参考回答:
相同点 :二者都继承PagerAdapter
不同点 :FragmentPagerAdapter的每个Fragment会持久的保存在FragmentManager中,只要用户可以返回到页面中,它都不会被销毁。因此适用于那些数据相对静态的页,Fragment数量也比较少的那种; FragmentStatePagerAdapter只保留当前页面,当页面不可见时,该Fragment就会被消除,释放其资源。因此适用于那些数据动态性较大、占用内存较多,多Fragment的情况;
参考回答:Service的生命周期涉及到六大方法
onCreate():如果service没被创建过,调用startService()后会执行onCreate()回调;如果service已处于运行中,调用startService()不会执行onCreate()方法。也就是说,onCreate()只会在第一次创建service时候调用,多次执行startService()不会重复调用onCreate(),此方法适合完成一些初始化工作;
onStartComand():服务启动时调用,此方法适合完成一些数据加载工作,比如会在此处创建一个线程用于下载数据或播放音乐;
onBind():服务被绑定时调用;
onUnBind():服务被解绑时调用;
onDestroy():服务停止时调用;
推荐文章:
- Android组件系列----Android Service组件深入解析
参考回答:Service的两种启动模式
startService():通过这种方式调用startService,onCreate()只会被调用一次,多次调用startSercie会多次执行onStartCommand()和onStart()方法。如果外部没有调用stopService()或stopSelf()方法,service会一直运行。
bindService():如果该服务之前还没创建,系统回调顺序为onCreate()→onBind()。如果调用bindService()方法前服务已经被绑定,多次调用bindService()方法不会多次创建服务及绑定。如果调用者希望与正在绑定的服务解除绑定,可以调用unbindService()方法,回调顺序为onUnbind()→onDestroy();
推荐文章:
- Android Service两种启动方式详解
参考回答:
onStartCommand方式中,返回START_STICKY或则START_REDELIVER_INTENT
START_STICKY:如果返回START_STICKY,表示Service运行的进程被Android系统强制杀掉之后,Android系统会将该Service依然设置为started状态(即运行状态),但是不再保存onStartCommand方法传入的intent对象
START_NOT_STICKY:如果返回START_NOT_STICKY,表示当Service运行的进程被Android系统强制杀掉之后,不会重新创建该Service
START_REDELIVER_INTENT:如果返回START_REDELIVER_INTENT,其返回情况与START_STICKY类似,但不同的是系统会保留最后一次传入onStartCommand方法中的Intent再次保留下来并再次传入到重新创建后的Service的onStartCommand方法中
提高Service的优先级 在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = "1000"这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播;
在onDestroy方法里重启Service 当service走到onDestroy()时,发送一个自定义广播,当收到广播时,重新启动service;
提升Service进程的优先级 进程优先级由高到低:前台进程 一 可视进程 一 服务进程 一 后台进程 一 空进程 可以使用startForeground将service放到前台状态,这样低内存时,被杀死的概率会低一些;
系统广播监听Service状态
将APK安装到/system/app,变身为系统级应用
注意:以上机制都不能百分百保证Service不被杀死,除非做到系统白名单,与系统同生共死
参考回答:
- Service默认并不会运行在子线程中,也不运行在一个独立的进程中,它同样执行在主线程中(UI线程)。换句话说,不要在Service里执行耗时操作,除非手动打开一个子线程,否则有可能出现主线程被阻塞(ANR)的情况;
- 参考回答:
参考回答: ActivityManagerService是Android中最核心的服务 , 主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作,其职责与操作系统中的进程管理和调度模块类似;
推荐文章:
- ActivityManagerService分析——AMS启动流程
参考回答:
普通广播:开发者自身定义 intent的广播(最常用),所有的广播接收器几乎会在同一时刻接受到此广播信息,接受的先后顺序随机;
有序广播:发送出去的广播被广播接收者按照先后顺序接收,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递,且优先级(priority)高的广播接收器会先收到广播消息。有序广播可以被接收器截断使得后面的接收器无法收到它;
本地广播:仅在自己的应用内发送接收广播,也就是只有自己的应用能收到,数据更加安全,效率更高,但只能采用动态注册的方式;
粘性广播:这种广播会一直滞留,当有匹配该广播的接收器被注册后,该接收器就会收到此条广播;
推荐文章:
- Android四大组件:BroadcastReceiver史上最全面解析
- 参考回答:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YXMOYkgW-1632818104981)(https://upload-images.jianshu.io/upload_images/26557619-34370b92832cecd1.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
- 参考回答:
推荐文章:
- 广播的底层实现原理
- 参考解答:Android 广播分为两个角色:广播发送者、广播接受者
广播接收器的注册分为两种:静态注册、动态注册。
静态广播接收者:通过AndroidManifest.xml的标签来申明的BroadcastReceiver。
动态广播接收者:通过AMS.registerReceiver()方式注册的BroadcastReceiver,动态注册更为灵活,可在不需要时通过unregisterReceiver()取消注册。
广播类型:根据广播的发送方式,
1.普通广播:通过Context.sendBroadcast()发送,可并行处理
2.系统广播:当使用系统广播时,只需在注册广播接收者时定义相关的action即可,不需要手动发送广播(网络变化,锁屏,飞行模式)
3.有序广播: 指的是发送出去的广播被 BroadcastReceiver 按照先后顺序进行接收 发送方式变为:sendOrderedBroadcast(intent);
广播接受者接收广播的顺序规则(同时面向静态和动态注册的广播接受者):按照 Priority 属性值从大-小排序,Priority属性相同者,动态注册的广播优先。
4.App应用内广播(Local Broadcast)
背景 Android中的广播可以跨App直接通信(exported对于有intent-filter情况下默认值为true)
冲突 可能出现的问题:
其他App针对性发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收广播并处理;
其他App注册与当前App一致的intent-filter用于接收广播,获取广播具体信息; 即会出现安全性 & 效率性的问题。
解决方案 使用App应用内广播(Local Broadcast)
App应用内广播可理解为一种局部广播,广播的发送者和接收者都同属于一个App。 相比于全局广播(普通广播),App应用内广播优势体现在:安全性高 & 效率高
具体使用1 - 将全局广播设置成局部广播
注册广播时将exported属性设置为false,使得非本App内部发出的此广播不被接收; 在广播发送和接收时,增设相应权限permission,用于权限验证;
发送广播时指定该广播接收器所在的包名,此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。
具体使用2 - 使用封装好的LocalBroadcastManager类
对于LocalBroadcastManage,方式发送的应用内广播,只能通过LocalBroadcastManager动态注册,不能静态注册
5.粘性广播(Sticky Broadcast) 由于在Android5.0 & API 21中已经失效,所以不建议使用,在这里也不作过多的总结。
应用场景
同一 App 内部的不同组件之间的消息通信(单个进程);
不同 App 之间的组件之间消息通信;
Android系统在特定情况下与App之间的消息通信,如:网络变化、电池电量、屏幕开关等。
- 参考解答:静态注册:常驻系统,不受组件生命周期影响,即便应用退出,广播还是可以被接收,耗电、占内存。
动态注册:非常驻,跟随组件的生命变化,组件结束,广播结束。在组件结束前,需要先移除广播,否则容易造成内存泄漏。
- 参考解答: https://juejin.im/post/6844904057891471367#heading-0
http://gityuan.com/2016/06/04/broadcast-receiver/
动态注册
1.创建对象LoadedApk.ReceiverDispatcher.InnerReceiver的实例,该对象继承于IIntentReceiver.Stub(InnerReceiver实际是一个binder本地对象(BBinder:本地Binder,服务实现方的基类,提供了onTransact接口来接收请求))。
2.将IIntentReceiver对象和注册所传的IntentFilter对象发送给AMS。 AMS记录IIntentReceiver、IntentFilter和注册的进程ProcessRecord,并建立起它们的对应关系。
3.当有广播发出时,AMS根据广播intent所携带的IntentFilter找到IIntentReceiver和ProcessRecord,然后回调App的ApplicationThread对象的scheduleRegisteredReceiver,将IIntentReceiver和广播的intent一并传给App,App直接调用IIntentReceiver的performReceive。
4.因为广播是通过binder线程回调到接收进程的,接收进程通过ActivityThread里的H这个Handler将调用转到主线程,然后回调BroadcastReceiver的onReceive。
静态注册
静态注册是通过在Manifest文件中声明实现了BroadcastReceiver的自定义类和对应的IntentFilter,来告诉PMS(PackageManagerService)这个App所注册的广播。
当AMS接收到广播后,会查找所有动态注册的和静态注册的广播接收器,静态注册的广播接收器是通过PMS(PackageManagerService)发现的,PMS找到对应的App
对应进程已经创建,直接调用App的ApplicationThread对象的scheduleReceiver
对应进程尚未创建,先启动App进程,App进程启动后回调AMS的attachApplication,attachApplication则继续派发刚才的广播App这边收到调用后会先通过Handler转到主线程,然后根据AMS传过来的参数实例化广播接收器的类,接着调用广播接收器的onReceive。
- 参考解答: BroadcastReceiver是针对应用间、应用与系统间、应用内部进行通信的一种方式
LocalBroadcastReceiver仅在自己的应用内发送接收广播,也就是只有自己的应用能收到,数据更加安全广播只在这个程序里,而且效率更高。
BroadcastReceiver采用的binder方式实现跨进程间的通信;
LocalBroadcastManager使用Handler通信机制。
LocalBroadcastReceiver 使用
LocalBroadcastReceiver不能静态注册,只能采用动态注册的方式。
在发送和注册的时候采用,LocalBroadcastManager的sendBroadcast方法和registerReceiver方法
http://gityuan.com/2017/04/23/local_broadcast_manager/
注册过程,主要是向mReceivers和mActions添加相应数据:
mReceivers:数据类型为HashMap
, 记录广播接收者与IntentFilter列表的对应关系; mActions:数据类型为HashMap
, 记录action与广播接收者的对应关系 根据Intent的action来查询相应的广播接收者列表;
发送MSG_EXEC_PENDING_BROADCASTS消息,回调相应广播接收者的onReceive方法
参考回答:
- ContentProvider作为四大组件之一,其主要负责存储和共享数据。与文件存储、SharedPreferences存储、SQLite数据库存储这几种数据存储方法不同的是,后者保存下的数据只能被该应用程序使用,而前者可以让不同应用程序之间进行数据共享,它还可以选择只对哪一部分数据进行共享,从而保证程序中的隐私数据不会有泄漏风险。
推荐文章:
- Android:关于ContentProvider的知识都在这里了!
参考回答:
读写分离
权限控制-精确到表级
URL控制
参考回答:
ContentProvider:管理数据,提供数据的增删改查操作,数据源可以是数据库、文件、XML、网络等,ContentProvider为这些数据的访问提供了统一的接口,可以用来做进程间数据共享。
ContentResolver:ContentResolver可以为不同URI操作不同的ContentProvider中的数据,外部进程可以通过ContentResolver与ContentProvider进行交互。
ContentObserver:观察ContentProvider中的数据变化,并将变化通知给外界。
- 参考解答:ContentProvider的作用是为不同的应用之间数据共享,提供统一的接口,我们知道安卓系统中应用内部的数据是对外隔离的,要想让其它应用能使用自己的数据(例如通讯录)这个时候就用到了ContentProvider。
ContentProvider(内容提供者)通过 uri 来标识其它应用要访问的数据。
通过 ContentResolver(内容解析者)的增、删、改、查方法实现对共享数据的操作。
还可以通过注册 ContentObserver(内容观察者)来监听数据是否发生了变化来对应的刷新页面
- 参考解答:ContentProvider:管理数据,提供数据的增删改查操作,数据源可以是数据库、文件、XML、网络等。
ContentResolver:外部进程可以通过 ContentResolver 与 ContentProvider 进行交互。其他应用中ContentResolver 可以不同 URI 操作不同的 ContentProvider 中的数据。
ContentObserver:观察 ContentProvider 中的数据变化,并将变化通知给外界。
- 参考解答: https://juejin.im/post/6844904062173839368#heading-0
http://gityuan.com/2016/07/30/content-provider/
https://blog.csdn.net/u011733869/article/details/83958712
ContentProvider的安装(ActivityThread.installProvider)
当主线程收到H.BIND_APPLICATION消息后,会调用handleBindApplication方法。
handleBindApplication->installProvider
installProvider()
创建了provider对象
创建ProviderClientRecord,这是一个provider在client进程中对应的对象
放入mProviderMap(记录所有contentProvider)
总结:把provider启动起来并记录和发布给AMS
ContentResolver.query
调用端App在使用ContentProvider前首先要获取ContentProvider
1。通过ContentResolver调用acquireProvider
2.ActivityThread首先通过一个map查找是否已经install过这个Provider,如果install过就直接将之返回给调用者,如果没有install过就调用AMS的getContentProvider,AMS首先查找这个Provider是否被publish过,如果publish过就直接返回,否则通过PMS找到Provider所在的App。
3.如果发现目标App进程未启动,就创建一个ContentProviderRecord对象然后调用其wait方法阻塞当前执行流程,启动目标App进程,AMS找到App的所有运行于当前进程的Provider,保存在map中,将要启动的所有Provider传给目标App进程,解除前面对获取Provider执行流程的阻塞.
4.如果目标App进程已启动,AMS在getContentProvider里会查找到要获取的Provider,就直接返回了.调用端App收到AMS的返回结果后(acquireProvider返回),调用ActivityThread的installProvider将Provider记录到本地的一个map中,下次再调用acquireProvider就直接返回。
ContentProvider所提供的接口中只有query是基于共享内存的,其他都是直接使用binder的入参出参进行数据传递。
AMS作为一个中间管理员的身份,所有的provider会向它注册
向AMS请求到provider之后,就可以在client和server之间自行binder通信,不需要再经过systemserver
- 参考解答:封装
采用ContentProvider方式,其 解耦了 底层数据的存储方式,使得无论底层数据存储采用何种方式,外界对数据的访问方式都是统一的,这使得访问简单 & 高效
如一开始数据存储方式 采用 SQLite 数据库,后来把数据库换成 MongoDB,也不会对上层数据ContentProvider使用代码产生影响
提供一种跨进程数据共享的方式。
应用程序间的数据共享还有另外的一个重要话题,就是数据更新通知机制了。因为数据是在多个应用程序中共享的,当其中一个应用程序改变了这些共享数据的时候,它有责任通知其它应用程序,让它们知道共享数据被修改了,这样它们就可以作相应的处理。
- 参考解答:
定义:Uniform Resource Identifier,即统一资源标识符作用:唯一标识 ContentProvider & 其中的数据,URI分为 系统预置 & 自定义,分别对应系统内置的数据(如通讯录、日程表等等)和自定义数据库
每一个 ContentProvider 都拥有一个公共的 URI ,这个 URI 用于表示这个 ContentProvider 所提供的数据。
将其分为 A,B,C,D 4个部分:
A:标准前缀,;“content://”;
B:URI 的标识,用于唯一标识这个 ContentProvider ,外部调用者可以根据这个标识来找到它。
C:路径( path ),通俗的讲就是你要操作的数据库中表的名字,
D:如果URI中包含表示需要获取的记录的 ID;则就返回该id对应的数据,如果没有 ID,就表示返回全部。
参考回答:Android平台实现数据持久存储的常见几种方式:
SharedPreferences存储:一种轻型的数据存储方式,本质是基于XML文件存储的key-value键值对数据,通常用来存储一些简单的配置信息(如应用程序的各种配置信息);
SQLite数据库存储:一种轻量级嵌入式数据库引擎,它的运算速度非常快,占用资源很少,常用来存储大量复杂的关系数据;
ContentProvider:四大组件之一,用于数据的存储和共享,不仅可以让不同应用程序之间进行数据共享,还可以选择只对哪一部分数据进行共享,可保证程序中的隐私数据不会有泄漏风险;
File文件存储:写入和读取文件的方法和 Java中实现I/O的程序一样;
网络存储:主要在远程的服务器中存储相关数据,用户操作的相关数据可以同步到服务器上;
参考回答:
SharedPreferences是一种轻型的数据存储方式,本质是基于XML文件存储的key-value键值对数据,通常用来存储一些简单的配置信息,如int,String,boolean、float和long;
注意事项:
勿存储大型复杂数据,这会引起内存GC、阻塞主线程使页面卡顿产生ANR
勿在多进程模式下,操作Sp
不要多次edit和apply,尽量批量修改一次提交
建议apply,少用commit
推荐文章:
史上最全面,清晰的SharedPreferences解析
SharedPreferences在多进程中的使用及注意事项
参考回答:
apply没有返回值而commit返回boolean表明修改是否提交成功。
apply是将修改数据原子提交到内存, 而后异步真正提交到硬件磁盘, 而commit是同步的提交到硬件磁盘,因此,在多个并发的提交commit的时候,他们会等待正在处理的commit保存到磁盘后在操作,从而降低了效率。而apply只是原子的提交到内容,后面有调用apply的函数的将会直接覆盖前面的内存数据,这样从一定程度上提高了很多效率。
apply方法不会提示任何失败的提示。 由于在一个进程中,sharedPreference是单实例,一般不会出现并发冲突,如果对提交的结果不关心的话,建议使用apply,当然需要确保提交成功且有后续操作的话,还是需要用commit的。
参考回答:
- SQLite在做CRDU操作时都默认开启了事务,然后把SQL语句翻译成对应的SQLiteStatement并调用其相应的CRUD方法,此时整个操作还是在rollback journal这个临时文件上进行,只有操作顺利完成才会更新db数据库,否则会被回滚;
参考回答:
- 使用SQLiteDatabase的beginTransaction方法开启一个事务,将批量操作SQL语句转化为SQLiteStatement并进行批量操作,结束后endTransaction()
参考回答:
- SQLite数据库只允许增加字段而不允许修改和删除表字段,只能创建新表保留原有字段,删除原表
参考回答:
使用事务做批量操作
及时关闭Cursor,避免内存泄露
耗时操作异步化:数据库的操作属于本地IO耗时操作,建议放入异步线程中处理
ContentValues的容量调整:ContentValues内部采用HashMap来存储Key-Value数据,ContentValues初始容量为8,扩容时翻倍。因此建议对ContentValues填入的内容进行估量,设置合理的初始化容量,减少不必要的内部扩容操作
使用索引加快检索速度:对于查询操作量级较大、业务对查询要求较高的推荐使用索引
参考回答:
线程是CPU调度的最小单元,同时线程是一种有限的系统资源
进程一般指一个执行单元,在PC和移动设备上一个程序或则一个应用
一般来说,一个App程序至少有一个进程,一个进程至少有一个线程(包含与被包含的关系), 通俗来讲就是,在App这个工厂里面有一个进程,线程就是里面的生产线,但主线程(主生产线)只有一条,而子线程(副生产线)可以有多个
进程有自己独立的地址空间,而进程中的线程共享此地址空间,都可以并发执行
推荐文章:
- Android developer官方文档–进程和线程
参考回答:
在AndroidMenifest中给四大组件指定属性android:process开启多进程模式
在内存允许的条件下可以开启N个进程
推荐讲解:
- 如何开启多进程?应用是否可以开启N个进程?
参考回答:
所有运行在不同进程的四大组件(Activity、Service、Receiver、ContentProvider)共享数据都会失败,这是由于Android为每个应用分配了独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这会导致在不同的虚拟机中访问同一个类的对象会产生多份副本。比如常用例子(通过开启多进程获取更大内存空间、两个或则多个应用之间共享数据、微信全家桶)
一般来说,使用多进程通信会造成如下几方面的问题
静态成员和单例模式完全失效:独立的虚拟机造成
线程同步机制完全实效:独立的虚拟机造成
SharedPreferences的可靠性下降:这是因为Sp不支持两个进程并发进行读写,有一定几率导致数据丢失
Application会多次创建:Android系统在创建新的进程会分配独立的虚拟机,所以这个过程其实就是启动一个应用的过程,自然也会创建新的Application
推荐文章:
- Android developer官方文档–进程和线程
- 参考回答:
与Linux上传统的IPC机制,比如System V,Socket相比,Binder好在哪呢? * 传输效率高、可操作性强:传输效率主要影响因素是内存拷贝的次数,拷贝次数越少,传输速率越高。从Android进程架构角度分析:对于消息队列、Socket和管道来说,数据先从发送方的缓存区拷贝到内核开辟的缓存区中,再从内核缓存区拷贝到接收方的缓存区,一共两次拷贝,如图:
而对于Binder来说,数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到同一块物理地址的,节省了一次数据拷贝的过程,如图:
由于共享内存操作复杂,综合来看,Binder的传输效率是最好的。 * 实现C/S架构方便:Linux的众IPC方式除了Socket以外都不是基于C/S架构,而Socket主要用于网络间的通信且传输效率较低。Binder基于C/S架构 ,Server端与Client端相对独立,稳定性较好。 * 安全性高:传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Binder机制为每个进程分配了UID/PID且在Binder通信时会根据UID/PID进行有效性检测。
推荐文章:
- 为什么 Android 要采用 Binder 作为 IPC 机制?
参考回答:
- Linux系统将一个进程分为用户空间和内核空间。对于进程之间来说,用户空间的数据不可共享,内核空间的数据可共享,为了保证安全性和独立性,一个进程不能直接操作或者访问另一个进程,即Android的进程是相互独立、隔离的,这就需要跨进程之间的数据通信方式
一次完整的 Binder IPC 通信过程通常是这样:
首先 Binder 驱动在内核空间创建一个数据接收缓存区;
接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。
参考回答:
- Binder框架 是基于 C/S 架构的。由一系列的组件组成,包括 Client、Server、ServiceManager、Binder驱动,其中 Client、Server、Service Manager 运行在用户空间,Binder 驱动运行在内核空间
* **Server&Client**:服务器&客户端。在Binder驱动和Service Manager提供的基础设施上,进行Client-Server之间的通信。 * **ServiceManager**(如同DNS域名服务器)服务的管理者,将Binder名字转换为Client中对该Binder的引用,使得Client可以通过Binder名字获得Server中Binder实体的引用。 * **Binder驱动**(如同路由器):负责进程之间binder通信的建立,传递,计数管理以及数据的传递交互等底层支持。 ![](https://img-blog.csdnimg.cn/img_convert/3a8ecfe83f06635b61bdb458a83163e0.png) 图片出自Carson\_Ho文章 —— Android跨进程通信:图文详解 Binder机制 原理
参考回答:
因为bundle传递数据时只支持基本数据类型,所以在传递对象时需要序列化转换成可存储或可传输的本质状态(字节流)。序列化后的对象可以在网络、IPC(比如启动另一个进程的Activity、Service和Reciver)之间进行传输,也可以存储到本地。
序列化实现的两种方式:实现Serializable/Parcelable接口。不同点如图:
参考回答:
AIDL(Android Interface Definition Language,Android接口定义语言):如果在一个进程中要调用另一个进程中对象的方法,可使用AIDL生成可序列化的参数,AIDL会生成一个服务端对象的代理类,通过它客户端实现间接调用服务端对象的方法。
AIDL的本质是系统提供了一套可快速实现Binder的工具。关键类和方法:
AIDL接口:继承IInterface。
Stub类:Binder的实现类,服务端通过这个类来提供服务。
Proxy类:服务器的本地代理,客户端通过这个类调用服务器的方法。
asInterface():客户端调用,将服务端的返回的Binder对象,转换成客户端所需要的AIDL接口类型对象。如果客户端和服务端位于统一进程,则直接返回Stub对象本身,否则返回系统封装后的Stub.proxy对象
asBinder():根据当前调用情况返回代理Proxy的Binder对象。
onTransact():运行服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。
transact():运行在客户端,当客户端发起远程请求的同时将当前线程挂起。之后调用服务端的onTransact()直到远程请求返回,当前线程才继续执行。
当有多个业务模块都需要AIDL来进行IPC,此时需要为每个模块创建特定的aidl文件,那么相应的Service就会很多。必然会出现系统资源耗费严重、应用过度重量级的问题。解决办法是建立Binder连接池,即将每个业务模块的Binder请求统一转发到一个远程Service中去执行,从而避免重复创建Service。
- 工作原理:每个业务模块创建自己的AIDL接口并实现此接口,然后向服务端提供自己的唯一标识和其对应的Binder对象。服务端只需要一个Service,服务器提供一个queryBinder接口,它会根据业务模块的特征来返回相应的Binder对象,不同的业务模块拿到所需的Binder对象后就可进行远程方法的调用了
- 参考解答:按照生命周期的不同,Android的进程可以分为前台进程、后台进程、可见进程、服务进程和空进程等。
前台进程
前台进程是用户当前正在使用的进程,一些前台进程可以在任何时候都存在,当内存低的时候前台进程也可能被销毁。对于这种情况下,设备会进行内存调度,中止一些前台进程来保持对用户交互的响应。
如果有以下的情形,那么它就是前台进程:
托管用户正在交互的 Activity(已调用 Activity 的 onResume() 方法)
托管某个 Service,后者绑定到用户正在交互的 Activity
托管正在“前台”运行的 Service(服务已调用 startForeground())
托管正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
托管正执行其 onReceive() 方法的 BroadcastReceiver
可见进程
可见进程指的是不包含前台组件,但是会在屏幕上显示一个可见的进程。
如果有如下的一种情形,那就是可见进程:
托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,如果 re前台 Activity启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况。
托管绑定到可见(或前台)Activity 的 Service。
服务进程
通过startService() 方法启动的Service,这个Service没有上面的两种进程重要,一般会随着应用的生命周期。
一般来说,使用 startService() 方法启动的服务且不属于上述两个更高类别进程的就是服务进程。
后台进程
包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。
空进程
不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。
- 参考解答:首先,进程一般指一个执行单元,在移动设备上就是一个程序或应用,我们在Android中所说的多进程(IPC)一般指一个应用包含多个进程。之所以要使用多进程有两方面原因:某些模块由于特殊的需求要运行在单独的进程;增加应用可用的内存空间。
在Android中开启多进程只有一种方法,就是在AndroidManifest.xml中注册Service、Activity、Receiver、ContentProvider时指定android:process属性,如下所示。
可以看到,MyService和MyActivity指定的android:process属性值有所不同,它们的区别如下:
:remote:以:开头是一种简写,系统会在当前进程名前附件当前包名,完整的进程名为:com.shh.ipctest:remote,同时以:开头的进程属于当前应用的私有进程,其它应用的组件不能和它跑在同一进程。
com.shh.ipctest.remote2:这是完整的命名方式,不会附加包名,其它应用如果和该进程的ShareUID、签名相同,则可以和它跑在同一个进程,实现数据共享。
不过,开启多进程会引发如下问题,必须引起注意:
静态成员和单例模式失效
线程同步机制失效
SharedPreferences可靠性降低
Application被多次创建
对于前两个问题,可以这么理解,在Android中,系统会为每个应用或进程分配独立的虚拟机,不同的虚拟机自然占有不同的内存地址空间,所以同一个类的对象会产生不同的副本,导致共享数据失败,必然也不能实现线程的同步。
由于SharedPreferences底层采用读写XML的文件的方式实现,多进程并发的的读写很可能导致数据异常。
Application被多次创建和前两个问题类似,系统在分配多个虚拟机时相当于把同一个应用重新启动多次,必然会导致 Application 多次被创建,为了防止在 Application 中出现无用的重复初始化,可使用进程名来做过滤,只让指定进程的才进行全局初,如下所示。
public class MyApplication extends Application{ @Override public void onCreate() { super.onCreate(); String processName = "com.xzh.ipctest"; if (getPackageName().equals(processName)){ // do some init } } }
多进程通信方式
目前,Android中支持的多进程通信方式主要有以下几种:
AIDL:功能强大,支持进程间一对多的实时并发通信,并可实现 RPC (远程过程调用)。
Messenger:支持一对多的串行实时通信, AIDL 的简化版本。
Bundle:四大组件的进程通信方式,只能传输 Bundle 支持的数据类型。
ContentProvider:强大的数据源访问支持,主要支持 CRUD 操作,一对多的进程间数据共享,例如我们的应用访问系统的通讯录数据。
BroadcastReceiver:即广播,但只能单向通信,接收者只能被动的接收消息。 文件共享:在非高并发情况下共享简单的数据。
Socket:通过网络传输数据。
由于内容较多目前只展示了部分面试题解,该面试题解包含了字节跳动、腾讯、美团、哔哩哔哩、华为、饿了么等20+一线互联网大厂面试原题全面覆盖,还有超完整详细的答案解析!(内含Activity、Fragment、Service、Broadcast Receiver、ContentProvider、数据存储、IPC、View、View事件分发、RecycleView、Viewpager&Fragment、WebView、Bitmap、mvc&mvp&mvvm、Handler、Binder、线程、Bitmap、性能优化、JNI、屏幕适配、设计模式、Android进阶延伸点、Android 开源库分析面试、Java篇、算法面试题汇总、网络面试题、Flutter相关面试题全解析等等)需要的可以通过下方二维码领取。