Android 高工专业体系路线

**

Android UI 体系

**

Android坐标系
android中有两种坐标系,分别称之为Android坐标系和视图坐标系。Android坐标系以屏幕左上角为原点,向右为X轴正坐标。向下为Y轴正坐标。视图坐标系,它描述的是子视图在父视图中的位置,视图坐标系是以父视图的左上角为坐标原点的。相应的原点向右为x轴正方向,原点向下为y轴正方向。

view 的绘制流程
当performTraversals方法被调用的时候,在其内部会按照条件去做判断然后顺序调用performMeasure,performLayout,performDraw方法,分别进行测量,布局,绘制过程,在performMeasure方法中会调用其measure方法,在该方法内,首先会判断是否需要重新布局,然后判断是否需要从缓存中获取,如果缓存命中,则直接从缓存中获取布局,否则调用onmeasure方法,一个view的实际测量工作是在被本方法所调用的onMeasure方法中实现的,所以,只有onMeasure可以并且必须被子类重写,对于decorView来说,实际执行测量工作的是FrameLayout的onMeasure()方法,FrameLayout是ViewGroup的子类,后者有一个View[]类型的成员变量mChildren,代表了其子View集合。通过getChildAt(i)能获取指定索引处的子View,通过getChildCount()可以获得子View的总数,首先调用measureChildWithMargins()方法对所有子View进行了一遍测量,并计算出所有子View的最大宽度和最大高度。而后将得到的最大高度和宽度加上padding,这里的padding包括了父View的padding和前景区域的padding。然后会检查是否设置了最小宽高,并与其比较,将两者中较大的设为最终的最大宽高。最后,若设置了前景图像,我们还要检查前景图像的最小宽高。
    经过了以上一系列步骤后,我们就得到了maxHeight和maxWidth的最终值,表示当前容器View用这个尺寸就能够正常显示其所有子View(同时考虑了padding和margin)。而后我们需要调用resolveSizeAndState()方法来结合传来的MeasureSpec来获取最终的测量宽高,并保存到mMeasuredWidth与mMeasuredHeight成员变量中。,容器View通过measureChildWithMargins()方法对所有子View进行测量后,才能得到自身的测量结果。    也就是说,对于ViewGroup及其子类来说,要先完成子View的测量,再进行自身的测量。接下来我们来看下ViewGroup的measureChildWithMargins()方法,对于ViewGroup来说,它会调用child.measure()来完成子View的测量。传入ViewGroup的MeasureSpec是它的父View用于约束其测量的,那么ViewGroup本身也需要生成一个childMeasureSpec来限制它的子View的测量工作。这个childMeasureSpec就由getChildMeasureSpec()方法生成,然后根据父View的MeasureSpec和子View的LayoutParams生成子View的MeasureSpec,在measureChildWithMargins()方法中,获取了知道子View测量的MeasureSpec后,接下来就要调用child.measure()方法,并把获取到的childMeasureSpec传入。这时便又会调用onMeasure()方法,若此时的子View为ViewGroup的子类,便会调用相应容器类的onMeasure()方法,其他容器View的onMeasure()方法与FrameLayout的onMeasure()方法执行过程相似,对于普通View,会调用View类的onMeasure()方法来进行实际的测量工作,其内部通过setMeasuredDimension()方法设置测量的结果,具体来说是以getDefaultSize()方法的返回值来作为测量结果,其内部是通过自身的size 和MeasureSpec共同决定,View的getDefaultSize()方法对于AT_MOST和EXACTLY这两种情况都返回了SpecSize作为result。所以若我们的自定义View直接继承了View类,我们就要自己对wrap_content (对应了AT_MOST)这种情况进行处理,否则对自定义View指定wrap_content就和match_parent效果一样了。
