安卓-面试篇 基础版 一

一、系统架构

1,四大组件

Activity、Service、broadcastReceiver、ContentProvider

1.1 activity 生命周期 、 启动模式
1.1.1 生命周期

https://www.jianshu.com/p/b1ff03a7bb1f

安卓生命周期

纠正:onStart()时处于可见不可交互状态。onResume()处于可见可交互状态

1.1.1.1 当前Activity为A,此时用户打开ActivityB后,那么A的onPause()和B的onResume()哪个方法先执行?
答:先 A的onPause() ,再B的onResume()

Activity的启动过程:由ActivityManagerService(AMS)对栈内的Activity状态进行同步管理 & 规定:新Activity启动前,栈顶的Activity必须先onPause(),才能启动新的Activity(执行onResume())
注:为了让新的Activity尽快切换到前台,在 onPause()尽量不要做耗时 / 重量级操作

1.1.1.2 常见场景的生命周期调用


生命周期II

在清单文件中指定了屏幕方向,则Activity在锁屏和开启屏幕的时候执行的方法和顺序是:MainActivity onPause--->MainActivity onStop--->MainActivity onRestart--->MainActivity onStart--->MainActivity onResume

如果在清单文件中没有对屏幕进行设置,则Activity在锁屏时候执行的方法和顺序是:MainActivity onPause--->MainActivity onStop--->MainActivity onDestory--->MainActivity onCreate--->MainActivity onStart--->MainActivity onResume--->MainActivity onPause销毁之后又新建。

在开启屏幕的时候,Activity执行的方法及顺序是:MainActivity onResume--->MainActivity onPause--->MainActivity onStop--->MainActivity onDestory--->MainActivity onCreate--->MainActivity onStart--->MainActivity onResume。对于这种,锁屏后再次开启屏幕会销毁两次,重建两次。

第二中情况的解决办法:在清单文件里activity标签下配置 android:configChanges="orientation|screenSize"。

1.1.1.3 android:configChanges
用于捕获手机状态的改变。在当所指定属性(Configuration Changes)发生改变时,通知程序调用onConfigurationChanged()函数。

(1)、不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次

(2)、设置Activity的android:configChanges="orientation"时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次

(3)、设置Activity的android:configChanges="orientation|keyboardHidden"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法

但是,自从Android 3.2(API 13),在设置Activity的android:configChanges="orientation|keyboardHidden"后,还是一样 会重新调用各个生命周期的。

因为screen size也开始跟着设备的横竖切换而改变。所以,在AndroidManifest.xml里设置的MiniSdkVersion和 TargetSdkVersion属性大于等于13的情况下,

如果你想阻止程序在运行时重新加载Activity,除了设置"orientation", 你还必须设置"ScreenSize"。

解决方法:

AndroidManifest.xml中设置android:configChanges="orientation|screenSize“

1.1.1.4 finish() onDestory()
finish() 是结束一个activity的生命周期,而onDestory() 则是activity的一个生命周期

finish()调用后,将此activity移出栈,并未及时调用onDestory()方法,释放资源。但因为已出栈,点击back键时,找不到此activity

1.1.2 启动模式
启动模式

1.1.2.1 知识

  • 任务栈:管理activity,后进先出


    image.png
  • 可在 manifest中设置,launchMode。也可通过Intent设置标志位

    intent.addFlags(FLAG_ACTIVITY_NEW_TASK)
    
  • intent 设置优先级更高

  • intent 无法设置单例模式

标记位属性 含义
FLAG_ACTIVITY_SINGLE_TOP 指定启动模式为栈顶复用模式(SingleTop)
FLAG_ACTIVITY_NEW_TASK 指定启动模式为栈内复用模式(SingleTask)
FLAG_ACTIVITY_CLEAR_TOP 所有位于其上层的Activity都要移除,SingleTask模式默认具有此标记效果
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 具有该标记的Activity不会出现在历史Activity的列表中,即无法通过历史列表回到该Activity上

1.2 fragment
1.2.1 生命周期
fragment生命周期
image.png
  • 创建时
    onAttach() onCreate() onCreateCiew() onActivityCreated()

  • 可见时
    onStart() onResume()

  • 进入后台时
    onPause() onStop()

  • fragment或activity被销毁
    onPause() onStop() onDestoryView() onDestory() onDetach()

  • 屏幕熄灭/回到桌面
    onPause() onSaveInstanceState() onStop()

  • 屏幕解锁/回到应用
    onStart() onResume()

Activity中调用replace()方法和addToBackStack()方法时的生命周期
新替换的Fragment(没有在BackStack中):
onAttach > onCreate > onCreateView > onViewCreated > onActivityCreated > onStart > onResume
新替换的Fragment(已经在BackStack中):
onCreateView > onViewCreated > onActivityCreated > onStart > onResume
被替换的Fragment:onPause > onStop > onDestroyView--> onDestroy --> onDetach

Fragment以下4个生命周期方法将跟随所属的Activity一起被调用:
onPause > onStop > onStart > onResume

1.2.2 fragment 和 activity 通信
  • Handler
  • 回调
  • 广播或eventbus
  • 持有对象
  • bundle
  • findFrgamentById/Tag

1.2.3 fragment 间通信

  • 通过activity操作
  • 回调、广播、eventbus
1.2.4 使用

静态添加: xml中作为view直接引用
动态添加步骤:

  • 获取fragmentManager对象
  • 开启一个事务 beginTransaction
  • add remove replace
  • 可以调用addToBackStack(),加入回退栈,管控此fragment
  • 提交事务 commit()
1.2.5 fragment 在viewpager容器的 resume 刷新

