Android基础3

Activity(A)跳转到Activity(B)又回到Activity(A),怎么保证数据的持久化?

  • 在Activity的onPause()方法中保存数据,比如用SharedPreference

如果跳转到活动B后,活动A被回收,此时再返回活动A,活动A会经历那些阶段?

  • 如果Activity是异常回收的,那么A回收的时候会调用onSaveInstanceState()来临时保存数据,在回到Activity A中时,会重新调用oncreate(),onStart(),然后调用onRestoreInstanceState来将通过onSaveInstanceState()保存的Bundle对象传递进来进行数据的恢复

由于资源相关配置发生改变,导致Activity被杀死和重新创建(例如屏幕旋转)

  • 先调用onSaveInstanceState来保存切换时的数据,然后调用onPause、onStop、onDestroy来销毁当前Activity,接着通过onCreate、onStart、onRestoreInstanceState、onResume来重建Activity并恢复数据

.广播接收器中的onReceive()方法中可以进行读取文件等IO操作吗?为什么?会发生什么?

  • 不能,广播接收器是运行在UI线程中的,所以不能进行耗时操作,如果进行耗时操作的话,容易导致ANR

内部类的分类和特点?
内部类主要有4种:静态内部类、非静态内部类、局部内部类、匿名内部类

  • 静态内部类:被声明为static的内部类,不能访问外部类的非成员变量,只能访问外部类的静态成员变量和静态方法
  • 非静态内部类:可以使用外部类的属性和方法
  • 局部内部类:在代码块中定义的内部类,作用范围为所在的代码块
  • 匿名内部类:没有类名,必须继承其他类或实现其他接口
    • 没有构造函数
    • 不能定义静态成员、方法和类
    • 只能创建匿名内部类的一个实例

Intent的类型?

  • 显示Intent
    通过指定组件的类名来启动一个组件,当然前提是需要知道这个组件的类名
  • 隐式Intent
    当不知道要启动的组件的类名时,用隐式Intent进行启动。
  • 注意:尽量保证在Service中使用显示Intent,因为如果使用隐式Intent的话就并不知道Service会响应该Intent,而Service的启动又不可见

Android中子线程与UI线程的交互?

  • Handler
  • runOnUIThread
  • post
  • AsyncTask

进程间的通信?
管道,队列,信号量,共享内存,socket

造成死锁的条件?
互斥、请求与保持、不可剥夺、循环等待

Http的状态码
200:请求成功
301:永久重定向
302:临时重定向
404:url错误
500:服务端发生错误
503:服务端当前不能处理,一段时间后可能能处理
502:服务器尝试执行请求时,从上有服务器收到了无效的响应
504:服务器尝试执行请求时,没有及时收到上游服务器的响应

tcp1.0,1.1,2.0的区别?
1.0需要主动建立长连接,1.1.默认长连接。1.1支持只发送header信息而不用发送请求体,并且还支持host域
2.0支持多路复用,数据压缩和服务器推送

进程的通信方式?
管道:分为匿名管道和命名管道,有亲缘关系的可以使用匿名管道,没有的使用命名管道
信号量:它是一个计数器,可以用来控制多个进程对共享资源的访问。经常作为一种锁机制来防止多个进程同时访问一个资源
套接字:可以用于不同机器之间的进程通信
共享内存:它可以映射一段能够被其他进程所访问的内存,这段内存由一个进程创建,但是可以被多个进程访问。它是最快的ipc方式

进程的五种状态?
创建状态
就绪状态:进程已经准备好,只要分配到cpu就能够立即执行
执行状态
阻塞状态:正在执行的进程由于某些事件(IO)而暂时无法运行
终止状态:进程结束或者出现错误

tcp/ip五层?
应用层、传输层、网络层、数据链路层、物理层

tcp/ip七层?
应用层、表示层、会话层、传输层、网络层、数据链路层、物理层

Anr?
5s内触摸事件没有响应
10s内broadcastReceiver没有响应
20s内Service没有处理完成

哪些对象可以作为gc root?
虚拟机栈中引用的对象
本地方法栈中引用的对象
方法区中的静态属性引用的对象
方法去中常量引用的对象