layout阶段的基本思想也是由根View开始,递归地完成整个控件树的布局工作。decorView的layout()方法的调用作为布局整个控件树的起点,实际上调用的是View类的layout()方法,这个方法会调用setFrame()方法来设置View的4个参数,这四个参数描述了View相对其父View的位置,在setFrame()方法中会判断View的位置是否发生了改变,若发生了改变,则需要对子View进行重新布局,对子View的布局是通过onLayout()方法实现,由于普通View不含子View,所以View类的onLayout()方法为空。继续看ViewGroup类的onLayout()方法,实际上ViewGroup类的onLayout()方法是abstract,这是因为不同的布局管理器有着不同的布局方式,这里我们以decorView,也就是FrameLayout的onLayout()方法为例内部先确定子View的显示区域,接下来,用一个for循环来完成子View的布局,在确保子View的可见性不为GONE的情况下才会对其进行布局。
对于draw的过程会经过以下几个步骤,当view的背景不透明时首先会对背景进行绘制,然后调用ondraw方法绘制自身,然后调用dispatchDraw绘制子View,最后绘制滚动条等完成这个view 的绘制过程。

view 的事件分发
手指触摸屏幕时,产生了触摸信息。这个触摸信息由屏幕这个硬件产生,被系统底层驱动获取,交给Android的输入系统服务:InputManagerService,也就是IMS,IMS会对这个触摸信息进行处理,通过WMS找到要分发的window,随后发送给对应的viewRootImpl。android的view管理是以window为单位的,每个window对应一个view树。Window机制不仅管理着view的显示,也负责view的事件分发。每一棵view树都有一个根,叫做ViewRootImpl ,负责管理这整一棵view树的绘制、事件分发等。App会有多个view树,activity布局就是一个view树、应用的悬浮窗也是一个view树、dialog界面也是一个view树,android中view的绘制和事件分发,都是以view树为单位。每一棵view树,则为一个window。系统服务WindowManagerService,管理界面的显示就是以window为单位,也可以说是以view树为单位。view树是由viewRootImpl来负责管理的,wms管理的是viewRootImpl,wms是运行在系统服务进程的,负责管理所有应用的window。app 与wms的通信必须通过Binder进行跨进程通信。每个viewRootImpl在wms中都有一个windowState对应,wms可以通过windowState找到对应的viewRootImpl进行管理。事件分发并不是由Activity驱动的,而是由系统服务驱动viewRootImpl来进行分发,IMS从系统底层接收到事件之后,会从WMS中获取window信息,并将事件信息发送给对应的viewRootImpl,viewRootImpl接收到事件信息,封装成motionEvent对象后,发送给管理的view,顶层viewGroup一般是DecorView,DecorView会根据自身callBack的情况,选择调用callBack或者调用父类ViewGroup的方法,管顶层viewGroup的类型,最终都会到达ViewGroup对事件进行分发,Window.CallBack接口中包含了 dispatchTouchEvent 和 onTouchEvent 方法,Activity和Dialog都实现了Window.CallBack接口,因此都实现了该方法,dispatchTouchEvent,事件分发的核心方法,事件分发的逻辑都是在这个方法中实现;View、以及 ViewGroup、其他的实现类都重写了该方法,如果成功处理则返回true,处理失败则返回false,表示事件没有被处理,在view的相关类中,该方法的主要作用是消费触摸事件,在viewGroup相关类中,该方法的主要作用是把事件分发到该viewGroup所拥有的子view,如果子view没有处理则自己处理。onInterceptTouchEvent,该方法只存在于viewGroup中,当一个事件需要被分发到子view时,viewGroup会调用此方法检查是否要进行拦截。如果拦截则自己处理,而如果不拦截才会调用子view的 dispatchTouchEvent 方法分发事件,方法返回true表示拦截事件,返回false表示不拦截,viewGroup分发事件时,如果没有一个子view消费事件,那么会调用viewGroup自身的onTouchEvent方法来处理事件,View的dispatchTouchEvent方法中,先调用onTouchListener判断是否消费;如果onTouchListener没有消费事件,才会调用onTouchEvent来处理事件,一个触控点的事件序列只能给一个view消费,除非发生异常情况;onTouchListener的onTouch方法优先级比onTouchEvent高,会先触发。
假如onTouchListener的onTouch方法返回false,会接着触发onTouchEvent,反之onTouchEvent方法不会被调用。
内置诸如click事件的实现等等都基于onTouchEvent,假如onTouchListener的onTouch返回true,这些事件将不会被触发。

