美文网首页Android开发Android开发Android技术知识
<查漏补缺_Android基础系列>视图组件

<查漏补缺_Android基础系列>视图组件

作者: 玉圣 | 来源:发表于2019-04-11 16:38 被阅读11次

本文主要总结的内容如下:

  • 一、组件
    1、Activity
    2、Service
    3、Activity与Service通信
    4、BroadcastReceiver
    5、ContentProvider
    6、Intent
    7、Fragment
    8、AndroidManifest.xml
  • 二、布局
    1、五大布局
    2、优化布局方式
  • 三、控件
    1、WebView
    2、RecyclerView

阅读建议:
不要一下子全看完,这些总结是我几天分段总结的,可以分步阅读,如果在阅读过程中发现有不准确或错误的地方,希望可以多多指正批评,谢谢。


一、组件

1、Activity

1.1、Activity是什么
  • 一般地,一个用户交互界面对应一个Activity。
  • Activity是Context 的子类,同时实现了Window.callbackkeyevent.callback,可以处理与窗体用户交互的事件。
1.2、Activity生命周期

生命周期描述的是一个类从创建(new 出来,Activity不是new出来的)到死亡(垃圾回收)的过程中会执行的方法。

  • 1、Activity生命周期方法:

    • onCreate():创建,activity实例由无到有。绑定布局,绑定控件,初始化数据等做一些初始化的工作。
    • onStart():开始,activity可见。但是还没有出现在前台,无法与用户进行交互。
    • onResume():可编辑。出现在前台,可以与用户进行交互,Activity处于运行状态。
    • onPause() :暂停。暂停与用户的交互。一般情况下onStop方法会紧接着被回调。
    • onStop():停止,activity不可见。可以做一些资源释放的操作(不能太耗时)。
    • onRestart():在Activity被onStop 后,但未被onDestory ,再次启动此Activity时就会调用onRestart()
    • onDestory():销毁,activity实例由有到无。做一些回收工作和最终的资源释放。
  • 2、两个Activity跳转执行的生命周期方法:

    • AActivity 跳转到BActivity,执行方法如下:
      onPause() [AActivity] --> onCreate() [BActivity] --> onStart() [BActivity]--> onResume() [BActivity]--> onStop() [AActivity]

      从执行的顺序可以看出,要先执行完AActivityonPause()方法,才能开启下一个界面BActivity,而当BActivity处于前台时,才执行的onStop() [AActivity],所以在保存数据(尤其是耗时操作)时,最好不要在onPause()中进行,应该尽量在onStop()中(也不建议做耗时操作)进行操作,尽可能的让下一个activity尽快显示出来。

    • BActivity是完全透明的主题背景(或对话框),则执行方法如下:
      onPause() [AActivity] --> onCreate() [BActivity] --> onStart() [BActivity]--> onResume() [BActivity]

  • 3、Activity生命周期图示:


    Activity生命周期方法
  • 4、保存和恢复View层次结构:
    首先当Activity意外终止时,Activity会调用onSaveInstanceState() 去保存数据,然后Activity会委托(通知)Window去保存数据,接着Window在委托(通知)它上面的顶级容器(是一个ViewGroup,一般很可能是DecorView)去保存数据。最后顶级容器一一通知它的子元素来保存数据,整个保存的过程就完成了。

    这是一个典型的委托思想,上层委托下层,父容器去委托子元素去处理一件事情。

1.3、启动模式与任务栈

当启动一个Activity后,此Activity就会被放入到任务栈中,当点击返回键的时候,位于栈顶的Activity就会被清除出去,当任务栈中不存在任何的Activity实例的时候,系统就会回收这个任务栈,则应用程序就会退出。

  • 1、启动模式:

    • standard(默认启动模式):
      每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否存在。被创建的实例的生命周期符合典型情况下的Activity的生命周期。

    • singleTop(栈顶复用启动模式):
      在这种模式下,如果新的Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的onNewIntent() 方法被回调,通过此方法的参数我们可以取出当前请求的信息。这个Activity的onCreate,onStart不会被系统调用,因为它并没有发生改变。

      若新的Activity不在栈顶,则会重新创建此Activity,而不会复用。

    • singleTask(栈内单例启动模式):
      在当前任务栈中,此种模式的Activity只存在一个实例,多次启动不会重复创建,而会调用onNewIntent() 方法。若栈中此Activity实例之上还有其他Activity实例,则在启动此Activity时,会将其上的其他实例都清除掉,这是由于singleTask默认具有clearTop 的效果。

    • singleInstance(单实例启动模式):
      具有此种模式的Activity只能单独位于一个任务栈中,即启动此Activity后,系统会为它创建一个新的任务栈(在不存在的情况下),再次启动,则不会再次创建。

  • 2、singleInstance模式特点:

    • singleInstance 模式的Activity具有全局唯一性,只存在一个这样的实例,如已存在,系统会重用。
    • singleInstance 模式的Activity具有独占性,独自占用一个任务栈,被它开启的Activity都会运行在其他的任务栈中。
    • singleInstance 模式的Activity开启的其他Activity,能在新的任务栈启动,但不一定是开启新的任务栈,也可能在一个已有的任务栈开启。
  • 3、任务栈:

    • 任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity位于暂停状态,用户可以通过切换将后台任务栈再次调用到前台。
    • TaskAffinity(任务相关性):
      此参数标识了一个Activity所需要的任务栈的名称。
      默认情况下,所有Activity所需的任务栈的名字为应用的包名。
      可以通过TaskAffinity为一个Activity单独指定任务栈的名称。
任务栈与回退栈
  • 任务栈:属于应用级别,维护着当前应用中Activity的一种队列。
  • 回退栈:属于系统级别,非特定应用独享,由ActivityManager 维护。所有应用的activity 实例都共享该回退栈。

2、Service

2.1、开启Service:
  • 1、startService
    开启服务,服务一旦开启,就长期在后台运行,即使调用者退出来,服务还会长期运行;
    资源不足时,被杀死,资源足够时,又会复活。(依据startCommand方法返回值决定)
  • 2、bindService
    绑定服务,绑定服务的生命周期会跟调用者关联起来,调用者退出,服务也会跟着销毁;
    通过绑定服务,可以间接的调用服务里面的方法(onBind 返回IBinder);
2.2、Service生命周期:

Service 根据启动的方式不同,其对应的生命周期方法也不同:

  • 1、startService(...) 方法启动服务的生命周期方法:

    • onCreate():创建服务时调用。
    • onStartCommand():启动服务时调用。每次通过startService(Intent) 方法启动服务时调用一次。
    • onDestory():服务销毁时调用。
    • 流程:
      1)startService():如果没创建就先onCreate()startCommand(),如果已创建就只执行startCommand()
      2)stopService():执行onDestroy()
  • 2、bindService(...) 方法绑定服务的生命周期方法:

    • onCreate():创建服务时调用。
    • onBind():每次绑定服务时调用,返回来自ServiceConnection.onServiceConnected(ComponentName, IBinder) 方法的IBinder 对象。
    • onUnbind():服务解绑时调用。
    • onDestory():服务销毁时调用。
    • 流程:
      1)bindService():如果没有创建就先onCreate()onBind()
      2)unbindService() :如果服务是在绑定时启动的,先执行onUnbind()再执行onDestroy()。 如果服务在绑定前已启动,那么只执行onUnbind()
2.3、onStartCommand返回值

在 sdk 2.0 及其以后的版本中,对应的 onStart 已经被否决变为了 onStartCommand,不过之前的 onStart 任然有效。这意味着,如果你开发的应用程序用的 sdk 为 2.0 及其以后的版本,那么你应当使用 onStartCommand 而不是 onStart
实现onStart等同于重写onStartCommand并返回START_STICKY
此方法返回值控制当Service被运行时终止后,系统应该如何响应Service的重新启动:

  • 1.START_STICKY:返回此值,在运行时终止Service后,当重新启动Service时,将会调用onStartCommand() 。而传入onStartCommand()中的Intent 参数将是null

  • 2.START_NO_STICKY:这种模式用于启动已处理特殊的操作和命令的Service。通过当命令完成后,这些Service会调用stopSelf终止自己。

    当被运行时终止后,只有当存在未处理的启动调用时,设为这个模式的Service才会重新启动。如果在终止Service后没有进行startService调用,那么Service将停止运行,而不会调用onStartCommand()

    对于处理特殊请求,尤其是诸如更新或网络轮询这样的定期处理,它是一种理想模式。这种模式比较谨慎,当停止Service后,它会在下一个调度间隔中尝试重新启动,而不会在存在资源竞争时重新启动Service。

    如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务。

  • 3.START_REDELIVER_INTENT:这种模式是前两种模式的组合,若Service被运行时终止,则只有当存在未处理的启动调用或进程在调用stopSelf 之前被终止时,才会重新启动Service。后一种情况下, 将会调用onStartCommand,并传入没有正常完成处理的Intent。

  • 4.START_STICKY_COMPATIBILITYSTART_STICKY的兼容版本,但不保证服务被kill后一定能重启。

2.4、相关问题:
  • 1、service能不能刷新UI,耗时操作?
    Service是运行在UI线程中的组件,虽然是后台组件,无法与用户直接进行交互,但是可以进行UI组件的更新,这就需要通过Service和Activity进行通信,从而实现UI组件的更新。
    Service属于后台服务,虽然不可见,但也是运行在UI线程的组件,进行耗时操作都需要放在子线程中进行处理。

  • 2、service被系统杀死,如何启动?

    • onStartCommand方法的返回值设置为START_STICKY,当Service被系统杀死时,可以进行重启。
    • 通过startForeground 将Service设为前台Service
    • 可以通过监听广播的方式,隔段时间来监测Service是否被杀死,进行Service重启。【Intent.ACTION_TIME_TIC系统时钟广播】

相关链接:
Android如何降低service被杀死概率

3、Activity与Service通信

  • 通过绑定的方式,使用Binder 进行Activity和Service的通信。

    //connection为自定义的ServiceConnection
    bindService(bindIntent, connection, BIND_AUTO_CREATE);
    
  • 通过广播形式进行通信。
    通过在Activity中创建广播,并接受由Service发送的广播数据来进行数据通信。

4、BroadcastReceiver

在自定义的BroadcastReceiver 中,onReceive方法处理程序必须要在5秒内完成,否则会出现ANR的错误提示。

4.1、分类:
  • 1、普通广播:不能中断;只能接收数据,不能修改发送的数据进行传递。
  • 2、有序广播:广播可中断,通过abortBroadcast() 方法;接受者之间可以传递发送者的数据。
  • 3、本地广播:只能在当前应用内传播。使用LocalBrodcastManager
    • 优点:
      a. 由于本地广播(局部广播)的作用域要小一些,所以比全局广播更高效。
      b. 确保了应用程序外部的任何组件都收不到你的广播Intent,因此不会有私人数据或敏感数据(如位置信息)泄漏出去的风险。
      c. 其他应用程序也不能向你的接收器发送广播,避免了这些接收器成为安全漏洞。
    • 限制:
      a. LocalBrodcastManager不支持有序broadcast(虽然它有个sendBroadcastSync(Intent intent)方法,但依然不灵)
      b. sendBroadcastSync方法不支持在独立线程上发送和接收broadcast,并且此方法直到每个已注册的接收器都接收到广播后才解除阻塞。
4.2、注册方式:
  • 1、在AndroidManifest.xml中注册(manifest接收器)

    • 应用程序可以在不处于运行状态时执行接收。
    • 当有匹配的Intent被广播时,它们(接收此广播的应用程序的manifest接收器)会被自动的启动。
  • 2、在代码中注册

    • 影响特定Activity的UI的Broadcast Receiver 通常在代码中注册。
    • 在代码中注册的接收器只会在包含它的应用程序组件运行时响应。
    • 一般在onResume方法中注册接收器,在onPause方法中注销接收器。

Android应用在未启动的情况下接受指定广播
从Android 3.1开始,系统给intent定义了两个新的Flag,分别为FLAG_INCLUDE_STOPPED_PACKAGES(表示包含未启动的App)和FLAG_EXCLUDE_STOPPED_PACKAGES(表示不包含未启动的App),用来控制Intent是否要对处于停止状态的App起作用,而默认所有广播intent flag都是FLAG_EXCLUDE_STOPPED_PACKAGES
要在应用未启动时接收到广播,就需要在发送的广播上添加flag:

FLAG_INCLUDE_STOPPED_PACKAGES

相关资料:https://www.jianshu.com/p/ac1ed1782adf

5、ContentProvider

  • 内容提供者可跨程序访问,这可以认为是一种进程间通信的方式,其实它原理核心就是Binder。
  • 共享应用程序内的数据, 在数据修改时可以监听
    1、特点
    ①、可以将应用中的数据对外进行共享;
    ②、数据访问方式统一,不必针对不同数据类型采取不同的访问策略;
    ③、内容提供者将数据封装,只暴露出我们希望提供给其他程序的数据(这点有点类似Javabeans);
    ④、内容提供者中数据更改可被监听;