Handler的内存泄漏?

  • 当Handler的消息队列还有未处理的消息或正在处理的消息时,消息队列中的Message持有Handler实例的引用,而Handler是一个费静态内部类,所以它持有着外部类的引用(即Activity)。因此这个时候如果想销毁Activity,但是由于被Handler持有着,所以垃圾回收器是无法回收该Activity的
  • 解决方案:由于静态内部类默认不持有外部类的引用,所以可以将Handler的子类设置为静态内部类,同时使用WeakReference弱引用持有Activity实例。或者在Activity结束生命周期,也就是调用onDestroy()方法时,清除Handler消息队列里面的所有消息(也就是调用removeCallbackAndMessage())方法

Android的数据存储?

  • SharedPreference:它以键值对的形式存储在xml中,适用于轻量级存储
  • SQLite:嵌入式数据库,使用sql语言,适用于结构性数据
  • 文件存储:主要通过Java I/O的读写方法写入到文件中,适用于大数据、文件缓存
  • ContentProvider:是Android的四大组件之一,主要用来进行数据共享、交换的

View绘制前的准备

  • 创建Window抽象类的子类PhoneWindow的实例对象
  • 为PhoneWindow对象设置WindowManager对象
  • 创建DecorView对象
  • 在DecorView的content中添加Activity中设置的布局文件
  • 将DecorView添加到WindowManager中
  • 创建ViewRootImpl对象
  • 接下来WindowManager会将DecorView交给ViewRootImpl对象
  • ViewRootImple通过Handler向主线程发送消息,也就是执行performTraversals()方法。该方法就是正式的进行view的绘制

IntentService的工作原理

  • IntentService在onCreate()方法中会通过HandlerThread开启一个新的工作线程
  • 然后创建一个内部Handler也就是ServiceHandler
  • 接着将ServiceHandler与IntentService进行绑定。通过onStartCommand()传递Intent到ServiceHandler并依次插入Intent到工作队列中,逐个发送给onHandleIntent()
  • 最后通过onHandleIntent()依次处理所有Intent对象对应的任务

IntentService的本质就是Handler+HandlerThread
不建议通过bindService()来启动IntentService。因为采用bindService方式启动的话,那么对应的生命周期如下:onCreate()-> onBind() -> onunbind() -> onDestory()也就是说并不会调用onStartCommand()方法,也就不会将消息发送到对应的消息队列,这样的话onHandleIntent就不会回调,那么就无法实现多线程的操作

SharedPreference

简介

SharedPreference是Android平台上的一个轻量级存储方式,提供了string、int、long、set、float、boolean六种数据类型,最终会以xml形式进行存储,通常用来做一些简单数据的持久化缓存

存储机制

当首次创建SharedPreference对象时,会根据文件名将文件下的内容一次性加载到mMap容器中,每次edit时都会创建一个新的EditorImpl对象,当修改或者添加数据时会将数据添加到mModifiled容器中,然后进行commit或者apply操作时会比较mMap和mModifiled数据并对mMap的数据进行修正并写入到文件中,当调用get方式时则直接从mMap中读取

性能问题

  • 跨进程不安全:由于没有跨进程的锁,SharedPreference在跨进程频繁读写的话有可能导致数据全部丢失
  • 加载缓慢:SharedPreference的加载使用了异步线程,并且加载线程没有优先级,如果这个时候读取数据就需要等待文件加载线程的结束,就会导致主线程等待低优先级线程锁的问题
  • 全量写入:SharedPreference的操作是全量操作,只要有改动,就会把整个内容全部写入到文件,这是性能差的主要原因

使用建议

  • 不要存放大的key和value到SharedPreference中,否则会一直存储在内存中得不到释放,内存使用过高会频繁发生GC,导致丢帧甚至ANR
  • 尽量不要存放json和html,这种可以直接文件缓存
  • 跨进程通信的时候使用ContentProvider
  • 提前对SharedPreference进行初始化

HashMap的put和get