事件分发面试题
事件是先到DecorView还是先到Window
ViewRootImpl -> DecorView -> Activity -> PhoneWindow -> DecorView -> ViewGroup

点击事件被拦截,但是想传到下面的view,如何操作
在子View中调用getParent().requestDisallowInterceptTouchEvent(true)

滑动冲突的解决方案
1、外部拦截法
外部拦截法是指点击事件都事先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截,这种方法比较符合点击事件的分发机制,外部拦截伐需要重写父容器的onInterceptTouchEvent方法在内部做相应的拦截即可;
2、内部拦截法
内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器处理,这种方法和Android的事件分发机制不一致需要配合requestDisallowInterceptTouchEvent方法才能正常工作。

同时对父 View 和子 View 设置点击方法,优先响应哪个
优先响应子 view;如果先响应父 view,那么子 view 将永远无法响应;

requestDisallowInterceptTouchEvent的调用
一个手势的操作,会经历down,move,up等;
子view调用requestDisallowInterceptTouchEvent(true)是必须获取到点击事件,如果在down的时候调用了此方法,接下来的move,up都会传到子view上了,如果是在子view的move方法中调用的话,那么父view在move的过程中能将事件传递给子view就即可。

Android canvas
Canvas指画布,表现在屏幕上就是一块区域,可以在上面使用各种API绘制想要的东西。canvas内部维持了一个mutable Bitmap,所以它可以使用颜色值去填充整个Bitmap,此外canvas也可以使用画笔去填充整个Bitmap。canvas虽然内部保持了一个Bitmap,但是它本身并不代表那个Bitmap,而更像是一个图层。我们对这个图层的平移、旋转和缩放等操作并不影响内部的Bitmap,仅仅是改变了该图层相对于内部Bitmap的坐标位置、比例和方向而已。

自定义view
自定义view分为以下方式。自定义组合控件,多个控件组合成为一个新的控件,方便多处复用。继承系统View控件,继承自TextView等系统控件,在系统控件的基础功能上进行扩展。继承View,不复用系统控件逻辑,继承View进行功能定义。继承系统ViewGroup,继承自LinearLayout等系统控件,在系统控件的基础功能上进行扩展。继承ViewViewGroup,不复用系统控件逻辑,继承ViewGroup进行功能定义。

Android 文字绘制
文字绘制使用canvas.drawText(),文字绘制的坐标起点默认为文字左下角,横向右侧为正,纵向往上为正,坐标即为BaseLine文本基线,若绘制多行文字,可以使用TextPaint进行绘制,系统会根据文本内容自动折行

Android 动画
Android动画主要包含补间动画,帧动画,属性动画这几种,补间动画,补间动画通过对view的内容进行一系列的图形变换包括平移、缩放、旋转、改变透明度等来实现动画效果,动画效果的定义可以采用XML来做也可以采用编码来做。帧动画即顺序播放事先做好的图像。属性动画通过对目标对象进行赋值来修改其属性。

补间动画和属性动画的主要区别
作用对象不同,补间动画只能作用在view上,属性动画可以作用在所有对象上。属性变化不同,补间动画只是改变显示效果,不会改变view的属性,比如位置、宽高等,而属性动画实际改变对象的属性。动画效果不同,补间动画只能实现位移、缩放、旋转和透明度四种动画操作,而属性动画还能实现补间动画所有效果及其他更多动画效果。

补间动画原理
补间动画有4种类型,平移,旋转,透明度,缩放。补间动画不会改变View的属性,只会改变显示效果.补间动画原理就是在每一次VSYN到来时 在View的draw方法里面 根据当前时间计算动画进度 计算出一个需要变换的Transformation矩阵 然后最终设置到canvas上去 调用canvas concat做矩阵变换.

motionevent与多点触控
在进行屏幕触摸onTouch操作的过程中,参数中的view代表触摸的视图,MotionEvent参数存储的是触摸时的整个触摸动作:对屏幕进行了什么动作,还有屏幕上同时触摸点的个数和各个同时触摸点的索引和id。,MotionEvent中存入的是一个整形的值,在单点触控的时候,它有8位的有效二进制值,这8位的二进制值分别对应ACTION_DOWN、ACTION_MOVE等动作;而在多点触控的过程中,它会增加高8位的有效值,这八位值里边存储的则是每个手指对应的索引值,通过这个索引值我们可以获取每个手指的ID值来唯一识别每个触控点。获取到每个触控点的ID号和触控点的Action,就可以控制每个触控点的各个动作了.