暂时不做具体总结,后续可能进行更新。
参考链接:
ContentProvider篇

6、Intent

6.1、简介:
  • Intent是一种消息传递机制,可以在应用程序内使用,也可以在应用程序间使用。
  • 使用Intent来传播动作,而不是显式地加载类,这是一条基本的Android设计原则。它鼓励组件之间的程序分离,允许无缝地替换应用程序元素。
6.2、分类:
  • 显式意图:
    通过指定组件名,来启动组件。
  • 隐式意图:
    通过Intent的ActionDataCategory来匹配指定组件的IntentFilter,进行启动。
6.3、Action(动作):
  • 1、配置方式:

    • 在AndroidManifest.xml中配置<action> 标签
    • 在代码中设置:
        //直接传入Intent中
        Intent intent = new Intent(Intent.ACTION_XXX);
        //设置
        setAction(Intent.ACTION_XXX);
      
  • 2、系统提供的常用Action:

    • ACTION_MAIN
      任务的主Activity,一般在AndroidManifest.xml 中配置
         <action android:name="android.intent.action.MAIN" />
      
    • ACTION_VIEW
      打开视图,查看Intent的数据URI提供的数据。通常包括如下情况:
      http:打开浏览器;tel:打开拨号程序;geo:显示Geegle地图的位置
    • ACTION_DIAL
      打开一个拨号程序。要拨的号码由Intent的URI提供。
    • ACTION_CALL
      打开一个电话拨号程序,并立即使用Intent的数据URI提供的号码拨打电话。
    • ACTION_SEND
      启动一个Activity,该Activity会发送Intent中指定的数据。接收人需要由解析的Activity来选择。
    • ACTION_WEB_SEARCH
      打开一个浏览器,根据SearchManager.QUERY 键提供的查询执行web搜索。
    • ACTION_ALL_APPS
      打开一个列出所有已安装应用程序的Activity。通常,此操作由启动器处理。
    • ACTION_ANSWER
      打开一个来电的Activity,通常这个动作由本地电话拨号程序进行处理。
  • 3、匹配规则:
    Intent中的action必须能够和过滤规则中的action匹配(需要和action的字符串完全一样),
    一个过滤规则中有多个action,只要Intent中的action能够和过滤规则中的任何一个action相同,即可匹配成功。
    action的匹配要求Intent中的action存在且必须和过滤规则中的其中一个action相同。

6.4、Category(类别):

当Action相同时,靠Category属性来细化和区分。它可以配合Action属性构成了<intent-filter>。

  • 1、配置方式:
    • 在AndroidManifest.xml中配置<category> 标签
    • 在代码中设置:
        Intent intent = new Intent();
        //设置
        Intent.addCategory(category...);
      
  • 2、系统提供的常用Category:
    • CATEGORY_DEFAULT:(android.intent.category.DEFAULT) Android系统中默认的执行方式,按照普通Activity的执行方式执行。
    • CATEGORY_HOME: (android.intent.category.HOME) 设置该组件为Home Activity(显示桌面)。
    • CATEGORY_LAUNCHER: (android.intent.category.LAUNCHER) 设置该组件为在当前应用程序启动器中优先级最高的Activity,通常与程序入口动作ACTION_MAIN配合使用。
  • 3、匹配规则:
    要求intent中如果含有category,则所有的category都必须和过滤规则中的某一个category相同。
    Intent中可以没有category,因为可以匹配系统默认的category.DEFAULT
6.5、Data(数据):

Data属性通常用于向Action属性提供重要操作的数据。例如拨打指定电话、发送短信指定电话号码和内容等数据。Data属性的值是一个Uri(统一资源标识符)对象。

  • 1、配置方式:
    • 在AndroidManifest.xml中配置<data> 标签
    • 在代码中设置:
        Intent intent = new Intent();
        //设置
        Intent. setDataAndType(Uri data, String type);
      
  • 2、结构参数说明:
    data由两部分组成:mimeTypeURI
    • mimeType:只媒体类型,如image/jpegaudio/mpeg4-genericvideo/*
    • URI:<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
      例如:
         content://com.example.project:200/folder/subfolder/ect
         http://www.baidu.com:80/search/info
      
      • Scheme:URI的模式,比如httpfilecontent
      • Host:URI主机名,如www.baidu.com,如果host未指定,则整个URI的其他参数无效,此URI也无效。
      • Port:URI端口号,如80,只有Scheme和Host都存在,才有意义。
      • path、pathPrefix、c:表示路径信息。
        path:完整的路径信息。
        pathPattern:表示完整的路径信息,其中可包含通配符:*
        pathPrefix:表示路径的前缀信息。
6.5、Android如何解析Intent Filter

当在startActivity中传入一个隐式Intent时,决定启动哪一个Activity的过程叫做intent解析。

intent 解析的目的是使用以下步骤来找出最匹配的intent Filter:

  • 1、Android 将已安装包的可用的Intent Filter放到一个列表中。
  • 2、那些与解析Intent 时相关联的动作或者类别不匹配的Intent Filter 将会从列表中移除。
    • 若Intent Filter包含了指定动作,则认为动作匹配了,否则检查到没有任何一个动作和Intent指定的动作匹配,则失败。
    • Intent Filter必须包含待解析的Intent中的搜索category,但可以包含Intent中所没有的其他category。一个没有指定category的Intent Filter只能和没有任何category的Intent匹配。
  • 3、最后,Intent的数据URI每一个部分都和Intent Filter的data标签进行比较。
    • Intent Filter指定的schemehost/authoritypathMIME类型 都要和Intent的URI一一匹配才算成功。
    • 若没有指定数据值的Intent Filter将和所有的Intent数据值匹配。
  • 4、当隐式启动一个Activity时,若这个进程解析出多个组件,则所有可能匹配的组件都会呈现给用户,供其选择。

7、Fragment

7.1、简介:
  • 1、碎片:

    • Fragment称之为碎片,可将Activity拆分成多个完全独立封装的可用的组件。
    • 每个组件都有它自己的生命周期和UI布局。
    • 为不同屏幕大小设备创建动态、灵活的UI。
    • 与Activity绑定在一起,可供多个Activity重用。
  • 2、分类:

    • android.app.Fragment
      a、Android 3.0(API 11)及以上版本可使用。
      b、所绑定的Activity正常继承Activity即可。
      c、获取FragmentManager的方式:getFragmentManager()
    • android.support.v4.app.Fragment
      a、Android 1.6(API 4)兼容版本可使用。
      b、所绑定的Activity必须继承FragmentActivity。(如果你继承自AppCompatActivity则不用担心,因为它本身就继承自FragmentActivity
      c、获取FragmentManager的方式:getSupportFragmentManager()
  • 3、说明:
    从源码中可以看出,在android.app.Fragment中,其上注解着@Deprecated,而且其上注释也建议用android.support.v4.app.Fragment代替:

       /*
        * //.......
        *
        * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
        *      {@link android.support.v4.app.Fragment} for consistent behavior across all devices
        *      and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
        */
    
