写点什么

备战金九银十:Android 面试 10+ 个知识点总结宝典助你通关

用户头像
Android架构
关注
发布于: 刚刚

事件总是从上往下进行分发,即先到达 Activity,再到达 ViewGroup,再到达子 View,如果没有任何视图消耗事件的话,事件会顺着路径往回传递。其中:


  1. dispatchTouchEvent 是事件的分发方法,如果事件能够到达该视图的话,就首先一定会调用,一般我们不会去修改这个方法。

  2. onInterceptTouchEvent 是事件分发的核心方法,表示 ViewGroup 是否拦截事件,如果返回 true 表示拦截,在这之后 ViewGroup 的 onTouchEvent 会被调用,事件就不会往下传递。

  3. onTouchEvent 是最低级的,在事件分发中最后被调用。

  4. 子 View 可以通过 requestDisallowInterceptTouchEvent 方法去请求父元素不要拦截。

注意
  1. 事件从 Activity.dispatchTouchEvent()开始传递,只要没有被停止或拦截,从最上层的 View(ViewGroup)开始一直往下(子 View)传递。子 View 可以通过 onTouchEvent()对事件进行处理。

  2. 事件由父 View(ViewGroup)传递给子 View,ViewGroup 可以通过 onInterceptTouchEvent()对事件做拦截,停止其往下传递。

  3. 如果事件从上往下传递过程中一直没有被停止,且最底层子 View 没有消费事件,事件会反向往上传递,这时父 View(ViewGroup)可以进行消费,如果还是没有被消费的话,最后会到 Activity 的 onTouchEvent()函数。

  4. 如果 View 没有对 ACTION_DOWN 进行消费,之后的其他事件不会传递过来。

  5. OnTouchListener 优先于 onTouchEvent()对事件进行消费。

自定义 View 的分类

  1. 对现有的 View 的子类进行扩展,例如复写 onDraw 方法、扩展新功能等。

  2. 自定义组合控件,把常用一些控件组合起来以方便使用。

  3. 直接继承 View 实现 View 的完全定制,需要完成 View 的测量以及绘制。

  4. 自定义 ViewGroup,需要复写 onLayout 完成子 View 位置的确定等工作。

View 的测量-onMeasure

View 的测量最终是在 onMeasure 方法中通过 setMeasuredDimension 把代表宽高两个 MeasureSpec 设置给 View,因此需要掌握 MeasureSpec。MeasureSpec 包括大小信息以及模式信息。


MeasureSpec 的三种模式:


  1. EXACTLY 模式:精确模式,对应于用户指定为 match_parent 或者具体大小的时候(实际上指定为 match_parent 实质上是指定大小为父容器的大小)

  2. AT_MOST 模式:对应于用户指定为 wrap_content,此时控件尺寸只要不超过父控件允许的最大尺寸即可。

  3. UNSPECIFIED 模式:不指定大小的测量模式,这种模式比较少用


下面给出模板代码:


public class MeasureUtils {/**


  • 用于 View 的测量

  • @param measureSpec

  • @param defaultSize

  • @return ? ? ? ? */public static int measureView(int measureSpec, int defaultSize) {int measureSize; ? ? ? ? ? ? ? ?//获取用户指定的大小以及模式 int mode = View.MeasureSpec.getMode(measureSpec);int size = View.MeasureSpec.getSize(measureSpec); ? ? ? ? ? ? ? ?//根据模式去返回大小 if (mode == View.MeasureSpec.EXACTLY) { ? ? ? ? ? ? ? ?//精确模式(指定大小以及 match_parent)直接返回指定的大小 measureSize = size;} else { ? ? ? ? ? ? ? ?//UNSPECIFIED 模式、AT_MOST 模式(wrap_content)的话需要提供默认的大小 measureSize = defaultSize;if (mode == View.MeasureSpec.AT_MOST) {//AT_MOST(wrap_content)模式下,需要取测量值与默认值的最小值 measureSize = Math.min(measureSize, defaultSize);}}return measureSize;}}


最后,复写 onMeasure 方法,把 super 方法去掉:


@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(MeasureUtils.measureView(widthMeasureSpec, 200),MeasureUtils.measureView(heightMeasureSpec, 200));}

View 的绘制-onDraw

View 绘制,需要掌握 Android 中 View 的坐标体系:



View 的坐标体系是以左上角为坐标原点,向右为 X 轴正方向,向下为 Y 轴正方向。


View 绘制,主要是通过 Android 的 2D 绘图机制来完成,时机是 onDraw 方法中,其中包括画布 Canvas,画笔 Paint。下面给出示例代码。相关 API 不是介绍的重点,重点是 Canvas 的 save 和 restore 方法,通过 save 以后可以对画布进行一些放大缩小旋转倾斜等操作,这两个方法一般配套使用,其中 save 的调用次数可以多于 restore。


@Override ? ?protected void onDraw(Canvas canvas) {super.onDraw(canvas);Bitmap bitmap = ImageUtils.drawable2Bitmap(mDrawable);canvas.drawBitmap(bitmap, getLeft(), getTop(), mPaint);canvas.save(); ? ? ? ?//注意,这里的旋转是指画布的旋转 canvas.rotate(90);mPaint.setColor(Color.parseColor("#FF4081"));mPaint.setTextSize(30);canvas.drawText("测试", 100, -100, mPaint);canvas.restore();}

View 的位置-onLayout

与布局位置相关的是 onLayout 方法的复写,一般我们自定义 View 的时候,只需要完成测量,绘制即可。如果是自定义 ViewGroup 的话,需要做的就是在 onLayout 中测量自身以及控制子控件的布局位置,onLayout 是自定义 ViewGroup 必须实现的方法。

8、性能优化

布局优化

  1. 使用 include 标签,通过 layout 属性复用相同的布局。


<includeandroid:id="@+id/v_test"layout="@layout/include_view" />


  1. 使用 merge 标签,去除同类的视图

  2. 使用 ViewStub 来进行布局的延迟加载一些不是马上就用到的布局。例如列表页中,列表在没有拿到数据之前不加载,这样做可以使 UI 变得流畅。


<ViewStubandroid:id="@+id/v_stub"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout="@layout/view_stub" />//需要手动调用 inflate 方法,布局才会显示出来。stub.inflate();//其中 setVisibility 在底层也是会调用 inflate 方法//stub.setVisibility(View.VISIBLE);//之后,如果要使用 ViewStub 标签里面的 View,只需要按照平常来即可。TextView tv_1 = (TextView) findViewById(R.id.tv_1);


  1. 尽量多使用 RelativeLayout,因为这样可以大大减少视图的层级。

内存优化

APP 设计以及代码编写阶段都应该考虑内存优化:


  1. 珍惜 Service,尽量使得 Service 在使用的时候才处于运行状态。尽量使用 IntentService


IntentService 在内部其实是通过线程以及 Handler 实现的,当有新的 Intent 到来的时候,会创建线程并且处理这个 Intent,处理完毕以后就自动销毁自身。因此使用 IntentService 能够节省系统资源。


  1. 内存紧张的时候释放资源(例如 UI 隐藏的时候释放资源等)。复写 Activity 的回调方法。


@Override public void onLowMemory() {super.onLowMemory(); }@Override public void onTrimMemory(int level) {super.onTrimMemory(level);switch (level) {case TRIM_MEMORY_COMPLETE://...break;case 其他:}}


  1. 通过 Manifest 中对 Application 配置更大的内存,但是一般不推荐


android:largeHeap="true"


  1. 避免 Bitmap 的浪费,应该尽量去适配屏幕设备。尽量使用成熟的图片加载框架,Picasso,Fresco,Glide 等。

  2. 使用优化的容器,SparseArray 等

  3. 其他建议:尽量少用枚举变量,尽量少用抽象,尽量少增加类,避免使用依赖注入框架,谨慎使用 library,使用代码混淆,时当场合考虑使用多进程等。

  4. 避免内存泄漏(本来应该被回收的对象没有被回收)。一旦 APP 的内存短时间内快速增长或者 GC 非常频繁的时候,就应该考虑是否是内存泄漏导致的。


分析方法 1. 使用 Android Studio 提供的 Android Monitors 中 Memory 工具查看内存的使用以及没使用的情况。2. 使用 DDMS 提供的 Heap 工具查看内存使用情况,也可以手动触发 GC。3. 使用性能分析的依赖库,例如 Square 的 LeakCanary,这个库会在内存泄漏的前后通过 Notification 通知你。

什么情况会导致内存泄漏

  1. 资源释放问题:程序代码的问题,长期保持某些资源,如 Context、Cursor、IO 流的引用,资源得不到释放造成内存泄露。

  2. 对象内存过大问题:保存了多个耗用内存过大的对象(如 Bitmap、XML 文件),造成内存超出限制。

  3. static 关键字的使用问题:static 是 Java 中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用 static 修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context 的情况最多),这时就要谨慎对待了。


解决方案 1. 应该尽量避免 static 成员变量引用资源耗费过多的实例,比如 Context。2. Context 尽量使用 ApplicationContext,因为 Application 的 Context 的生命周期比较长,引用它不会出现内存泄露的问题。3. 使用 WeakReference 代替强引用。比如可以使用 WeakReference<Context> mContextRef


  1. 线程导致内存溢出:线程产生内存泄露的主要原因在于线程生命周期的不可控。例如 Activity 中的 Thread 在 run 了,但是 Activity 由于某种原因重新创建了,但是 Thread 仍然会运行,因为 run 方法不结束的话 Thread 是不会销毁的。


解决方案 1. 将线程的内部类,改为静态内部类(因为非静态内部类拥有外部类对象的强引用,而静态类则不拥有)。2. 在线程内部采用弱引用保存 Context 引用。

查看内存泄漏的方法、工具

  1. android 官方提供的工具:Memory Monitor(当 APP 占用的内存在短时间内快速增长或者 GC 变得频繁的时候)、DDMS 提供的 Heap 工具(手动触发 GC)

  2. Square 提供的内存泄漏检测工具,LeakCanary(能够自动完成内存追踪、检测、输出结果),进行演示,并且适当的解说。

性能优化

  1. 防止过度绘制,通过打开手机的“显示过度绘制区域”即可查看过度绘制的情况。

  2. 最小化渲染时间,使用视图树查看节点,对节点进行性能分析。

  3. 通过 TraceView 进行数据的采集以及分析。在有大概定位的时候,使用 Android 官方提供的 Debug 类进行采集。最后通过 DDMS 即可打开这个.trace 文件,分析函数的调用情况(包括在指定情况下执行时间,调用次数)


//开启数据采集 Debug.startMethodTracing("test.trace");//关闭 Debug.stopMethodTracing();

OOM

避免 OOM 的一些常见方法:


  1. App 资源中尽量少用大图。使用


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


Bitmap 的时候要注意等比例缩小图片,并且注意 Bitmap 的回收。


BitmapFactory.Options options = new BitmapFactory.Option();options.inSampleSize = 2; //Options 只保存图片尺寸大小,不保存图片到内存 BitmapFactory.Options opts = new BitmapFactory.Options();opts.inSampleSize = 2;Bitmap bmp = null;bmp = BitmapFactory.decodeResource(getResources(), mImageIds[position],opts); //回收 bmp.recycle();


  1. 结合组件的生命周期,释放资源

  2. IO 流,数据库查询的游标等应该在使用完之后及时关闭。

  3. ListView 中应该使用 ViewHolder 模式缓存 ConverView

  4. 页面切换的时候尽量去传递(复用)一些对象

ANR

不同的组件发生 ANR 的时间不一样,主线程(Activity、Service)是 5 秒,BroadCastReceiver 是 10 秒。


ANR 一般有三种类型:


  1. KeyDispatchTimeout(5 seconds)主要类型按键或触摸事件在特定时间内无响应

  2. BroadcastTimeout(10 seconds)BroadcastReceiver 在特定时间内无法处理完成

  3. ServiceTimeout(20 seconds)小概率类型 Service 在特定的时间内无法处理完成


解决方案:1. UI 线程只进行 UI 相关的操作。所有耗时操作,比如访问网络,Socket 通信,查询大量 SQL 语句,复杂逻辑计算等都放在子线程中去,然后通过 handler.sendMessage、runonUITread、AsyncTask 等方式更新 UI。2. 无论如何都要确保用户界面操作的流畅度。如果耗时操作需要让用户等待,那么可以在界面上显示进度条。3. BroadCastReceiver 要进行复杂操作的的时候,可以在 onReceive()方法中启动一个 Service 来处理。

9、九切图(.9 图)、SVG 图片

九切图

点九图,是 Android 开发中用到的一种特殊格式的图片,文件名以”.9.png“结尾。这种图片能告诉程序,图像哪一部分可以被拉升,哪一部分不能被拉升需要保持原有比列。运用点九图可以保证图片在不模糊变形的前提下做到自适应。点九图常用于对话框背景图片中。



  1. 1、2 部分规定了图像的可拉伸部分,当实际程序中设定了对话框的宽高时,1、2 部分就会被拉伸成所需要的高和宽,呈现出于设计稿一样的视觉效果。

  2. 而 3、4 部分规定了图像的内容区域。内容区域规定了可编辑区域,例如文字需要被包裹在其内。


android5.0 的 SCG 矢量动画机制

  1. 图像在方法缩小的时候图片质量不会有损失

  2. 使用 XML 来定义图形

  3. 适配不同分辨率

10、Android 中数据常见存储方式

  1. 文件(包括 XML、SharePreference 等)

  2. 数据库

  3. Content Provider

  4. 保存在网络

11、进程间通信

操作系统进程间通信的方法,android 中有哪些?

操作系统:


  1. Windows:剪贴板、管道、邮槽等

  2. Linux:命名管道、共享内存、信号量


Android 中的进程通信方式并不是完全继承于 Linux:


  1. Bundle

  2. 文件共享

  3. AIDL

  4. Messenger

  5. Content Provider

  6. Socket

12、常见的网络框架

常用的 http 框架以及他们的特点

  1. HttpURLConnection:在 Android 2.2 版本之前,HttpClient 拥有较少的 bug,因此使用它是最好的选择。而在 Android 2.3 版本及以后,HttpURLConnection 则是最佳的选择。它的 API 简单,体积较小,因而非常适用于 Android 项目。压缩和缓存机制可以有效地减少网络访问的流量,在提升速度和省电方面也起到了较大的作用。对于新的应用程序应该更加偏向于使用 HttpURLConnection,因为在以后的工作当中我们也会将更多的时间放在优化 HttpURLConnection 上面。特点:比较轻便,灵活,易于扩展,在 3.0 后以及 4.0 中都进行了改善,如对 HTTPS 的支持,在 4.0 中,还增加了对缓存的支持。

  2. HttpClient:高效稳定,但是维护成本高昂,故 android 开发团队不愿意在维护该库而是转投更为轻便的

  3. okHttp:okhttp 是一个 Java 的 HTTP+SPDY 客户端开发包,同时也支持 Android。需要 Android 2.3 以上。特点:OKHttp 是 Android 版 Http 客户端。非常高效,支持 SPDY、连接池、GZIP 和 HTTP 缓存。默认情况下,OKHttp 会自动处理常见的网络问题,像二次连接、SSL 的握手问题。如果你的应用程序中集成了 OKHttp,Retrofit 默认会使用 OKHttp 处理其他网络层请求。从 Android4.4 开始 HttpURLConnection 的底层实现采用的是 okHttp。

  4. volley:早期使用 HttpClient,后来使用 HttpURLConnection,是谷歌 2013 年推出的网络请求框架,非常适合去进行数据量不大,但通信频繁的网络操作,而对于大数据量的网络操作,比如说下载文件等,Volley 的表现就会非常糟糕。

  5. xutils:缓存网络请求数据

  6. Retrofit:和 Volley 框架的请求方式很相似,底层网络请求采用 okhttp(效率高,android4.4 底层采用 okhttp),采用注解方式来指定请求方式和 url 地址,减少了代码量。

  7. AsyncTask

13、常用的图片加载框架以及特点、源码

  1. Picasso:PicassoSquare 的网络库一起能发挥最大作用,因为 Picasso 可以选择将网络请求的缓存部分交给了 okhttp 实现。

  2. Glide:模仿了 Picasso 的 API,而且在他的基础上加了很多的扩展(比如 gif 等支持),支持图片流,因此在做爱拍之类的视频应用用得比较多一些。

  3. Fresco:Fresco 中设计有一个叫做 image pipeline 的模块。它负责从网络,从本地文件系统,本地资源加载图片。 为了最大限度节省空间和 CPU 时间,它含有 3 级缓存设计(2 级内存,1 级文件)。Fresco 中设计有一个叫做 Drawees 模块, 方便地显示 loading 图,当图片不再显示在屏幕上时,及时地释放内存和空间占用。


Fresco 是把图片缓存放在了 Ashmem(系统匿名内存共享区)


  1. Heap-堆内存:Android 中每个 App 的 Java 堆内存大小都是被严格的限制的。每个对象都是使用 Java 的 new 在堆内存实例化,这是内存中相对安全的一块区域。内存有垃圾回收机制,所以当 App 不在使用内存的时候,系统就会自动把这块内存回收。不幸的是,内存进行垃圾回收的过程正是问题所在。当内存进行垃圾回收时,内存不仅仅进行了垃圾回收,还把 Android 应用完全终止了。这也是用户在使用 App 时最常见的卡顿或短暂假死的原因之一。

  2. Ashmem:Android 在操作 Ashmem 堆时,会把该堆中存有数据的内存区域从 Ashmem 堆中抽取出来,而不是把它释放掉,这是一种弱内存释放模式;被抽取出来的这部分内存只有当系统真正需要更多的内存时(系统内存不够用)才会被释放。当 Android 把被抽取出来的这部分内存放回 Ashmem 堆,只要被抽取的内存空间没有被释放,之前的数据就会恢复到相应的位置。


不管发生什么,垃圾回收器都不会自动回收这些 Bitmap。当 Android 绘制系统在渲染这些图片,Android 的系统库就会把这些 Bitmap 从 Ashmem 堆中抽取出来,而当渲染结束后,这些 Bitmap 又会被放回到原来的位置。如果一个被抽取的图片需要再绘制一次,系统仅仅需要把它再解码一次,这个操作非常迅速。

14、在 Android 开发里用什么做线程间的通讯工具?

传统点的方法就是往同步代码块里些数据,然后使用回调让另外一条线程去读。在 Android 里我一般会创建 Looper 线程,然后 Hanlder 传递消息。

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
备战金九银十:Android面试10+个知识点总结宝典助你通关