从get()方法来看

    public V get(Object key) {  
        if (key == null)  
            return getForNullKey();  
        int hash = hash(key.hashCode());  
        for (Entry<K,V> e = table[indexFor(hash, table.length)];  
             e != null;  
             e = e.next) {  
            Object k;  
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))  
                return e.value;  
        }  
        return null;  
    }  

一共分4步:

  • 首先判断key是否为空,不为空的话通过key.hashcode()算出索引
  • 然后根据索引到数组中获得索引位置所对应的键值对链表(因为hashmap的底层是数组+链表,当冲突的时候就会在数组对应位置生成链表)
  • 接着遍历键值对链表,根据key找到对应的Entry键值对
  • 然后返回value

如果要保证hashmap的时间复杂度为O(1),那么得保证每一步都是O(1),从代码中可以看出假如出现了哈希冲突,也就是成为了链表的话时间复杂度就是O(n)了,我们只有保证链表长度为1时时间复杂度才是O(1),所以hashmap的时间复杂度为O(1)需要我们手动保证,应该尽量减少hash冲突,它只有在理想情况下才能保证时间复杂度为O(1)。

从put方法看

  • 首先会执行hash()方法得到hash值,然后判断table是否为空,为空的话就说明这是第一个元素插入,需要进行初始化,初始化大小默认为16
  • 如果不需要初始化,那么会判断要插入节点的位置是否为空,如果为空则直接插入
  • 如果不为空,那么说明要么是key相同,这时候会调用equals方进行比较。这样的话就是修改value,要么key不同,那么就需要找到链表的下一个节点进行插入操作
  • 插入操作时如果节点为6,那么是一个链表,如果节点有8个了,那么会转变为红黑树再找到对应位置进行插入

App的启动过程

  • Launcher通知AMS启动App的MainActivity
  • AMS记录要启动的Activity的信息,并且通知Launcher进入pause状态
  • Launcher进入pause状态后,通知AMS已经pause了,就可以启动app了
  • 如果这个App没有启动过,AMS就要启动新的进程,并且在新进程中创建ActivityThread对象,执行其中的main函数方法
  • app主线程启动完毕后通知AMS,并传入applicationThread来进行通讯
  • AMS通知App绑定Application并启动MainActivity
  • 启动MainActivity后创建Context并与之关联,最后调用onCreate()方法

Activity和Window的关系

Activity并不负责视图控制,它只是控制生命周期和处理事件。真正控制视图的是Window,Window是一个抽象类,实际Activity持有的是一个PhoneWindow,它才是真正的窗口。Window的内部有一个DecorView,它是View的根布局。DecorView·是FrameLayout的子类,它是Android的根节点视图,它的内部包含一个竖直方向的LinearLayout,其中有标题栏和内容栏,内容栏叫做contentView,所以我们在onCreate()方法中调用的都是setContentView()方法而不是setView()

View的绘制流程

View的绘制主要有三个过程:measure、layout、draw

measure过程

假如是View:那么就直接通过measure()完成测量,measure()中进行基本的测量逻辑判断,在measure()中会调用onMeasure()方法来计算View的宽高并存储,onMeasure()中会调用setMeasureDimension()来存储计算后的View宽高,最后调用getDefaultSize()完成测量

假如是ViewGroup:它除了完成自身ViewGroup的测量外,还会去遍历调用所有子元素的measure方法,在ViewGroup中没有重写onMeasure(),而是提供了一个measureChildren()方法

layout过程

从顶级View开始依次调用layout(),其中子View的layout()会调用setFrame()来设定自己的四个顶点,,接着调用onLayout()来确定坐标

draw过程

它的绘制顺序是:

  • 绘制背景:backgroup.draw()
  • 绘制自己:onDraw()
  • 绘制Children : dispatchDraw()
  • 绘制装饰: onDrawScrollBars()

ListView和RecyclerView的区别?

布局效果不同

listView比较单一,只有纵向效果,RecyclerView支持多种布局(线性布局、表格布局、瀑布流布局)
在RecyclerView中如果存在的LayoutManager不能满足需求,可以在LayoutManager的API中自定义layout

动画效果