android 中的手势
在android应用层上主要有两个层面的触摸事件监听,第一是Activity中重写父类中的onTouchEvent方法,第二个是重写View类中的GestDetector.OnGestureListener接口中定义的onTouch方法,第三个是利用GestureDetector.onTouchEvent在View.onTouch方法中来接管事件处理。

Android RecyclerView
它可以说是一个增强版的ListView,不仅可以轻松实现和ListView同样的效果, 还优化了ListView中存在的各种不足之处。 目前Android官方更加推荐使用RecyclerView,RecyclerView可以轻松实现横向布局,网格布局,流式布局等效果。相比 ListView,RecyclerView 将控件的测量和布局独立出来委托给 LayoutManager,Adapter 负责将数据转换成 itemView,LayoutManager 负责测量和摆放 ItemView,最终将 itemView 交给 RecyclerView 展示出来。LayoutManager 不仅负责的 itemView 的测量和布局,同时它也负责 itemView 的触摸反馈。RecyclerView 在 onMeasure() 时将测量过程委托给 LayoutManager,调用 mLayout.onMeasure(),RecyclerView 的 onLayout() 同样会在调用 dispatchLayoutStep2() 时将控件布局委托给 LayoutManager。RecyclerView 通过将测量和布局委托给 LayoutManager,这样就实现了测量和布局流程不再耦合于 RecyclerView,而是通过外部传入的 LayoutManager 处理,这样就提供给我们可自定义测量和布局的环境。RecyclerView 吸收了 ViewHolder 机制,在创建 RecyclerView.Adapter 时强制要求使用 ViewHolder,ViewHolder:作用就是减少不必要的findViewById, 然后将底下的控件引用存在ViewHolder里面。 再在View.setTag(holder)把它放在view 里面, 这样一来下次就可以直接取了在 RecyclerView 中,负责管理缓存的对象是 Recycler,和 ListView 不同的是,Recycler 有多个 ArrayList 变量,缓存的是 ViewHolder,在 Recycler 还可以看到 RecycledViewPool,它可以通过 viewType 的不同将 ViewHolder 存储在不同的 ArrayList

recycler view 运行机制
RecyclerView 要展示 itemView,会找 LayoutManager 要 itemView,但 LayoutManager 没有 itemView,它就会找 Recycler 要 itemView,刚开始时 Recycler 也没有 View,所以 Recycler 就会找 Adapter 获取,也就是我们创建一个 Adapter 时要实现的 onCreateViewHolder();Recycler 从 Adapter 获取 ViewHolder 也就能获取到 itemView, LayoutManager 对 View 进行测量后就能知道 RecyclerView 是否需要再显示更多的 itemView。RecyclerView 支持 itemView 动画的组件是 ItemAnimator。这个组件的职责是在 item 改变时能够比较简单的自动的做出正确动画,除了 ItemAnimator 组件外,RecyclerView 还提供了 ItemDecoration,通过它可以给每个 item 添加额外的样式,比如分割线、高亮等等。需要注意的是,ItemDecoration 并不仅仅只针对于 item,对于绘制的范围可以是整个 RecyclerView 的任意位置,比如在 RecyclerView 快速滑动时显示出滚动条,滚动条就是使用 ItemDecoration 绘制的。