7.2、Fragment的生命周期
  • 1、Fragment生命周期方法:
    • onAttach(Context):将Fragment关联到它的父Activity上
      这个方法中会调用过时的方法onAttach(Activity)
      此方法可以获得关联的Activity的引用。

    • onCreate():初始创建Fragment

    • onCreateView():创建Fragment中的用户界面,在这个方法中进行Fragment的UI的创建、填充,并返回一个View实例。

    • onActivityCreated():Activity和Fragment都创建完成后,回调此方法。
      此方法常用于做一些Fragment的初始化工作,尤其是那些父Activity辈初始化完成后,或Fragment的View被完全填充后才能做的工作。

    • onStart():在 可见 生命周期的开始时调用。
      此时Fragment是可见的,应用所有需要的UI变化。但还不能与用于进行交互。
      此方法是在Activity的onStart()之前调用。

    • onResume():在 活动 生命周期爱上的时候被调用,此时已可与用户交互。
      在这里恢复所有暂停的Fragment需要的UI更新,线程或进程。
      此方法是在Activity的onResume之后调用。

    • onPause():在 活动 生命周期结束时调用,停止与用户交互。
      当Activity不是活动的前台Activity时,需要暂停UI更新、挂起线程或暂停那些不需要更细的CPU的集中处理。由于调用这个方法后,进程可能被终止,所以要保存所有的编辑和状态改变信息。
      此方法是在Activity的onPause之前调用。

    • onStop():在 可见 生命周期结束时调用该方法。
      此方法是在Activity的onStop方法之前调用。

    • onDestoryView():当Fragment的View被分离时,调用该方法。
      在此处可清除资源相关的View。

    • onDestory():在Fragment整个生命周期结束时调用。
      在此处清除所有的资源,包括结束线程和关闭数据库链接等等。

    • onDetach():当Fragment从它的父Activity上分离时,调用此方法。
      如果Activity实例被销毁了,则是在Fragment从其移除后,才调用的onDestory方法。

    • 2、Activity生命周期图示:


      Activity和Fragment生命周期对应关系

相关链接:Activity和Fragment生命周期函数对应关系图