setUserVisibleHint(true)

  • 在onResume的时候,如果getUserVisibleHint的值是false,不一定不是当前显示,如果为true就一定是当前显示的Fragment
  • setUserVisibleHint()在Fragment实例化时会先调用一次,并且默认值是false,当选中当前显示的Fragment时还会再调用一次。
  • setUserVisibleHint()可能会在Fragment的生命周期之外被调用,也就是可能在view创建前就被调用,也可能在destroyView后被调用,所以如果涉及到一些控件的操作的话,可能会报 null 异常,因为控件还没初始化,或者已经摧毁了。
  • 控制viewpager 加载数量 mPager .setOffscreenPageLimit(2);
1.3 broadcastReceiver

https://www.jianshu.com/p/ca3d87a4cdf3

使用了观察者模式,将发送者和接收者解耦

1.3.1 应用场景
  • 不同组件简通信(应用内、应用间)
  • 多线程通信
  • 系统通信 【电话呼入,网络可用】
1.3.2 细解
广播模型

广播使用流程
  • 广播接收器运行在UI线程,因此 onReceive()不可执行耗时操作,否则ANR
1.3.3 使用

静态注册:manifest中通过receiver标签声明



//用于指定此广播接收器将接收的广播类型
//本示例中给出的是用于接收网络状态改变时发出的广播
 

    

  • 当 app首启时,系统会自动实例化此 mBroadcastReceiver 类,并注册

动态注册:代码中调用 .registerReceiver()方法

  • 需记得在对应位置【onPause】销毁广播
  • 当此 activity 实例化时,注册此广播。当activity销毁时,动态注册的广播将不再接收消息
  • 建议在 onResume()注册广播,onPause()注销广播,而非 onCreate() & onDestory() , 是因为当系统内存不足,需要回收资源时,activity执行完onPause()之后就会被销毁,此时还未执行 onStop() onDestory(),即广播还未注销,导致内存泄漏


    两种注册方式区别
1.3.4 分类

普通广播、系统广播、有序广播、粘性广播、local broadcast、粘性广播
普通广播:开发者自定义 intent ,匹配接受者的intentFilter的action
系统广播:仅需注册广播接收器,接收系统广播的action

有序广播:优先级别高的广播接收器先接收,接收完了没有丢弃/修改(abortBroadcast()),传给次一级的广播接收器

  • 优先级相同的情况下,动态注册的广播优先

粘性广播:发送之后一直存在于消息处理器中,等待对应的接收器去处理。如果接收器被销毁,则会在下次重建时自动接收消息数据

  • 需要 权限 BROADCAST_STICKY
  • 没有10秒限制,10秒限制是指普通的广播如果onReceive方法执行时间太长,超过10秒的时候系统会将这个广播置为可以干掉的candidate,一旦系统资源不够的时候,就会干掉这个广播而让它不执行。
  • 5.0后已 不建议使用

应用内广播
存在原因:

  • 其他App发出与当前app广播接收器相匹配的广播,导致当前app不断接收广播信息
  • 其他app注册与当前app一直的接收器,接收当前app的广播,出现安全问题

应用内广播保证发送者和接受者属于同一个app
LocalBroadcastManager。接受者只能动态注册,不能静态注册

全局广播 -> 应用内广播 :

  • exported=false 使得非本app发送的广播不被接收
  • 增加permission
  • 发送广播时,指定包名 intent.setPackage(包名)
1.3.5 onReceive()参数context上下文区别

不同注册方式的广播接收器回调OnReceive(Context context,Intent intent)中的context返回值是不一样的:

  • 对于静态注册(全局+应用内广播),回调onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext;
  • 对于全局广播的动态注册,回调onReceive(context, intent)中的context返回值是:Activity Context;
  • 对于应用内广播的动态注册(LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Application Context。
  • 对于应用内广播的动态注册(非LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Activity Context;
1.4 service 使用和生命周期、跨进程通信
1.5 contentProvider

https://www.jianshu.com/p/ea8bc4aaf057
结合消息模块message,存储数据库交互一起看

作用:进程间数据共享和交互


作用

原理: binder机制,分析见后文

1.5.1 contentProvider 使用

URI:统一资源标识符,外部线程通过URI找到对应的ContentProvider和其中的数据,并对数据进行操作
分为系统预置 和 自定义URI

Mime:指定数据类型,及打开的程序 。 = 类型+子类型

 text/html
 application/pdf

ContentProvider 以表格的形式,组织和管理数据【增删改查】
用于外部进程对数据进行操作的 实现。但不会直接和外部进程交互。

 ContentProvider.getType(uri)
  • 需注意线程同步
  • 若配合SQLite使用,则不需要考虑同步,因为SQLite内部实现了线程同步。 但使用多个SQLite时仍需考虑同步
  • 若存储数据到内存等,需手动实现线程同步

ContentResolver:通过Uri操作ContentProvider的数据
外部进程,通过ContentResolver , 与 ContentProvider 交互

为何不允许直接交互?
一款应用若使用多个contentProvider,直接交互需了解每个contentProvider的不同实现,成本高

提供了增删改查四个外部类
提供了三个辅助工具类: ContentUris、UriMatcher、ContentObserver

1.5.2 ContentResolver 辅助工具类

ContentUris : 操作URI

      parseId()   withAppendedId()

UriMatcher:
向ContentProvider注册URI : matcher.addURI()
根据URI匹配ContentProvider中对应的数据表:matcher.match()

ContentObserver:内容观察者。观察指定URI引起的ContentProvider的数据变化,通知外界【即ContentObserver注册者】。即当ContentProvider的数据发生变化时,会触发ContentObserver的监听

    getContentResolver().registerContentObserver(URI)
    @Override onChange()

你可能感兴趣的:(安卓-面试篇 基础版 一)