Android中数据存储方式和缓存
1- 请介绍下Android中的数据存储方式(8分)
1 使用SharedPreferences存储数据
适用范围:保存少量的数据,且这些数据的格式非常简单:字符串型、基本类型的值。
比如应用程序的各种配置信息(如是否打开音效等),解锁口 令密码等
核心原理:保存基于XML文件存储的key-value键值对数据,通常用来存储一些简单的配置信息。
SharedPreferences对象本身只能获取数据而不支持存储和修改,存储修改是通过SharedPreferences.edit()获取的内部接口Editor对象实现
实现SharedPreferences存储的步骤如下:
一、根据Context获取SharedPreferences对象
二、利用edit()方法获取Editor对象。
三、通过Editor对象存储key-value键值对数据。
四、通过commit()方法提交数据。
Editor有如下主要重要方法:
SharedPreferences.Editor clear():清空SharedPreferences里所有数据
SharedPreferences.Editor putXxx(String key , xxx value): 向SharedPreferences存入指定key对应的数据,其中xxx 可以是boolean,float,int等各种基本类型据
SharedPreferences.Editor remove(): 删除SharedPreferences中指定key对应的数据项
boolean commit(): 当Editor编辑完成后,使用该方法提交修改
2 文件存储数据
核心原理: Context提供了两个方法来打开数据文件里的文件IO流:
FileInputStream openFileInput(String name);
FileOutputStream openFileOutput(String name , int mode)
注意:创建的文件保存在/data/data/<package name>/files目录,如: /data/data/cn.tony.app/files/message.txt
3 SQLite数据库存储数据
什么是SQLite???
SQLite是轻量级嵌入式数据库引擎,它支持 SQL 语言,并且只利用很少的内存就有很好的性能。
SQLite基本使用!!!
实际开发中,为了能够更好的管理和维护数据库,我们会封装一个继承自SQLiteOpenHelper类的数据库操作类,然后以这个类为基础,再封装我们的业务逻辑方法
针对SQLiteOpenHelper进行阐述:
SQLiteOpenHelper是SQLiteDatabase的一个帮助类,用来管理数据库的创建和版本的更新。一般是建立一个类继承它,并实现它的onCreate和onUpgrade方法。
通过SQLiteDatabase对象操作数据库,进行curd
实例化sqliteDBHelper并获取一个SQLiteDatabase对象,作为整个应用的数据库实例;
在增删改信息时,我们采用了事务处理,确保数据完整性;最后要注意释放数据库资源db.close(),这一个步骤在我们整个应用关闭时执行
查询:
通过SQL语句进行查询
插入操作:
更新操作:
删除操作:
通过SQL语句执行curd操作:
注意:getWritableDatabase()和getReadableDatabase()方法区别
Android使用getWritableDatabase()和getReadableDatabase()方法都可以获取一个用于操作数据库的SQLiteDatabase实例。
(getReadableDatabase()方法中会调用getWritableDatabase()方法) 其中getWritableDatabase() 方法以读写方式打开数据库,一旦数据库的磁盘空间满了,数据库就只能读而不能写,倘若使用的是getWritableDatabase() 方法就会出错。
方法则是先以读写方式打开数据库,如果数据库的磁盘空间满了,就会打开失败,当打开失败后会继续尝试以只读方式打开数据库。如果该问题成功解决,则只读数据库对象就会关闭,然后返回一个可读写的数据库对象。
4 使用ContentProvider存储数据
什么是ContentProvider????
ContentProvider:为存储和获取数据提供统一的接口。可以在不同的应用程序之间共享数据。
并且此种方式忽略了底层的数据存储实现,ContentProvider提供了一种统一的通过Uri实现数据操作的方式
其步骤为:
1. 在当前应用程序中定义一个ContentProvider。
2. 在当前应用程序的AndroidManifest.xml中注册此ContentProvider
3. 其他应用程序通过ContentResolver和Uri来获取此ContentProvider的数据。
ContentResolver提供了insert(), delete(), query()和update()之类的方法。用于实现对ContentProvider中数据的存取操作
5 网络存储数据
Android提供了通过网络来实现数据的存储和获取的方法
如下代码片段:
2- 在项目中,你是如何缓存数据的?(10分)
数据的缓存从两方面来阐述:
1. 对网络数据
我们采取了两级缓存方案,来提高性能和用户体验的
一级: 网络缓存
请求消息或响应消息中设置Cache-Control来判断是否读取缓存
Cache-Control:
Cache-Control指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置Cache-Control并不会修改另一个消息处理过程中的缓存处理过程。请求时的缓存指令有下几种:
Public指示响应可被任何缓存区缓存。
Private指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效。
no-cache指示请求或响应消息不能缓存
no-store用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。
max-age指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应。
min-fresh指示客户机可以接收响应时间小于当前时间加上指定时间的响应。
max-stale指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。
Cache-Control类的使用:
请求时如何使用??
二级: 磁盘缓存
使用了DiskLruCache进行本地缓存的
DiskLruCache介绍:
Google提供了一套硬盘缓存的解决方案:DiskLruCache(非Google官方编写,但获得官方认证)
DiskLruCache并没有限制数据的缓存位置,可以自由地进行设定,但是通常情况下多数应用程序都会将缓存的位置选择为 /sdcard/Android/data/<application package>/cache 这个路径
选择在这个位置有两点好处:
第一, 这是存储在SD卡上的,因此即使缓存再多的数据也不会对手机的内置存储空间有任何影响,只要SD卡空间足够就行。
第二,这个路径被Android系统认定为应用程序的缓存路径,当程序被卸载的时候,这里的数据也会一起被清除掉,这样就不会出现删除程序之后手机上还有很多残留数据的问题
缓存原理:
打开缓存:
创建一个DiskLruCache的实例,则需要调用它的open()方法
写入缓存:
DiskLruCache的实例之后,我们就可以对缓存的数据进行操作了,操作类型主要包括写入、访问、移除等
写入的操作是借助DiskLruCache.Editor这个类完成的,通过edit()方法获取Editor,同时,edit()方法接收一个key,这个key将会成为缓存文件的文件名,通常我们对url进行md5之后,作为key传递editor。
有了DiskLruCache.Editor的实例之后,我们可以调用它的newOutputStream()方法来创建一个输出流,然后把它传入到downloadUrlToStream()中就能实现下载并写入缓存的功能了
注意:在写入操作执行完之后,我们还需要调用一下commit()方法进行提交才能使写入生效,调用abort()方法的话则表示放弃此次写入
读缓存:
主要是借助DiskLruCache的get()方法,获取一个DiskLruCache.Snapshot对象,通过该对象,调用它的getInputStream()方法就可以得到缓存文件的输入流了,然后把
有了文件的输入流之后,将文件输入流转换为文件或者字符串。就获取缓存数据
2. 对图片数据
网络缓存:
通过Cache-Control的设置进行网络缓存
内存缓存:
内存缓存我们采用了LruCache
LruCache是android提供的一个缓存工具类,其算法是最近最少使用算法。
它把最近使用的对象用“强引用”存储在LinkedHashMap中,并且把最近最少使用的对象在缓存值达到预设定值之前就从内存中移除
LruCache:底层实现原理:
LruCache中Lru算法的实现就是通过LinkedHashMap来实现的。LinkedHashMap继承于HashMap,它使用了一个双向链表来存储Map中的Entry顺序关系,
对于get、put、remove等操作,LinkedHashMap除了要做HashMap做的事情,还做些调整Entry顺序链表的工作。
LruCache中将LinkedHashMap的顺序设置为LRU顺序来实现LRU缓存,每次调用get(也就是从内存缓存中取图片),则将该对象移到链表的尾端。
调用put插入新的对象也是存储在链表尾端,这样当内存缓存达到设定的最大值时,将链表头部的对象(近期最少用到的)移除。
磁盘缓存
使用的是DiskLruCache
3- 说说LruCache底层原理(10分)
LruCache中Lru算法的实现就是通过LinkedHashMap来实现的。LinkedHashMap继承于HashMap,它使用了一个双向链表来存储Map中的Entry顺序关系,
对于get、put、remove等操作,LinkedHashMap除了要做HashMap做的事情,还做些调整Entry顺序链表的工作。
LruCache中将LinkedHashMap的顺序设置为LRU顺序来实现LRU缓存,每次调用get(也就是从内存缓存中取图片),则将该对象移到链表的尾端。
调用put插入新的对象也是存储在链表尾端,这样当内存缓存达到设定的最大值时,将链表头部的对象(近期最少用到的)移除。
查看实例实例代码:C:\AndroidStudioProject\Questions-Explain\day02\lrucachetest
4- 加载图片的三级缓存原理(10分)
原理:
当Android端需要获得数据时比如获取网络中的图片,我们首先从内存中查找(按键查找),内存中没有的再从磁盘文件或sqlite中去查找,若磁盘中也没有才通过网络获取
参考图示:
网络缓存:
通过Cache-Control的设置进行网络缓存
内存缓存:
内存缓存我们采用了LruCache
LruCache是android提供的一个缓存工具类,其算法是最近最少使用算法。
它把最近使用的对象用“强引用”存储在LinkedHashMap中,并且把最近最少使用的对象在缓存值达到预设定值之前就从内存中移除
LruCache:底层实现原理:
LruCache中Lru算法的实现就是通过LinkedHashMap来实现的。LinkedHashMap继承于HashMap,它使用了一个双向链表来存储Map中的Entry顺序关系,
对于get、put、remove等操作,LinkedHashMap除了要做HashMap做的事情,还做些调整Entry顺序链表的工作。
LruCache中将LinkedHashMap的顺序设置为LRU顺序来实现LRU缓存,每次调用get(也就是从内存缓存中取图片),则将该对象移到链表的尾端。
调用put插入新的对象也是存储在链表尾端,这样当内存缓存达到设定的最大值时,将链表头部的对象(近期最少用到的)移除。
磁盘缓存
使用的是DiskLruCache
动画相关面试题
5- android中的动画有哪些,它们的特点和区别是什么?(20分)
Android提供了几种动画类型:
View Animation :视图动画,其实就是补间动画。可以使视图组件移动、放大、缩小以及产生透明度的变化
相关的类:
AlphaAnimation,
RotateAnimation,
ScaleAnimation,
TranslateAnimation
分别对应透明度,旋转,大小,位移四种变化
Drawable Animation :资源动画,其实就是帧动画, 通过顺序的播放排列好的图片来实现,类似电影
相关的类:
AnimationDrawable
动画配置
播放动画
Property Animation :属性动画,通过动画的方式改变对象的属性。
相关类:
ObjectAnimator
是属性动画框架中最重要的实现类。
创建一个ObjectAnimator只需要通过它的静态工厂类直接返回一个ObjectAnimator对象。
参数包括一个对象和对象的属性名字,但这个属性必须有get和set函数,内部会通过Java反射机制来调用set函数修改对象的属性值。
ValueAnimator
是整个属性动画中最核心的一个类,前面介绍的ObjectAnimator也是继承自ValueAnimator。
我们知道属性动画的实现机制是通过不断的地对View属性进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类负责计算的。
它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮助我们完成从初始值平滑过渡到结束值这样的效果。
ValueAnimator本身不提供任何动画效果,它更像一个数值发生器,用来产生具有一定规律的数字,从而让调用者来控制动画的实现过程AnimatorSet 用于控制一组动画的执行:线性,一起,每个动画的先后执行等。
区别:
视图动画只提供为视图类设置动画的能力,所以如果你想为非视图的类设置动画,你需要自己实现代码来完成这个功能。
视图动画也只能设置视图类的一部分属性设置动画,例如缩放和旋转视图,但不能为视图的背景设置动画。
视图动画只能为当前绘制的视图设置动画,但并没有改变视图本身的属性,例如,如果你为一个按钮设置动画,使按钮在屏幕上移动,按钮在屏幕上按照预期的期望绘制在指定的位置,但是实际你点击按钮能触发按钮事件的位置还是在原处。
使用属性动画,上面提到的这些视图动画的缺点都将不存在。
你可以为任何对象设置动画,包括视图类和非视图类,并且修改的就是视图本身的属性。
属性动画在实现动画的策略方面也是更健壮的。在一个更高的水平上,可以为动画分配你想设置动画的属性,例如颜色,位置,大小并且可以为动画设置插值器的行为,你也可以同时执行多个动画。
6- 动画插值器是什么(20分)
插值器:就是一个函数,将时间t经过一个函数的变换映射到t’,从而影响动画的运动轨迹和速度变化
Android系统提供的默认插值器:
AccelerateDecelerateInterpolator:
动画开始与结束的地方速率改变比较慢,在中间的时候加速。
AccelerateInterpolator:
动画开始的地方速率改变比较慢,然后开始加速。
AnticipateInterpolator:
开始的时候向后然后向前甩。
AnticipateOvershootInterpolator:
开始的时候向后然后向前甩一定值后返回最后的值。
BounceInterpolator:
动画结束的时候弹起。
CycleInterpolator:
动画循环播放特定的次数,速率改变沿着正弦曲线。
DecelerateInterpolator:
在动画开始的地方快然后慢。
LinearInterpolator:
以常量速率改变。
OvershootInterpolator:
向前甩一定值后再回到原来位置。
如何实现一个插值器:
使用:
请查看案例:C:\AndroidStudioProject\Questions-Explain\day02\animationinterpolator
7- 请说一下对属性动画的理解(20分)
Android 3.0版本开始,系统给我们提供了一种全新的动画模式,属性动画(property animation),它的功能非常强大,
弥补了之前视图动画的一些缺陷,几乎是可以完全替代掉补间动画了
说一下视图动画的缺陷:
视图动画只提供为视图类设置动画的能力,所以如果你想为非视图的类设置动画,你需要自己实现代码来完成这个功能。
视图动画也只能设置视图类的一部分属性设置动画,例如缩放和旋转视图,但不能为视图的背景设置动画。
视图动画只能为当前绘制的视图设置动画,但并没有改变视图本身的属性,例如,如果你为一个按钮设置动画,使按钮在屏幕上移动,按钮在屏幕上按照预期的期望绘制在指定的位置,但是实际你点击按钮能触发按钮事件的位置还是在原处。
但是使用属性动画,上面提到的这些视图动画的缺点都将不存在。
你可以为任何对象设置动画,包括视图类和非视图类,并且修改的就是视图本身的属性。
属性动画在实现动画的策略方面也是更健壮的。在一个更高的水平上,可以为动画分配你想设置动画的属性,例如颜色,位置,大小并且可以为动画设置插值器的行为,你也可以同时执行多个动画。
8- 如何让自定义view中绘制的内容执行动画(20分)
在自定义view中,对绘制view,可以使用Matrix工具来执行动画
对Matrix的阐述:
Matrix的对图像的处理可分为四类基本变换:
Translate 平移变换
Rotate 旋转变换
Scale 缩放变换
Skew 错切变换
这里我们可以通过这四个基本转换功能,对图片进行执行动画。
平移动画:
旋转动画:
平移+旋转动画:
当然,除了使用Matrix,还可以使用以下工具类,帮助实现动画效果
TranslateAnimation
ScaleAnimation
AlphaAnimation
等动画工具来实现
如图所示:
9- 介绍一下Matrix的作用(3分)
在自定义view中,对绘制view,可以使用Matrix工具来执行动画
对Matrix的阐述:
Matrix在处理动画时, 可分为四类基本变换:
Translate 平移变换
Rotate 旋转变换
Scale 缩放变换
Skew 错切变换
这里我们可以通过这四个基本转换功能,对图片进行执行动画。
平移动画:
旋转动画:
平移+旋转动画:
自定义控件相关面试题
10- 介绍下说一下view的绘制流程 (10分)
整个View树的绘图流程是在ViewRootImpl类的performTraversals()方法开始的。
该函数做的执行过程主要是根据之前设置的状态,判断是否重新计算视图大小(measure)、是否重新放置视图的位置(layout)、以及是否重绘 (draw)
如下就是整个流程的大致流程图:
View绘制流程第一步:递归measure:
为整个View树计算实际的大小,然后设置实际的高和宽。
每个View控件的实际宽高都是由父视图和自身决定的。
实际的测量是在onMeasure方法进行,所以在View的子类需要重写onMeasure方法
View绘制流程第二步:递归layout
layout也是从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上
View绘制流程第三步:递归draw
如果该View是一个ViewGroup,则需要递归绘制其所包含的所有子View
如果是View:
首先对View的背景进行绘制
对View的内容进行绘制
对当前View的所有子View进行绘制,如果当前的View没有子View就不需要进行绘制。
对View的滚动条进行绘制。
11- 如何自定义控件?(3分)
1继承View完全自定义或继承View的派生子类
View 派生出来的直接或间接子类:ImageView, Button, CheckBox, SurfaceView, TextView, ViewGroup, AbsListView
2 定义自定义属性
在资源元素<declare-styleable>中为您的view定义自定义属性
在指定的XML布局中使用指定属性的值
获取自定义属性
我们知道:
当view从XML布局中创建了之后,XML标签中所有的属性都从资源包中读取出来并作为一个AttributeSet传递给view的构造函数。
尽管从AttributeSet中直接读取值是可以的,但是这样做有一些缺点:
带有值的资源引用没有进行处理。比如:样式资源并没有处理
为了解决这样问题:
可以通过Context.obtainStyledAttributes()方法,这个方法传回了一个TypedArray数组,包含了引用和样式化的值。
TypedArray数组时如何生成的呢?
我们知道Android资源编译器在编译res文件夹下的文件时3, 它把每个<declare-styleable>资源,生成的R.java都定义了一个属性ID的数组以及一套定义了指向数组中的每一个属性 的常量。您可以使用预定义的常量从TypedArry中读取属性。
下例是PieChart类是如何读取这些属性的:
注意:TypedArry对象是一个共享的资源,使用完毕必须回收它。
添加属性和事件
属性是控制view的行为和外观的强有力的方式,但是只有view在初始化后这些属性才可读。为了提供动态的行为,需要暴露每个自定义属性的一对getter和setter。下面的代码片段显示PieChart是如何提供showText属性的
2自定义绘制
重写onMeasure方法,测量控件的大小
重写onLayout方法,计算布局的位置
重写onDraw方法:进行绘制
onDraw()的参数是视图可以用来绘制自己的Canvas对象.
Canvas定义用来绘制文本、线条、位图和其他图像单元. 你可以在onDraw()里使用这些方法创建你的自定义用户界面(UI).
12- 自定义控件的方式有哪些?
1. 组合方式(如优酷菜单)
2、直接继承于View(如ToggleButton)
3、直接继承ViewGroup(如SlidingMenu)
4、继承于其它View,对功能进行增强(如PullToRefreshListView)
13- 自定义控件常用的方法?
1、onMeasure方法:测量控件的大小
2、onLayout方法:对子Viw进行排版
3、onDraw方法:把控件画出来
14- Touch事件相关方法和作用(5分)
事件分发:public boolean dispatchTouchEvent(MotionEvent ev)
Touch 事件发生时 Activity 的 dispatchTouchEvent(MotionEvent ev) 方法,然后将事件传递给最外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法,并由该 View 的 dispatchTouchEvent(MotionEvent ev) 方法对事件进行分发。dispatchTouchEvent 的事件分发逻辑如下:
如果 return true,事件会分发给当前 View 并由 dispatchTouchEvent 方法进行消费,同时事件会停止向下传递;
如果 return false,事件分发分为两种情况:
如果当前 View 获取的事件直接来自 Activity,则会将事件返回给 Activity 的 onTouchEvent 进行消费;
如果当前 View 获取的事件来自外层父控件,则会将事件返回给父 View 的 onTouchEvent 进行消费。
如果返回系统默认的 super.dispatchTouchEvent(ev),事件会自动的分发给当前 View 的 onInterceptTouchEvent 方法。
事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)
在外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法返回系统默认的 super.dispatchTouchEvent(ev) 情况下,事件会自动的分发给当前 View 的 onInterceptTouchEvent 方法。onInterceptTouchEvent 的事件拦截逻辑如下:
如果 onInterceptTouchEvent 返回 true,则表示将事件进行拦截,并将拦截到的事件交由当前 View 的 onTouchEvent 进行处理;
如果 onInterceptTouchEvent 返回 false,则表示将事件放行,当前 View 上的事件会被传递到子 View 上,再由子 View 的 dispatchTouchEvent 来开始这个事件的分发;
如果 onInterceptTouchEvent 返回 super.onInterceptTouchEvent(ev),事件默认会被拦截,并将拦截到的事件交由当前 View 的 onTouchEvent 进行处理。
事件响应:public boolean onTouchEvent(MotionEvent ev)
在 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev) 并且 onInterceptTouchEvent 返回 true 或返回 super.onInterceptTouchEvent(ev) 的情况下 onTouchEvent 会被调用。onTouchEvent 的事件响应逻辑如下:
如果事件传递到当前 View 的 onTouchEvent 方法,而该方法返回了 false,那么这个事件会从当前 View 向上传递,并且都是由上层 View 的 onTouchEvent 来接收,如果传递到上面的 onTouchEvent 也返回 false,这个事件就会“消失”,而且接收不到下一次事件。
如果返回了 true 则会接收并消费该事件。
如果返回 super.onTouchEvent(ev) 默认处理事件的逻辑和返回 false 时相同。
15- Android中touch事件的传递机制是怎样的? (10分)
总结:
1.Touch事件传递的相关API有dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent
2.Touch事件相关的类有View、ViewGroup、Activity
3.Touch事件会被封装成MotionEvent对象,该对象封装了手势按下、移动、松开等动作
4.Touch事件通常从Activity#dispatchTouchEvent发出,只要没有被消费,会一直往下传递,到最底层的View。
5.如果Touch事件传递到的每个View都不消费事件,那么Touch事件会反向向上传递,最终交由Activity#onTouchEvent处理.
6.onInterceptTouchEvent为ViewGroup特有,可以拦截事件.
7.Down事件到来时,如果一个View没有消费该事件,那么后续的MOVE/UP事件都不会再给它
16- onTouch和onTouchEvent有什么区别,又该如何使用?(5分)
通过源码查看: View – dispatchTouchEvent方法中
可以看到:
onTouchListener的接口的优先级是要高于onTouchEvent的,假若onTouchListener中的onTouch方法返回true,
表示此次事件已经被消费了,那onTouchEvent是接收不到消息的。
通过上面的源代码,大家思考一个问题。
如果给一个Button设置一个onTouchListener并且重写onTouch方法,返回值为true,
此时的Button的点击事件还处理吗?
实例代码:
答: 是得不到处理的。
由于Button的performClick是利用onTouchEvent实现,假若onTouchEvent没有被调用到,那么Button的Click事件也无法响应。
这里可以查看源码:View – onTouchEvent方法,来说明
总结:
1. onTouchListener的onTouch方法优先级比onTouchEvent高,会先触发。
2. 假如onTouch方法返回false会接着触发onTouchEvent,反之onTouchEvent方法不会被调用。
3. 内置诸如click事件的实现等等都基于onTouchEvent,假如onTouch返回true,这些事件将不会被触发。
17- getMeasuredWidth和getWidth分别在什么时候可以获取到值(10分)
1.在构造方法中无论是getWidth还是getMeasuredWidth都是得不到正确数值的。
2.getMeasuredWidth得到正确数值需要在调用过onMeasure之后。
3.getWidth得到正确数值需要调用过onLayout之后
可以查看实例代码:C:\AndroidStudioProject\Questions-Explain\day02\measurewidthdemo
18- viewpager和viewpager中的view的触摸事件的响应规则(10分)
从事件的传递机制分析:
Acitivity获取事件,分发给Viewpager
ViewPager获取事件以后,将分发给ViewPager的View,也就是它的孩子
如果View不处理onTouchEvent事件,且没有滑动事件,此时,将这次的触摸事件传递给ViewPager
而Viewpager默认有左右滑动的事件,因此,它会处理的onTouchEvent事件,来实现左右滑动。
如果View也需要左右滑动事件,此时,ViewPager和它的孩子View就会发生事件的冲突。
如何解决viewpager和子view冲突?
可以用requestDisallowInterceptTouchEvent()方法防止viewpager和子view冲突
我们知道Android事件机制是从父View传向子View的,也就是说,ViewPager 默认会把事件传递最底层的。
此时,View就可以在它的dispatchTouchEvent()方法中进行判断,是否告诉Viewpager的进行事件拦截。
下面对应的实例代码:
19- 如何禁止viewpager滑动(5分)
20- Android中如何实现控件触摸拦截判断?(10分)
触摸控件的拦截判断:
第一种方案:
可以在控件的dispatchTouchEvent方法中,请求父控件进行拦截或者不拦截。
可以在控件中重写onInterceptTouchEvent方法,直接对这次事件进行是否拦截的操作。
第二种方法:
通过android-support-v4包下的ViewDragHelper(视图拖拽辅助工具)实现拦截判断和控件的拖拽。
执行ViewDragHelper.create(...)创建对象, 分别在onInterceptTouchEvent和onTouchEvent中把拦截判断和触摸事件处理交由ViewDragHelper对象处理。
在ViewDragHelper.Callback中控制并接收其控件移动后的位置。
在callback中也可以通过onViewReleased方法处理拖拽结束后的动画等事件,进而实现控件的完整拖拽。
21- 触摸事件中getX()和getRawX()的区别(5分)
getX是获取以widget左上角为坐标原点计算的X轴坐标直.
getRawX 获取的是以屏幕左上角为坐标原点计算的X轴坐标直.
22- onmeasure 两个参数是什么? measureSpec这个类是干嘛的(10分)
widthMeasureSpec和heightMeasureSpec这两个int类型的参数。
注意:它们其实不是宽和高,而是由宽、高和各自方向上对应的模式来合成的一个值。
widthMeasureSpec:这个值,包含实际宽度 +
heightMeasureSpec:这个值,包含实际高度 +
此两个值,被定义在Android中的View类的一个内部类中:View.MeasureSpec:
MeasureSpe描述了父View对子View大小的期望.
里面包含了测量模式和大小.我们可以通过以下方式从MeasureSpec中提取模式和大小。
取出对应的测量模式和实际大小:
int specMode = MeasureSpec.getMode(measureSpec);//得到模式
int specSize = MeasureSpec.getSize(measureSpec);//得到大小
也可以通过MeasureSpec的静态方法把大小和模式合成一个widthMeasureSpec值或者heightMeasureSpec值。
MeasureSpec.makeMeasureSpec(specSize,specMode);
注意:这里的specMode分为三种类型:
① UNSPECIFIED:表示默认值,父控件没有给子view任何限制。
② EXACTLY:表示父控件给子view一个具体的值,子view要设置成这些值的大小。 - 对应的是match_parent,具体的dp值
③ AT_MOST:表示父控件给子view一个最大的特定值,而子view不能超过这个值的大小。 - 对应的是wrap_content
因此:可以通过MeasureSpe,计算出子View在父View的期望大小。
使用场景:
23- 谈一谈对canvas.save()和canvas.restore()的理解(3分)
在自定义view中的ondraw方法中进行使用这两方法。
阐述save和restore方法的作用:
Save:会把当前的画布的状态进行保存,然后放入特定的栈中;
restore:会把栈中最顶层的画布状态取出来,并按照这个状态恢复当前的画布,并在这个画布上做画。
Save和restore使用流程:
调用save方法(保存当前的绘制状态)
绘制图形
调用restore方法,把栈中最顶层的画布状态取出来,并按照这个状态恢复当前的画布,并在这个画布上做画
参考实例代码:C:\AndroidStudioProject\Questions-Explain\day02\canvas_save_restore
24- emoji表情实现(3分)
该题考察的富文本的使用:
什么是富文本???
富文本:就是让普通的文本显示的内容更加丰富
富文本的表现???
1. 加载一个表情
2. 文本 + 表情
3. 加载的文本内容为HTML格式
4. 带有下划线的文本
要实现以上功能,这里android系统给我们提供SpannableString工具类。
显示表情的实例代码:
参考实例代码:C:\AndroidStudioProject\Questions-Explain\day02\spannablestring
推荐开源的表情库:https://github.com/shlsy/yykEmoji
与WebView相关的面试题
25- webview和js的交互(5分)
要想使用WebView和js进行互相交互
1. 初始化WebView,并设置WebView支持js
2. Android 执行js
3. Js 执行android
编写Js代码:
编写android代码:
注意:
1. 这里的字符串“Android”,告诉js,调用哪个实例方法
2. 在定义fun1FromAndroid方法上必须要使用@JavascriptInterface注解
总结:
a) 使用WebSettings打开JavaScript开关
b) 使用webview的addJavascriptInterface方法向webView注册回调接口。
比如 Webview.addJavascriptInterface(new JsFunc(),”funcObj”);
c) 创建JsFunc对象,并创建方法,方法必须要使用@JavascriptInterface注解
比如
class JsFunc{
@JavascriptInterface
public void test(){
Log.e(“itheima”,”java 打印的log”);
}
}
d) 在网页里执行javascript代码就可以调用Java代码里的方法
比如:funcObj.test();
26- WebView加载数据性能优化?webview加载数据如何做流量优化(3分)
1. WebView 缓存
开启WebView的缓存功能可以减少对服务器资源的请求,一般使用默认缓存策略就可以了。
2. 提高WebView的渲染的优先级
3. 把图片加载放在最后来加载渲染
4. 资源文件本地存储
资源等文件(不需要更新)本地存储,在需要的时候直接从本地获取。
例如图片文件,js文件,css文件等持久化到本地。延迟加载js文件,图片文件等
5. 减少耗时操作
减少同步操作的操作时间,尽量使用异步操作替代同步操作。
如果服务端存在读取数据库和计算耗时的操作,尽量使用异步(ajax)进行操作,把原本的时间花在异步操作上
27- 如何使WebView自适应屏幕大小?
第一种:
//设置加载进来的页面自适应手机屏幕
settings.setUseWideViewPort(true);
settings.setLoadWithOverviewMode(true);
第一个方法设置webview推荐使用的窗口,设置为true。
第二个方法是设置webview加载的页面的模式,也设置为true。
这方法可以让你的页面适应手机屏幕的分辨率,完整的显示在屏幕上,可以放大缩小。
第二种:根据手机分辨率,设置webView缩放类型