recycler view 缓存机制
RecyclerView 中有一个内部类 Recycler,Recycler 中的 mCacheViews 对应的就是 ListView 中 mActiveViews,就是连绑定都不需要就可以直接使用的,但是它和 mActiveViews 的触发场景不同,前面提到 mActiveViews 触发的场景是在数据没有改变的时候触发了 onLayout() 才会发挥作用;而 mCacheViews 主要是优化滑动时候的性能,在滑动和回滚的时候能极大的提升复用的效率。mCacheViews 虽然是一个 ArrayList,但它默认情况下只能同时存放最多两个回收的 ViewHolder,这个主要是为了针对某一特殊场景设置更合适的容积,可以通过代码对容积进行修改。刚才提到 mCacheViews 主要是优化滑动时候的性能,当 ViewHolder 滑出屏幕的时候就会被存进 mCacheViews。在 mCacheViews 存放 ViewHolder 时是将 position 也存放的,而不是通过 position 找到 viewType 最后才拿到 ViewHolder。mCacheViews 不会根据 viewType 分类。随着滑动mCacheViews 中存放的ViewHolder会被挤出进入到mRecyclerPool,mRecyclerPool 对应的是 ListView 的 mScrapViews,在 RecycledViewPool 中缓存 ViewHolder 的容器是 SparseArray,相比 ListView 的 mScrapView 使用的是 ArrayList[] 数组,只能通过索引找值而 RecycledViewPool 使用 SparseArray 就没有太多限制了,只要是 int 值即可。RecycledViewPool 默认可以存放5个ViewHolder,RecycledViewPool 还可以让多个 RecyclerView 使用同一个 RecycledViewPool,当随着列表滑动mCacheViews 已经拿不到对应 position 的 ViewHolder,会从 mRecyclerPool 获取到符合的ViewHolder然后会调用 onBindViewHolder() 重新绑定。 mCacheViews 只缓存不需要绑定数据的 ViewHolder,而且默认只能缓存两个 ViewHolder,所以数据更新时不会存放到 mCacheViews。调用 notifyDataSetChanged() 更新大量数据时,会直接跳过 mCacheViews,然后存放到 mRecyclerPool。几句话简单总结下 RecyclerView 相比 ListView 上的改进,支持多个 RecyclerView 共用同一个回收池,可以单独的根据 viewType 设置容量,针对性进行优化,viewType 的值不需要连续的,因为数据结构不是数组。

recycler view 四级缓存
Scrap是RecyclerView中最轻量的缓存,它不参与滑动时的回收复用,只是作为重新布局时的一种临时缓存,缓存(保存)动作只发生在重新布局时,布局完成后就要清空缓存。它的目的是,缓存当界面重新布局(不包括初始化第一次)的前后都出现在屏幕上的ViewHolder,这样就省去了不必要的CreateView和bindView的工作,CacheView是在RecyclerView列表位置产生变动的时候,对刚刚移出屏幕的View进行回收复用的缓存列表,mCacheViews的缓存动作发生在滑动时,当有Item滑出屏幕外,就会原封不动的保存到mCacheViews中,复用动作发生在滑动回来的时候,场景是当上下小距离滑动时,刚划出去的Item又划回来,不用再重新创建和重新绑定数据。mViewCacheExtension这是Google工程师预留给程序员的,可以做自己的缓存逻辑,RecycledViewPool是最后一层缓存:RecycledViewPool保存的是以ViewHolder的viewType为区分的多个列表。总的来看,RecyclerView着重在两个场景使用缓存与回收复用进行了性能上的优化。一是,在数据更新时,利用Scrap实现局部更新,尽可能地减少没有被更改的View进行无用地重新创建与绑定工作。二是,在快速滑动的时候,重复利用已经滑过的ViewHolder对象,以尽可能减少重新创建ViewHolder对象时带来的压力。

Android 常用的几种布局
FrameLayout(帧布局)是最简单的布局方式,放置的控件都只能罗列到左上角,控件会有重叠,不能进行复杂的布局。LinearLayout(线性布局)可以通过orientation属性设置线性排列的方向是垂直还是纵向的,每行或每列只有一个元素,可以进行复杂的布局。AbsoluteLayout(绝对布局)可以让子元素指定准确的x、y坐标值,并显示在屏幕上。Absolute Layout没有页边框,允许元素之间相互重叠。它是绝对坐标,所以在实际中不提倡使用。RelativeLayout(相对布局)允许子元素制定他们相对于其他元素或父元素的位置,TableLayout(表格布局)将以子元素的位置分配到行或列,一个TableLayout由许多的TableRow组成。

Android webview