RecyclerView中有已经封装好的API来实现自己的动画效果,而ListView并没有实现,需要自己在Adapter中实现Item的动画效果

嵌套滚动机制

在事件分发机制中,Touch事件在进行分发时,由父View向子View传递,一旦子View消费这个时间,那么接下来的事件进行分发就直接由子View处理。但是嵌套滚动机制可以弥补这个不足,使得子View与父View都能够同时处理这个Touch事件。而RecyclerView实现了NestedScrollingChild,所以能够实现嵌套滚动机制

ListView没有

缓存机制对比

层级不同

RecyclerView比ListView多两级缓存,支持多个离开屏幕的ItemView缓存,支持开发者自定义缓存处理逻辑,支持所有RecyclerView共用一个RecyclerViewPool(缓存池)

ListView的两级缓存

  • mActivityViews
    生命周期在onLayout内,用于屏幕内的ItemView快速重用
  • mScrapViews
    生命周期与mAdapter一致,当mAdapter被更换时,mScrapViews被清空

RecyclerView的四级缓存

  • mAttachScrap:生命周期onLayout内,用于屏幕内ItemView的快速重用
  • mCacheViews:与mScrapViews类似,但是它的上限默认为两个,即缓存屏幕外两个ItemView
  • mViewCacheExtension:不直接使用,需要用户自己定制,默认不实现
  • mRecyclerViewPool:默认上限为5,技术上可以使用所有RecyclerView使用同一个RecyclerViewPool

listView和RecyclerView的缓存机制几乎一致

  • mActivityViews与mAttachedScrap功能相似,都是用于快速重用屏幕上可见的ItemView,而不需要重新createView和bindView
  • mScrapView和mCachedViews+nRecyclerViewPool功能相似,用于缓存离开屏幕的ItemView,目的是让即将进行屏幕的ItemView重用
  • RecyclerView的优势在于mCachedViews的使用,可以做到屏幕外的列表项ItemView进入屏幕内时也不需要bindView快速重用,mRecyclerViewPool可以供多个RecyclerView使用,在特定场景比如viewPaper+多个列表页下有优势

缓存不同

  • RecyclerView缓存RecyclerView.ViewHolder,可以理解为View+ViewHolder+flag
  • ListView缓存View

缓存的使用不同

ListView

ListView使用缓存时会先判断源数据是否发生改变,如果发生了改变,则会从mScrapViews中读取View缓存,如果没有发生改变,则从mActivityViews中通过匹配pos获取View缓存,如果成功则返回view,失败就又从mScrapViews中读取view缓存

RecyclerView

RecyclerView先会尝试从mAttachScrap中通过匹配pos获取holder缓存,失败的话再去从mCachedViews中通过匹配pos获取holder缓存,再失败就从mViewCachedExtension中定义holder缓存,还是不行的话就去从mRecyclerViewPool中获取holder缓存,假如在mRecyclerViewPool中获取holder缓存成功的话,那么清楚holder所有标志位,确保能够重新bindView;如果失败的话,那么调用createViewHolder方法
Android基础3_第1张图片

局部刷新

RecyclerView的缓存机制虽然更加完善,但是并不是最大的优点,RecyclerView更大的亮点就是提供了局部刷新的接口,通过局部刷新,就能够避免调用许多无用的bindView

原理

RecyclerView的重新绘制分为三步:onMeasure、onLayout、onDraw
其中onLayout分三步:

  • dispathLayoutStep1():记录RecyclerView刷新前列表项ItemView的各种信息,用于动画的相关计算
  • dispathLayoutStep2():真正测量布局大小、位置,核心函数为layoutChildren()
  • dispathLayoutStep3():计算布局前后各个ItemView的状态

最大区别

ListView和RecyclerView的最大区别在于数据源改变时的缓存的处理逻辑,ListView是将所有的mActivityViews移到二级缓存mScrapViews中,而RecyclerView则是更加灵活地修改每个View的标志位,区分是否需要重新bindView

如果列表页展示界面需要支持动画,频繁刷新,局部刷新,建议使用RecyclerView。ListView在使用上更加简单

你可能感兴趣的:(Android学习笔记)