7.3、Fragment的使用与管理
  • 1、添加Fragment:

    • 在activity布局中添加fragment:
      通过在布局中添加fragment 标签,并将指定继承自Fragment 的类的全路径赋值在布局中的name属性上,如下:

       <fragment
           android:id="@+id/fg_container"
           android:name="com.demo.FgDemoFragment"
           android:layout_width="match_parent"
           android:layout_height="match_parent"/>
      

      一旦一个Fragment 被填充后,它就成为了一个ViewGroup ,会在Activity内显示和管理它所包含的UI。

    • 在activity代码中添加fragment:
      先在activity布局中放置一个承载fragment的容器。然后在activity的onCreate方法中通过FragmentManager 来将fragment添加到activity预置的容器中,一般常用框架布局——FrameLayout

       <FrameLayout
           android:id="@+id/fl_container"
           android:layout_width="match_parent"
           android:layout_height="match_parent"/>`
      
          @Override
          protected void onCreate(@Nullable Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
      
              FragmentManager fm = getSupportFragmentManager();
              Fragment fg = fm.findFragmentById(R.id.fl_container);
              
              if (fg == null) {
                  fg = new FgDemoFragment();
                  fm.beginTransaction()
                      .add(R.id.fl_container, fg)
                      .commit();
              }
          }
      

      其中add 方法第一个参数是activity布局中的容器id,第二个参数就是需要添加的fragment。最后必须通过commit方法将其提交,此为fragment事务

      说明:
      这里建议先通过id查找容器中是否存在fragment的实例,没有再进行创建添加。因为在某些情况下(如屏幕旋转导致的页面销毁重建),系统会保存fragment的状态,所以可以通过这种方式来恢复fragment之前的状态。(除非你确定需要每次都重建,则可以不用。)

    • 两种方式的区别:
      布局添加:简单但不够灵活,和activity绑在一起,在activity生命周期中无法替换fragment视图,复用性较低。
      代码添加:复杂却可动态控制,自由添加和移除,复用性较高。

  • 2、FragmentManager:

    • FragmentManager 类负责管理Fragment,并将它们的视图添加到activity的视图层级结构中:
      FragmentManager图解
    • fragment 事务被用来添加、移除、附加、分离或替换fragment 队列中的fragment,这是使用fragment 动态组装和重新组装用户界面的关键,FragmentManager 管理着fragment事务回退栈。
    • 容器视图资源id(R.id. fl_container)作用:
      1、告知FragmentManagerfragment 视图在activity视图中的位置。
      2、唯一标识FragmentManager 队列中的fragment,通过此ID可以识别并找到指定的fragment
      3、这是FragmentManager 的一种内部实现机制。
      4、若向activity 中添加多个fragment 通常需要分别为每个fragment 创建具有不同ID的不同容器。
  • FragmentManagerfragment 生命周期:
    activity中的FragmentManager 负责调用队列fragment 的生命周期方法:

    • 当添加fragmentFragmentManager 管理时,会调用如下方法:
      onAttach(Context)onCreate(Bundle)onCreateView(...)
      activityonCreate(Bundle) 方法执行后,onActivityCreated(Bundle)也会被调用。
    • activity 在运行状态时,添加fragmentfragment 调用的生命周期方法:
      onAttach(Context)onCreate(Bundle)onCreateView(...)onActivityCreated(Bundle)onStart()onResume()
      这被称之为:FragmentManager驱赶Fragment,快速跟上activity的步伐(与activity最新状态保持同步)。
7.4、Fragment事务和回退栈
  • 1、回退栈:
    fragment 被作为一个新的视图,就可能期望通过Back按键返回到前一个视图(如果也是fragment 时),同样包括回滚前一个已经执行的Fragment Transaction(事务)。

    为达到上述目的,通过addToBackStack 方法,将Fragment Transaction(事务)添加到back栈中:

         FragmentManager fm = getSupportFragmentManager();
         //假设这里是第二个Fragment,第一个已经添加到activity布局容器中了
         Fragment fg2 = new Fragment2();
         FragmentTransaction ft2 = fm.beginTransaction();
         ft2.replace(R.id.fl_container, ft2);
         //将当前的事务添加到了回退栈
         ft2.addToBackStack(null);
         ft2.commit();
    

    强调 --> 加入回退栈中的是Fragment Transaction,管理fragment的事务。

    通过FragmentManagerpopBackStack(); 将当前的事务退出回退栈。

  • 2、Fragment 事务
    一个fragment实例添加(或移除等操作)都是通过一个FragmentTransaction(事务)进行的,实际就是作为一个记录(BackStackRecord,继承FragmentTransaction)存在fragment队列ArrayList<Op> mOps集合,Op中持有fragment引用)中的。

    关于addToBackStack方法到底做了些什么?看源码:

        @Override
        public FragmentTransaction addToBackStack(String name) {
            if (!mAllowAddToBackStack) {
                throw new IllegalStateException(
                        "This FragmentTransaction is not allowed to be added to the back stack.");
            }
            mAddToBackStack = true;
            mName = name;
            return this;
        }
    

    其中只是将mAddToBackStack改为了true,并给mName赋值。
    修改mAddToBackStack主要是在commit的时候用的,用来说明要将当前事务加入到回退栈(ArrayList<BackStackRecord> mBackStackIndices; 集合)中。(在方法BackStackRecord#commitInternal中)

    mName(还有计算出来的mIndex)是用来在出栈FragmentManager#popBackStackState 时,找到指定fragment用的,简单说就是当前fragment的标识名称。
    注:这个BackStackRecord#mNamefindFragmentByTag(tag) 中的FragmentManager#mTag没有关系。

    相关链接:Android开发之Fragment动态使用

7.5、Fragment通信
  • 1、FragmentActivity 之间的通信方式:

    • Activity --> FragmentActivity实例方法
       BaseActivity baseAct = (BaseActivity) getActivity();
       baseAct.dispatchFragment(tag);
    

    个人想法:将方法定义到模板类BaseActivity 中,并定义一个tag 队列(存储fragment标识tag的集合),在fragment中调用这个方法时,传入此fragmenttag,以此来区分一个activity实例中不同的fragment

    • Fragment --> Activity接口通信
      即在Fragment 中定义一个接口,在Activity 中创建fragment的时候,实现此接口,通过回调来接收fragment 中的数据。
  • 2、FragmentFragment 之间的通信方式:

    • 通过1中的两种方式结合,AFragment先和Activity通信,然后通过Activity进行转发消息到BFragment,实现两个Fragment的通信。
    • 另一种就是通过路由 转发的方法,创建一个路由类,其中通过handler来进行消息转发和处理,并提供对外接口,在AFragmentBFragment中分别实现路由类的接口,以此来建立两者的连接。
  • 3、其他通信方式:

    • 广播
    • EventBus
7.6、Fragment懒加载
  • 1、简介:
    Fragment懒加载就是对fragment 实例中的数据请求,或页面初始化等进行延迟加载的一种方式。其目的是为了节省资源,并提高应用性能和用户体验。

  • 2、问题:
    ViewPager中嵌入Fragment时,由于ViewPager 的预加载机制,会预先加载三个页面,就导致未显示的fragment实例也在默默的在做页面数据的加载,如果页面过于复杂,情况会更加糟糕。会有以下的问题:

    • 页面开启缓慢,严重则导致卡顿,影响用户体验,
    • 浪费资源,损耗用户的流量。
  • 3、机制:
    Fragment懒加载 主要是利用了setUserVisibleHint(boolean isVisibleToUser)这个方法,并在相应的生命周期(主要是onActivityCreated方法)的时机进行标志(页面初始未完成页面初始完成数据加载中数据加载完成)的逻辑处理,从而达到数据延迟加载的效果。

    关于标记的方式:

    • 通过两个boolean变量标记来记录状态,初始化状态和加载状态
    • 通过一个int变量(二进制00)标记来记录状态,一位表示初始化状态,一位表示加载状态,利用位运算来表示不同状态。

具体的代码就不贴出来了,后续可能会单独写一篇相关的文章,我找了一篇相关的文章,可以参考一下:仿今日头条N个fragment懒加载

相关链接:
转译:android之Fragment(官网资料翻译)
Android面试系列文章2018之Android部分Fragment篇

8、AndroidManifest.xml

8.1、简介:
  • AndroidManifest.xml 是每个android程序中必须的文件。
  • 它位于整个项目的根目录,描述了package中暴露的组件(activities, services, 等等),他们各自的实现类,各种能被处理的数据和启动位置。
  • 除了能声明程序中的Activities, ContentProviders, Services, 和Intent Receivers,还能指定permissions和instrumentation(安全控制和测试)
8.2、AndroidManifest.xml结构
<?xmlversion="1.0"encoding="utf-8"?> 
<manifest> //根节点,描述了package中所有的内容
    <uses-permission/>  //请求你的package正常运作所需赋予的安全许可。一个manifest能包含零个或更多此元素
    <permission/>   //声明了安全许可来限制哪些程序能使用你的package中的组件和功能。一个manifest能包含零个或更多此元素
    <permission-tree/> 
    <permission-group/> 
    <instrumentation/>   //声明了用来测试此package或其他package指令组件的代码。一个manifest能包含零个或更多此元素
    <uses-sdk/>   //指定当前应用程序兼容的最低sdk版本号
    <uses-configuration/>  
    <uses-feature/>  
    <supports-screens/> 
    <application> 
       <activity> 
           <intent-filter> 
               <action/> 
               <category/> 
           </intent-filter> 
       </activity> 
       <activity-alias> 
           <intent-filter></intent-filter> 
           <meta-data/> 
       </activity-alias> 
       <service> 
           <intent-filter></intent-filter> 
           <meta-data/> 
       </service> 
       <receiver> 
           <intent-filter></intent-filter> 
           <meta-data/> 
       </receiver> 
       <provider> 
           <grant-uri-permission/> 
           <meta-data/> 
       </provider> 
       <uses-library/> 
    </application> 
</manifest>
8.3、作用:
  • 为应用的 Java 软件包命名。软件包名称充当应用的唯一标识符。
  • 描述应用的各个组件,包括构成应用的 Activity、服务、广播接收器和内容提供程序。它还为实现每个组件的类命名并发布其功能,例如它们可以处理的 Intent 消息。这些声明向 Android 系统告知有关组件以及可以启动这些组件的条件的信息。
  • 确定托管应用组件的进程。
  • 声明应用必须具备哪些权限才能访问 API 中受保护的部分并与其他应用交互。还声明其他应用与该应用组件交互所需具备的权限
  • 列出 Instrumentation类,这些类可在应用运行时提供分析和其他信息。这些声明只会在应用处于开发阶段时出现在清单中,在应用发布之前将移除。
  • 声明应用所需的最低 Android API 级别
  • 列出应用必须链接到的库
8.4、详细介绍(挑选重要的进行说明):
  • 1、manifest

    • package:指定本应用内java主程序包的包名,它也是一个应用进程的默认名称。
    • sharedUserId:表明数据权限,因为默认情况下,Android给每个APK分配一个唯一的UserID,所以是默认禁止不同APK访问共享数据的。
    • versionCode:是给设备程序识别版本(升级)用的必须是一个interger值代表app更新过多少次。
    • versionName:版本号,这个名称是给用户看的。

    注:其中的versionNameversionCode,包括后面的uses-sdk都可以在build.gradle中进行配置。

  • 2、application

       <application
              android:allowClearUserData=["true" | "false"]    //用户是否能选择自行清除数据,默认为true,程序管理器包含一个选择允许用户清除数据。
              android:allowTaskReparenting=["true" | "false"] //是否允许activity更换从属的任务,比如从短信息任务切换到浏览器任务
              android:backupAgent="string" //这也是Android2.2中的一个新特性,设置该APP的备份,属性值应该是一个完整的类名,如com.project.TestCase,此属性并没有默认值,并且类名必须得指定(就是个备份工具,将数据备份到云端的操作)
              android:debuggable=["true" | "false"] //这个从字面上就可以看出是什么作用的,当设置为true时,表明该APP在手机上可以被调试。
              android:description="string resource" //此两个属性都是为许可提供的,均为字符串资源,当用户去看许可列表(android:label)或者某个许可的详细信息(android:description)时,这些字符串资源就可以显示给用户。
              android:enabled=["true" | "false"] //Android系统是否能够实例化该应用程序的组件,如果为true,每个组件的enabled属性决定那个组件是否可以被 enabled。如果为false,它覆盖组件指定的值;所有组件都是disabled。
              android:hasCode=["true" | "false"] //表示此APP是否包含任何的代码,默认为true,若为false,则系统在运行组件时,不会去尝试加载任何的APP代码
              android:icon="drawable resource" //声明整个APP的图标
              android:killAfterRestore=["true" | "false"] //是否复位需要重启
              android:label="string resource" //应用名称
              android:manageSpaceActivity="string" //让应用手动管理应用的数据目录
              android:name="string" //为应用程序所实现的Application子类的全名。
              android:permission="string" //设置许可名,这个属性若在上定义的话,是一个给应用程序的所有组件设置许可的便捷方式,当然它是被各组件设置的许可名所覆盖的
              android:persistent=["true" | "false"] //该应用程序是否应该在任何时候都保持运行状态,默认为false。因为应用程序通常不应该设置本标识,持续模式仅仅应该设置给某些系统应用程序才是有意义的。
              android:process="string" //应用程序运行的进程名,它的默认值为元素里设置的包名,当然每个组件都可以通过设置该属性来覆盖默认值。如果你想两个应用程序共用一个进程的话,你可以设置他们的android:process相同,但前提条件是他们共享一个用户ID及被赋予了相同证书的时候
              android:restoreAnyVersion=["true" | "false"] //同样也是android2.2的一个新特性,用来表明应用是否准备尝试恢复所有的备份,甚至该备份是比当前设备上更要新的版本,默认是false
              android:taskAffinity="string" //拥有相同的affinity的Activity理论上属于相同的Task,应用程序默认的affinity的名字是元素中设定的package名
              android:theme="resource or theme" >//应用程序的主题 
       </application> 
    

参考资料:Android关于AndroidManifest.xml详细分析

二、布局

1、五大布局:

1.1、五大布局包括:

RelativeLayout:相对布局
LinearLayout:线性布局
FrameLayout:框架布局
AbsoluteLayout:绝对布局
TableLayout:表格布局

1.2、详细说明:
  • 1、RelativeLayout:
    相对布局可以理解为某一个元素为参照物,来定位的布局方式。

  • 2、LinearLayout:
    线性布局,每一个LinearLayout里面又可分为:
    - 垂直布局(android:orientation="vertical"),元素由上向下垂直排列
    - 水平布局(android:orientation="horizontal" ),元素由左向右水平排列

  • 3、FrameLayout:
    所有东西依次都放在左上角,会重叠,这个布局比较简单,也只能放一点比较简单的东西。

  • 4、AbsoluteLayout:
    绝对布局用X,Y坐标来指定元素的位置,这种布局方式也比较简单,但是在屏幕旋转时,往往会出问题,而且多个元素的时候,计算比较麻烦。

  • 5、TableLayout:
    表格布局,每一个TableLayout里面有表格行TableRow,TableRow里面可以具体定义每一个元素。

2、优化布局方式:

2.1、建议:
  • 1、尽量减少布局文件的层级。
  • 2、删除布局中无用的控件和层级。
  • 3、选择更优的布局容器,减少View的测量、布局和绘制的工作量。
  • 4、为了最大限度的提高应用程序的速度和响应能力,布局包含的View个数不应该超过80个。
  • 使用相关工具分析布局:
    Hierarchy Viewew:显示布局树,在应用程序运行时分析布局。
    Lint 的代码扫描工具:可帮助发现并纠正代码结构质量的问题,而无需实际执行该应用,也不必编写测试用例。
    Layout Inspector:提供应用程序视图层次结构的直观表示。
2.2、优化方式:
  • 1、图文组合的情况,尽量减少使用ImageView + TextView的方式,而转而使用TextView + drawableXxx

  • 2、view控件(RelativeLayout中的子控件)尽量减少margin属性的使用,而用**padding** 来代替实现间隔。

由于当子View的Height不等于其实际高度(设置了margin),那么在measure中所做得优化(源码中有体现)将不起作用,这一过程将进一步影响RelativeLayout的绘制性能。

  • 3、根据UI界面合理选择使用容器RelativeLayoutLinearLayout

    • 在不考虑布局层级深度(即两种容器实现效果相同的情况)时,优先考虑使用LinearLayout(或FrameLayout)。
      从两者的源码可以看出,由于RelativeLayout中的view是通过兄弟view来确定位置的,所以在测量宽高的时候,要先对子view进行排序并进行横向测量,然后再进行最终的测量,因此进行了两次测量。

      而LinearLayout在测量的时候,先判断布局方向,确定后再对其中的子view进行一个方向的宽/高的叠加测量,如果不考虑使用weight 属性的情况下,LinearLayout只进行了一次测量;即便是用了weight 属性(源码中进行了lp.weight>0的判断)而测量了两次,在性能上也优于RelativeLayout。

    • 需要嵌套布局时,优先考虑使用RelativeLayout。
      这个很好理解了,层级嵌套越深,若使用LinearLayout,每一层都要绘制一次(用了weight就是2次),而RelativeLayout只在一层上进行绘制,性能上也会更优一些。

    参考链接:
    Android中RelativeLayout和LinearLayout性能分析

  • 4、include标签
    作用:
    使用<include> 可以实现布局复用,使得布局看起来更清晰分明。

    注意:
    如果在<include> 中使用了android:id 这个属性,则会覆盖通过layout="@layout/xxx"引入的xxx这个布局文件中根元素的id属性(如果有的话)。

  • 5、merge标签
    作用:
    减少布局视图的层级。
    一般和<include> 标签配合使用,作为<include> 引入的布局的根元素使用

  • 6、ViewStub
    作用:
    按需加载,需要时加载,不需要是并不占位

    说明:
    ViewStub继承自View,非常轻量级且宽/高皆为0,因此其本身并不参与任何的布局和绘制过程。
    当ViewStub通过setVisibilityinflate 方法加载后,ViewStub就会被它内部的布局替换掉,此时ViewStub就不再是整个布局结构中的一部分了。
    目前ViewStub并不支持<merge>标签。

三、控件

1、WebView

1.1、简介:
  • 1、WebView控件功能强大,除了具有一般View的属性和设置外,还可以对url请求、页面加载、渲染、页面交互进行强大的处理。
1.2、WebView的使用
  • 1、加载url:

      //方式1. 加载一个网页:
      webView.loadUrl("http://www.google.com/");
     
      //方式2:加载apk包中的html页面
      webView.loadUrl("file:///android_asset/test.html");
     
      //方式3:加载手机本地的html页面
      webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");
     
      // 方式4: 加载 HTML 页面的一小段内容
      /**
       *
       * @param data 需要截取展示的内容
       *             内容里不能出现 ’#’, ‘%’, ‘\’ , ‘?’ 这四个字符
       *             若出现了需用 %23, %25, %27, %3f 对应来替代,否则会出现异常
       * @param mimeType 展示内容的类型
       * @param encoding 编码
       */
      WebView.loadData(String data, String mimeType, String encoding)
    
  • 2、对比:


    Webview总结

参考链接:
Android:这是一份全面 & 详细的Webview使用攻略

1.3、缓存
  • 1、缓存机制:

    • 浏览器 缓存机制
      根据 HTTP 协议头里的 Cache-Control(或 Expires)和 Last-Modified(或 Etag)等字段来控制文件缓存的机制。
      可用于静态资源文件的存储,如JS、CSS、字体、图片等。
      Android WebView内置自动实现,即不需要设置即实现。

    • Application Cache 缓存机制
      以文件为单位进行缓存,且文件有一定更新机制(类似于浏览器缓存机制)
      可用于存储静态文件(如JS、CSS、字体文件)

         // 通过设置WebView的settings来实现
         WebSettings settings = getSettings();
         String cacheDirPath = context.getFilesDir().getAbsolutePath()+"cache/";
         // 1. 设置缓存路径      
         settings.setAppCachePath(cacheDirPath);
         // 2. 设置缓存大小
         settings.setAppCacheMaxSize(20*1024*1024);
         // 3. 开启Application Cache存储机制
         settings.setAppCacheEnabled(true);
      
         // 特别注意
         // 每个 Application 只调用一次 WebSettings.setAppCachePath() 和
         WebSettings.setAppCacheMaxSize()
      
    • Dom Storage 缓存机制
      通过存储字符串的 Key - Value 对来提供
      用于存储临时、简单的数据
      (Dom Storage 机制类似于 Android 的 SharedPreference机制)

         // 通过设置 `WebView`的`Settings`类实现
         WebSettings settings = getSettings();
         // 开启DOM storage
         settings.setDomStorageEnabled(true);
      
    • Web SQL Database 缓存机制
      基于 SQL 的数据库存储机制
      用于存储适合数据库的结构化数据

      // 通过设置WebView的settings实现
      WebSettings settings = getSettings();
      String cacheDirPath = context.getFilesDir().getAbsolutePath()+"cache/";
      // 设置缓存路径
      settings.setDatabasePath(cacheDirPath);
      // 开启 数据库存储机制
      settings.setDatabaseEnabled(true);
      

      说明:根据官方说明,Web SQL Database存储机制不再推荐使用(不再维护)

    • Indexed Database 缓存机制
      属于 NoSQL 数据库,通过存储字符串的 Key - Value 对来提供
      用于存储 复杂、数据量大的结构化数据

      // 通过设置WebView的settings实现
      WebSettings settings = getSettings();
      // 只需设置支持JS就自动打开IndexedDB存储机制
      settings.setJavaScriptEnabled(true); 
      // Android 在4.4开始加入对 IndexedDB 的支持,只需打开允许 JS 执行的开关就好了。
      
    • File System 缓存机制
      为 H5页面的数据 提供一个虚拟的文件系统
      通过文件系统 管理数据
      由于 File System是 H5 新加入的缓存机制,所以Android WebView暂时不支持

  • 2、缓存模式:
    缓存模式是一种 当加载 H5 网页时,该如何读取之前保存到本地缓存
    从而进行使用的方式。

       // 缓存模式说明: 
       // LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
       // LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
       // LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。
       // LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
    
    //设置缓存模式    getSetting().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK)
    
  • 3、资源预加载
    提早加载将需使用的H5页面,即 提前构建缓存

    • 预加载WebView对象
    • 预加载H5资源
      1、先将更新频率较低、常用 & 固定的H5静态资源 文件(如JS、CSS文件、图片等) 放到本地。
      2、重写WebViewClient 的 shouldInterceptRequest 方法,当向服务器访问这些静态资源时进行拦截,检测到是相同的资源则用本地资源代替


      预加载原理

参考链接:Android:手把手教你构建 全面的WebView 缓存机制 & 资源加载方案

1.4、安全问题:
  • 1、WebView 任意代码执行漏洞

    • addJavascriptInterface 接口引起远程代码执行漏洞的解决方法:
      对于Android 4.2以前,需要采用拦截prompt()的方式进行漏洞修复
      对于Android 4.2以后,则只需要对被调用的函数以 @JavascriptInterface进行注解
    • searchBoxJavaBridge_接口引起远程代码执行漏洞的解决方法:
      删除searchBoxJavaBridge_接口
    // 通过调用该方法删除接口
    removeJavascriptInterface();
    
  • 2、密码明文存储漏洞

    • 关闭密码保存提醒
    WebSettings.setSavePassword(false);
    
  • 3、域控制不严格漏洞

    • 对于不需要使用 file 协议的应用,禁用 file 协议
      // 禁用 file 协议;
      setAllowFileAccess(false); 
      setAllowFileAccessFromFileURLs(false);
      setAllowUniversalAccessFromFileURLs(false);
      
    • 对于需要使用 file 协议的应用,禁止 file 协议加载 JavaScript。
      // 需要使用 file 协议
      setAllowFileAccess(true); 
      setAllowFileAccessFromFileURLs(false);
      setAllowUniversalAccessFromFileURLs(false);
      
      // 禁止 file 协议加载 JavaScript
      if (url.startsWith("file://") {
          setJavaScriptEnabled(false);
      } else {
          setJavaScriptEnabled(true);
      }
      

参考链接:Android:你不知道的 WebView 使用漏洞

Android的WebView控件载入网页显示速度慢的究极解决方案

2、RecyclerView

2.1、简介:
  • 1、RecyclerView 是support-v7包中的新组件,是一个强大的可扩展的滑动组件。
  • 2、与经典的ListView相比,同样拥有item回收复用的功能,这一点从它的名字RecyclerView即回收view也可以看出。
2.2、与Listview的比较:
  • 1、高度解耦,自由定制。只负责回收、复用View。
  • 2、RecyclerView封装了ViewHolder的回收复用,省去了ListView中编写ViewHolder繁琐的代码。
  • 3、没有ListView中可直接调用的item单击、长按等相关监听方法。RecyclerView需要自己进行处理。
  • 4、设置布局管理器以控制Item的布局方式,横向、竖向以及瀑布流方式。
  • 5、针对item的显示异常灵活,可为其自由设置间隔样式,增删动画等等效果。
2.3、RecyclerView流程及原理:
  • 1、职责关系:

    • RecyclerView的任务仅限于回收和定位屏幕上的View。
    • ViewHolder子类和Adapter子类,负责列表项View显示数据。
    • ViewHolder只做一件事:容纳View视图。持有整个View视图。
         //自定义ViewHolder来获取itemView
         public static class ListVH extends RecyclerView.ViewHolder{
             public final TextView xxxTv;
             public VH(View itemView) {
                 super(itemView);
                 xxxTv = (TextView) itemView.findViewById(R.id.tv_xxx);
             }
         }
      
    • Adapter:
      创建必要的ViewHolder。
      绑定(填充数据)ViewHolder至模型层数据。
      Adapter是一个控制器对象,从模型层获取数据,并提供给RecyclerView显示,是沟通的桥梁。
  • 2、流程简述:


    RecyclerView-Adapter 会话
    • 首先,调用Adapter的getItemCount() 方法,RecyclerView询问数组列表中包含多少个对象
    • 接着,RecyclerView调用Adapter的onCreateHolder(ViewGroup, int)方法创建ViewHolder及其要显示的视图。
    • 最后,RecyclerView会传入ViewHolder及其位置,调用onBindViewHolder(ViewHolder, int)方法。Adapter会找到指定目标位置的数据并将其绑定到ViewHolder的视图上。所谓绑定,就是使用模型数据填充视图。

    说明:
    相对于onBindViewHolder方法,onCreateHolder方法的调用并不频繁。一旦有了够用的ViewHolder,RecycleView就会停止调用onCreateHolder方法。随后他会回收利用旧的ViewHolder以节约时间和内存。
    这是RecyclerView的缓存机制。

    说明:缓存机制中,实际缓存的是ViewHolder,ViewHolder中持有名为itemView的根元素。

  • 3、缓存机制:

    • 简述:
      要获取某个位置需要展示的View,先检查是否有可复用(缓存)的View,有则返回,没有则创建新View并返回。大致过程如下(这里从LinearLayoutManager中获取View开始):
      ---> 通过调用LinearLayoutManager中的next(RecyclerView.Recycler)方法,获取指定位置的View。
      --->RecyclerView.Recycler中获取指定位置的View:recycler.getViewForPosition(mCurrentPosition);
      ---> 接着调用的是RecyclerView.RecyclerView getViewForPosition方法。
      ---> 最后是通过调用RecyclerView.RecyclertryGetViewHolderForPositionByDeadline方法。此方法中是获取ViewHolder的缓存机制。

      0)、holder = getChangedScrapViewForPosition(position);
      这一步是查找有无发生变化的ViewHolder,通过从mChangedScrap列表中获取:

        // i 为循环中的变量
        final ViewHolder holder = mChangedScrap.get(i);
      

      通过两种方式:

         // find by position
         if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
             holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
             return holder;
         }
        // find by id
        if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
             holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
             return holder;
        }
      

      1)、holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
      这一步是从两个缓存集合(mAttachedScrapmCachedViews)中获取ViewHolder:

         //从mAttachedScrap中获取
         final ViewHolder holder = mAttachedScrap.get(i);
         //从mCachedViews中获取
         final ViewHolder holder = mCachedViews.get(i);
      

      2)、holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
      这一步同1),也是从两个缓存集合中获取:

         //从mAttachedScrap中获取
         final ViewHolder holder = mAttachedScrap.get(i);
         //从mCachedViews中获取
         final ViewHolder holder = mCachedViews.get(i);
      

      3)、final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
      这一步是开发者自己实现对View进行缓存(ViewCacheExtension)。

         //if (view != null)
         holder = getChildViewHolder(view);
      

      4)、holder = getRecycledViewPool().getRecycledView(type);
      这一步是通过从RecycledViewPool 中获取缓存的ViewHolder。

      5)、holder = mAdapter.createViewHolder(RecyclerView.this, type);
      如果上面的每一步都没有获得缓存的View,则最后会创建新的ViewHolder,并在后续的逻辑中进行绑定ViewHolder。

      其中的2、3、4、5称之为四级缓存

    • 示意图:


      RecyclerView.Recycler缓存机制示意图

相关链接:
基于场景解析RecyclerView的回收复用机制原理
Android面试系列文章2018之Android部分之RecyclerView篇
Android之ListView原理学习与优化总结
Android 控件 RecyclerView

相关文章

网友评论

    本文标题:<查漏补缺_Android基础系列>视图组件

    本文链接:https://www.haomeiwen.com/subject/tnqabqtx.html