什么是webview
WebView在是一个特殊的View, 它能用来显示网页,这个类可以被用来在app中仅仅显示一张在线的网页,还可以用来开发浏览器。WebView内部实现是采用chromium 渲染引擎来渲染View的内容。WebView有两种实现方法,第一种方法是在要Activity中实例化WebView组件,然后调用WebView中的loadUrl()方法,设置WebView要显示的网页,最后调用Activity的setContentView()方法显示网页视图。使WebView多次打开请求界面时,具有back功能,需覆盖Activity的onKeyDown方法,如果不做任何处理,那么一使用手机back功能,那么浏览器则会调用finish,从而结束自身,并不会进行界面的回退!第二种方式是在布局文件中声明WebView,然后在Activity中实例化WebView。

webview原理
Android WebView的UI来自于网页,是通过Chromium渲染的;Android WebView是一个android系统组件,用来展示web内容,最开始的时候,WebView是android framework的一部分,从Android 5.0 开始,WebView的实现由一个单独的apk来提供,apk 预置在设备里面,可以和普通应用一样更新。

android webview与js交互
原生提供的交互方案和方法都存在不同的缺点,建议之间通过第三方框架来完成交互动作。

webview崩溃问题处理
在 Android 9.0 系统上如果引入多个进程使用 WebView 需要使用官方提供的 api 在子进程中给 WebView 的数据文件夹设置后缀。否则会出现异常,通过阅读源码发现,在 Android P 及更高版本检测应用是否存在多进程公用 WebView 数据目录的原理就是进程持有 WebView 数据目录中的 webview_ data.lock 文件的锁。所以如果子进程也尝试对 webvie w_data.loc 文件加锁则会导致应用崩溃。解决方案是既然获取文件锁失败就会发生崩溃,并且该文件只是用于加锁判断是否存在多进程共用 WebView 数据目录,每次加锁成功都会重新写入对应进程信息,那么我们可以在应用启动时对该文件尝试加锁,如果加锁失败就删除该文件并重新创建,加锁成功就立即释放锁,这样当系统尝试加锁时理论上是可以加锁成功的,也就避免了这个问题的发生。

App 升级支持 64 位系统后,部分国产手机升级覆盖安装后,进入 WebView 的页面发生 Crash,问题原因是Android 8.0 版本的 WebView 在读取 WebView 缓存时可能出现内存溢出,解决方案是我们采用暴力删除的方式,App 版本升级后,首次启动时删除/data/data/包名/所有包含 webview 的缓存目录。

App 覆盖升级安装后在部分手机上进入 WebView 页面直接崩溃的现象,而且是必现的,非首次安装不会出现该问题。出现原因是系统在覆盖安装或升级新版本的时候如果老版本和新版本存在相同库文件并不会重新加载进系统导致新版本安装之后用的还是老版本加载的库文件,然而新版本与老版本的缓存文件之间没有必要的关联,从而导致找不到方法名而报错。解决方案是在新版本进入应用初始化的时候对应用缓存进行一次清理。

