本文主要总结的内容如下:
- 一、组件
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.callback
和keyevent.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
]从执行的顺序可以看出,要先执行完
AActivity
的onPause()
方法,才能开启下一个界面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_COMPATIBILITY
:START_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的Action
、Data
、Category
来匹配指定组件的IntentFilter,进行启动。
6.3、Action(动作):
-
1、配置方式:
- 在AndroidManifest.xml中配置
<action>
标签 - 在代码中设置:
//直接传入Intent中 Intent intent = new Intent(Intent.ACTION_XXX); //设置 setAction(Intent.ACTION_XXX);
- 在AndroidManifest.xml中配置
-
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...);
- 在AndroidManifest.xml中配置
- 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);
- 在AndroidManifest.xml中配置
- 2、结构参数说明:
data由两部分组成:mimeType
和URI
。-
mimeType
:只媒体类型,如image/jpeg
、audio/mpeg4-generic
,video/*
等 - URI:
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
;
例如:content://com.example.project:200/folder/subfolder/ect http://www.baidu.com:80/search/info
-
Scheme
:URI的模式,比如http
,file
,content
等 -
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指定的
scheme
、host/authority
、path
或MIME类型
都要和Intent的URI一一匹配才算成功。 - 若没有指定数据值的Intent Filter将和所有的Intent数据值匹配。
- Intent Filter指定的
- 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、告知FragmentManager
,fragment
视图在activity视图中的位置。
2、唯一标识FragmentManager
队列中的fragment,通过此ID
可以识别并找到指定的fragment
。
3、这是FragmentManager
的一种内部实现机制。
4、若向activity
中添加多个fragment
通常需要分别为每个fragment
创建具有不同ID的不同容器。
-
-
FragmentManager
与fragment
生命周期:
activity
中的FragmentManager
负责调用队列fragment
的生命周期方法:- 当添加
fragment
供FragmentManager
管理时,会调用如下方法:
onAttach(Context)
、onCreate(Bundle)
、onCreateView(...)
activity
的onCreate(Bundle)
方法执行后,onActivityCreated(Bundle)
也会被调用。 -
activity
在运行状态时,添加fragment
,fragment
调用的生命周期方法:
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的事务。通过
FragmentManager
的popBackStack();
将当前的事务退出回退栈。 -
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#mName
和findFragmentByTag(tag)
中的FragmentManager#mTag
没有关系。
7.5、Fragment通信
-
1、
Fragment
和Activity
之间的通信方式:-
Activity
-->
Fragment
:Activity实例方法
BaseActivity baseAct = (BaseActivity) getActivity(); baseAct.dispatchFragment(tag);
个人想法:将方法定义到模板类
BaseActivity
中,并定义一个tag
队列(存储fragment
标识tag
的集合),在fragment中调用这个方法时,传入此fragment
的tag
,以此来区分一个activity
实例中不同的fragment
。-
Fragment
-->
Activity
:接口通信
即在Fragment
中定义一个接口,在Activity
中创建fragment
的时候,实现此接口,通过回调来接收fragment
中的数据。
-
-
2、
Fragment
与Fragment
之间的通信方式:- 通过1中的两种方式结合,
AFragment
先和Activity
通信,然后通过Activity
进行转发消息到BFragment
,实现两个Fragment
的通信。 - 另一种就是通过
路由
转发的方法,创建一个路由类,其中通过handler来进行消息转发和处理,并提供对外接口,在AFragment
和BFragment
中分别实现路由类的接口,以此来建立两者的连接。
- 通过1中的两种方式结合,
-
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
:版本号,这个名称是给用户看的。
注:其中的
versionName
和versionCode
,包括后面的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界面合理选择使用容器
RelativeLayout
和LinearLayout
:-
在不考虑布局层级深度(即两种容器实现效果相同的情况)时,优先考虑使用
LinearLayout
(或FrameLayout)。
从两者的源码可以看出,由于RelativeLayout中的view是通过兄弟view来确定位置的,所以在测量宽高的时候,要先对子view进行排序并进行横向测量,然后再进行最终的测量,因此进行了两次测量。而LinearLayout在测量的时候,先判断布局方向,确定后再对其中的子view进行一个方向的宽/高的叠加测量,如果不考虑使用
weight
属性的情况下,LinearLayout只进行了一次测量;即便是用了weight
属性(源码中进行了lp.weight>0
的判断)而测量了两次,在性能上也优于RelativeLayout。 -
需要嵌套布局时,优先考虑使用RelativeLayout。
这个很好理解了,层级嵌套越深,若使用LinearLayout,每一层都要绘制一次(用了weight就是2次),而RelativeLayout只在一层上进行绘制,性能上也会更优一些。
-
-
4、include标签:
作用:
使用<include>
可以实现布局复用,使得布局看起来更清晰分明。注意:
如果在<include>
中使用了android:id
这个属性,则会覆盖通过layout="@layout/xxx"
引入的xxx这个布局文件中根元素的id属性(如果有的话)。 -
5、merge标签:
作用:
减少布局视图的层级。
一般和<include>
标签配合使用,作为<include>
引入的布局的根元素使用 -
6、ViewStub:
作用:
按需加载,需要时加载,不需要是并不占位说明:
ViewStub继承自View,非常轻量级且宽/高皆为0,因此其本身并不参与任何的布局和绘制过程。
当ViewStub通过setVisibility
或inflate
方法加载后,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();
- addJavascriptInterface 接口引起远程代码执行漏洞的解决方法:
-
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); }
- 对于不需要使用 file 协议的应用,禁用 file 协议
参考链接: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
的根元素。 - 首先,调用Adapter的
-
3、缓存机制:
-
简述:
要获取某个位置需要展示的View,先检查是否有可复用(缓存)的View,有则返回,没有则创建新View并返回。大致过程如下(这里从LinearLayoutManager
中获取View开始):
---> 通过调用LinearLayoutManager
中的next(RecyclerView.Recycler)
方法,获取指定位置的View。
---> 从RecyclerView.Recycler
中获取指定位置的View:recycler.getViewForPosition(mCurrentPosition);
---> 接着调用的是RecyclerView.Recycler
的View getViewForPosition
方法。
---> 最后是通过调用RecyclerView.Recycler
的tryGetViewHolderForPositionByDeadline
方法。此方法中是获取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);
这一步是从两个缓存集合(mAttachedScrap
和mCachedViews
)中获取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
网友评论