webview性能优化
webview预初始化,为了减少WebView的性能损耗,我们可以在合适时机提前创建好WebView,并存入缓存池,当页面需要显示内容时,直接从缓存池获取创建好的WebView,根据性能数据显示,WebView预创建可以减少首屏渲染时间200ms+。若想实现预创建的逻辑,需要保证context的一致性,常规做法我们考虑可以用fragment来实现承载H5页面的容器,这样context可以用外层的activity实例,但Fragment本身的切换流畅度存在一定问题,并且这样做限定了WebView预创建适用的场景。为此,我们找到了一种更加完美的替代方案,即:MutableContextWrapper,它允许外部修改它的baseContext,并且所有ContextWrapper调用的方法都会代理到baseContext来执行。
关联的Native组件懒加载,WebView初始化完成,可以立刻loadUrl,无需等待框架onCreate或者OnResume结束。另外WebView初始完成后到页面首页绘制完成之间,尽量减少UI线程的其他操作,繁忙的UI线程会拖慢WebView.loadUrl的速度。
离线缓存,我们通过setCacheMode方法来设置WebView的缓存策略,WebSettings.LOAD_DEFAULT是默认的缓存策略,它在缓存可获取并且没有过期的情况下加载缓存,否则通过网络获取资源。这样的话可以减少页面的网络请求次数,那我们如何在离线的情况下也能打开页面呢,这里我们在加载页面的时候可以通过判断网络状态,在无网络的情况下更改webview的缓存策略。
资源预加载,可以把html,css,js,image等资源预置在客户端本地,并和服务端协商好前端的版本控制和增量更新策略,如此一来Webview就可以先快速加载本地缓存页面资源,加载完成后,返回给WebView。剩下的就只需要拉取那些需要更新的增量资源即可。这样可以提升加载速度也能减少服务器压力。针对这些需要拉取的增量资源,可以对它们进行webpack+gzip数据压缩和CDN加速处理,以提升拉取速度。并且在建立网络连接时,可以让前端请求的域名和客户端API接口域名一致,以减少DNS解析时间。对于H5页面来说,图片资源的拉取是最为耗时的,一个比较好的解决方案就是先加载并展示非图片内容,延迟这些图片的加载,以提升用户体验。
内存泄漏,针对WebView内存泄漏的问题,可用AS的Profiler来进行检测,WebView内存泄漏其实只是早期内核版本的一个漏洞,现早已修复。所以包括5.0以上的大部分设备不会产生所谓的内存泄漏,如果发生了内存泄漏,可以避免在xml布局文件中直接嵌套webview控件,而是采用addview的方式new一个webview并加载到布局中,上下文变量使用applicationContext,然后可以当activity生命周期结束时及时销毁/释放资源,解决WebView的内存泄漏及OOM的另一种解决方案是通过开辟独立进程来实现。

WebView独立进程的实现
WebView独立进程的实现比较简单,只需要在AndroidManifest中找到对应的WebViewActivity,对其配置"android: process"属性即可。有两个进程就必然会涉及到进程间的通信,进程之间通信通过aidl完成,web通信通过原生提供的通信方法来完成。

Android插件化换肤

activity如何与视图绑定关联,以及原理
当我们通过LayoutInflater 加载布局的时候其内部最终都是通过Context.getSystemService()来获得的LayoutInflater对象,把xml文件转换为VIEW则是在inflate方法中实现,在该方法里通过Resources的getLayout方法获得一个XmlResourceParser对象,用该对象作为参数调用另一个inflate()方法,XmlResourceParser可以理解成Xml文件的解释器,是一个接口,定义了一系列的解析xml文件的API,Resources的getLayout()方法调用了loadXmlResourceParser()方法,在该方法内获得了一个ResourcesImpl对象impl,然后调用impl.loadXmlResourceParser()来获取一个XmlResourceParser,在获得XmlResourceParser,后会继续将其作为参数回调inflate重载方法,在该方法内,会通过XmlResourceParser对象去获取布局的根节点并递归生成其子节点,通过createViewFromTag生成根节点View ,然后调用父View即root的generateLayoutParams()方法生成根节点View的LayoutParams,如果不需要将根节点View添加到父View中,则对根节点View自身设置LayoutParams,传入的root即父View为null时,xml文件中以“layout_”开头的属性都会消失,因为没有父View root生成其对应的LayoutParams,如果出入的root为空,根节点定义的宽和高将会失效,因为当一个根节点没有设置LayoutParams的时候,默认的LayoutParams将会是wrap_content。rInflateChidlren是递归生成子节点的方法,其内部会先获取view树的高度,然后对改view树进行遍历,具体逻辑会根据传入的XmlResourceParser对象获取view的相关信息,以及对include节点进行判断处理,然后调用createViewFromTag()创建节点View以及调用generateLayoutParams()创建节点View的LayoutParams,进入createViewFromTag看下view是如何被创建的,在该方法内首先会对Factory2和Factory做非空判断,Factory2和Factory是两个接口,如果我们自己在代码中设置了mFactory2或者mFactory,在createViewFromTag()方法中创建View的时候就会调用mFactory2或者mFactory的onCrateView()方法,通过我们自己实现的onCreateView()来创建View,在侵入式的插件换肤框架的设计上,就利用了mFactory2这个对象, 实现了onCrea

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值