code小生,一个专注 Android 领域的技术平台
公众号回复
Android
加入我的安卓技术群
作者:念人远乡链接:https://www.jianshu.com/p/3cd5ef51eed5声明:本文已获
念人远乡
授权发表,转发等请联系原作者授权
从 2018.12.28 的第一次面试到 2019.01.09 整整横跨了一年。也面试了几家公司的 Android 实习僧的岗位。
有大厂:滴滴、猪厂、字节等也有中厂:玩吧App等也有小厂:xxxxxx
给我的感觉就是
大厂更注重你的基本知识,你对待一个问题的思路。是否是以一个工程师的角度去看待问题。也更注重数据结构&&算法(我的痛)这一块的知识的考察。不会是简单的考你排序、查找这种考研层面上的东西。小厂的话,大概是因为需要即插即用,也没有很好的导师类资源,更看重你的自我学习能力和一个自我学习的过程,以及实际开发的能力。好了,话不多说,我们就直接进入面试的总结。题目旁边会标注是大厂中厂还是小厂的面试题,就不按公司整理啦,如果是All就是基本都会考到(需要重点理解)(部分问题的答案是自己
google 后的结果和学习的结果,部分问题的答案也来自
厘米姑娘
小姐姐的简书里的三份安卓面试题解(真的很详细很感谢了!))
1. 四大组件相关问题
四种启动模式:Standerd、SingleTop、SingleTask、SingleInstance。
Standard(默认标准启动模式):每次启动都重新创建一个新的实例,不管它是否存在。且谁启动了这个Acitivity,那么这个Acitivity就运行在启动它的那个Acitivity的任务栈中。
SingleTop(栈顶复用模式):如果新的Activity已经位于任务栈的栈顶,那么不会被重新创建,而是回调onNewIntent()方法,通过此方法的参数可以取出当前请求的信息。
SingleTask(栈内复用模式):这是一种单例模式,在这种模式下,只要Acitivity在一个栈中存在,那么多次启动此Acitivity都不会重建实例,而是回调onNewIntent方法。同时由于SingleTask模式有ClearTop功能,因此会导致所要求的Acitivity上方的Acitivity全部销毁。
SingleInstance(单实例模式):和栈内复用类似,此种模式的Acitivity只能单独位于一个任务栈中。全局唯一性。单例实例,不是创建,而是重用。独占性,一个Acitivity单独运行在一个工作栈中。
此题在理解启动模式的情况下解答。
栈内情况
onCreate()表示 Activity 正在创建,常做初始化工作,如 setContentView界面资源、初始化数据。onStart()表示Activity 正在启动,这时Activity 可见但不在前台,无法和用户交互。onResume()表示Activity 获得焦点,此时Activity 可见且在前台并开始活动。onPause()表示Activity 正在停止,可做 数据存储、停止动画等操作。onStop()表示activity 即将停止,可做稍微重量级回收工作,如取消网络连接、注销广播接收器等。onDestroy()表示Activity
即将销毁,常做回收工作、资源释放。onRestart()表示Activity由不可见到可见的过程,Activity重新启动。
屏幕旋转时的生命流程:onPause()onSaveInstanceState()onStop()onDestroy()onCreate()onStart()onRestoreInstanceState()
onResume()为了避免由于配置改变导致 Activity 重建,可在AndroidManifest.xml中对应的 Activity中设置android:configChanges="orientation|screenSize"。此时再次旋转屏幕时,该Activity不会被系统杀死和重建,只会调用onConfigurationChanged。
打开第一个A:A.OnCreate()->A.onStart()->A.onResume()此时由A跳转至B:A.onPause()->B.onCreate()->B.onStart()->B.onResume()->A.onStop()此时B的启动模式是栈顶模式,再由B打开B:B.onPause()->B.onNewIntent()->B.onResume()
Service是Android中实现程序后台运行的一种解决方案,适合执行那些不需要与用户交互而且要求长期运行的任务。
①组件通过调用 Context 的 StartService()方法启动一个服务,回调服务中的onStartCommand()。如果该服务还没有被创建,则回调的顺序为 onCreate()->onStartCommand()。服务被启动后会一直保存运行的状态,直到 StopService()或者 StopSelf() 方法被调用,服务停止并回调 onDestroy()。无论调用多少次StartService()只需要调用一次 StopService() 就能终止服务。②组件通过调用
Context 的 bindService() 可以绑定一个服务,回调服务中的onBind() 方法。类似地,如果该服务之前还没创建,那么回调的顺序是onCreate()->onBind()。之后调用方可以获取到 onBind() 方法里返回的IBinder 对象的实例,从而实现和服务的通信。直到调用了 unBindService() 方法使服务终止,回调顺序 onUnBind()->onDestroy()。
①通过绑定服务的方式。在绑定的服务中声明一个Binder类,并创建一个Binder对象,在onBind()函数中返回这个对象,并让Activity实现ServiceConnection接口,在OnServiceConnected方法中获取到Service提供的这个Binder对象,通过这个对象的各种自定义的方法就能完成Service与Activity的通信。②通过Intent的方式,在StartService()中需要传入一个Intent对象作为参数,通过这个Intent实例对象进行实现通信。③通过Callback和Handler的方式,在绑定的服务中声明一个Binder类,并创建一个Binder对象,在onBind()函数中返回这个对象,让Activity实现ServiceConnection接口,并且在OnserviceConnected方法中实例化Service中的CallBack接口,并且实现OnDataChange()方法,其中的实质是一段Handler代码,可以在其中完成耗时操作,以这种方式完成通信。
①File文件存储方式:写入读取文件和Java中实现IO类似②SharePreferences存储:一种轻型的数据存储方式,适用于基本类型数据,本质是以键值对
存在的XML文件。③SQLite数据库存储: 一款轻量级的关系型数据,运算速度快,资源占用少,存储复杂的关系型数据时候使用④ContentProvider:四大组件,用于数据的存储和共享,不止局限于数据被该应用程序使用,且能让不同的应用之间进行数据共享,还能通过对指定的一部分数据进行共享,从而保证隐私数据不会有泄露的风险。
广播是一种运用在应用程序之间传输信息的机制,Android中我们发送广播内容实质是一个Intent,这个Intent中可以携带我们要发送的数据。(当然也不可以像Service一样简单的谈谈概念性的东西,你可以深入的展开。)
譬如广播的三大种类①普通广播:一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因此它们接收的先后是随机的。②有序广播:一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递,所以此时的广播接收器是有先后顺序的,且优先级(priority)高的广播接收器会先收到广播消息。有序广播可以被接收器截断使得后面的接收器无法收到它。③本地广播:发出的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收本应用程序发出的广播。
或者广播的两大注册方式?
静态注册:① 创建一个广播接受器类,在onReceive()方法中Toast一段信息。② 在AndroidMainfest.xml中注册才可以使用。
动态注册:新建一个类,继承自BroadCastReceiver,并重写OnReceive函数。由于动态注册是实现在OnCreate方法中的,因此存在一个缺点,必须启动后才能接受到广播。未启动之下就能接收到广播的话,使用静态注册广播接收器。
或者再深入可以聊聊本地广播的源码角度分析其高效、安全、内部协作是如何实现的。这里我们就不展开了。
二. Android 消息机制
① 概述:Handler是可以通过发送和处理Message和Runnable对象来进行消息传递,是一种异步消息机制。可以让对应的Message和Runnable在未来的某个时间点进行相应的处理。让耗时操作在子线程里执行,让更新UI的操作在主线程中完成,而子线程和主线程之间的通信就是靠Handler实现的。② 成员:
Message(消息):是线程之间传递的信息,可以携带少量的信息,用于在不同线程之间交换数据。
Handler(处理者):负责Message的发送及处理。通过 Handler.sendMessage() 向消息池发送各种消息事件;通过 Handler.handleMessage() 处理相应的消息事件。
MessageQueue(消息队列):用来存放Handler发送过来的消息,内部通过单链表的数据结构来维护消息列表,等待Looper的抽取。
Looper(消息泵):通过Looper.loop()不断地从MessageQueue中抽取Message,按分发机制将消息分发给目标处理者。③ 流程:
Handler信息传递机制
Handler.sendMessage()发送消息时,会通过MessageQueue.enqueueMessage()向 MessageQueue 中添加一条消息;
通过 Looper.loop() 开启循环后,不断轮询调用 MessageQueue.next();
调用目标 Handler.dispatchMessage() 去传递消息,目标 Handler 收到消息后调用 Handler.handlerMessage() 处理消息。
和在主线程中直接 new 一个 Handler 不同,由于子线程的 Looper 需要手动去创建,需要手动编写 Looper.loop() 与 Looper.prepare() 方法。
@Override public void run() { Looper.prepare();//调用Looper.prepare() new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; Looper.loop(); //调用Looper.loop() } }).start();
这题我当时没回答上来,回来以后仔细查看了相关的答案。在这里直接引用的
厘米姑娘
小姐姐的答案回答的这个问题。
先说点无关的,这是一连串的提问,涉及到
性能优化->ANR问题定位->消息传递->实现原理,所以在面试的时候你尽量不要选择他问一个你答一句话,你可以把你有把握,之前有所准备的内容,按照一个条理跟面试官阐述清楚,他大概会好感度UP
。当然必须要对你所说的东西保证充分的熟悉,因为他基本都会抓着你所说的继续跟你深入探讨下去。当然实习僧不会问的特别深入,但是你如果能把原理、机制都跟他说的很清晰有条理且有结构的话。会让面试官对你的好感有一定的上升(我猜的但应该没猜错)。也就是我们开题的时候所说的用工程师的眼光看待问题。什么问题?为什么发生问题?怎么定位并解决问题?
以下是我对这道题的回答,只是参考,如果有错误希望指正:① ANR(Application Not Responding,应用程序无响应):当操作在一段时间内系统无法处理时,譬如:应用在5秒内未相应用户的输入事件。广播接收器10秒内未完成相关的处理。服务20秒内无法处理完成,那么会在系统层面会弹出应用程序无响应的对话框。② 所以为了避免发生ANR,我们尽量使用多线程,不要在主线程做耗时操作,而是通过开子线程,把耗时的工作放在工作线程中处理。所使用的方法比如继承自Thread类、实现Runnable接口、使用AsyncTask、IntentService、HandlerThread等机制。③
如果发生了ANR则可以通过data/anr找到traces.txt文件确定ANR发生的原因。④ 关于上面说的这些多线程机制,如果您感兴趣的话我可以简要的跟你阐述其中的一个或几个机制的具体内容。比如AsyncTask机制,AsyncTask机制底层封装了线程池和Handler,便于执行后台任务以及在子线程中进行UI操作。使用AsyncTask要理解3个泛型参数和4个方法。
Params: 这个泛型指定的是我们传递给异步任务执行时的参数的类型。
Progress: 这个泛型指定的是我们的异步任务在执行的时候将执行的进度返回给UI线程的参数的类型。
Result: 这个泛型指定的异步任务执行完后返回给UI线程的结果的类型。我们在定义一个类继承AsyncTask类的时候,必须要指定好这三个泛型的类型,如果都不指定的话,则都将其写成Void。4个方法:当我们执行一个异步任务的时候,其需要按照下面的4个步骤分别执行onPreExecute(): 这个方法是在执行异步任务之前的时候执行,并且是在主线程当中执行的,通常我们在这个方法里做一些UI控件的初始化的操作,例如弹出一给ProgressDialog。
doInBackground(Params… params): 在onPreExecute()方法执行完之后,会马上执行这个方法,这个方法就是来处理异步任务的方法,Android操作系统会在后台的线程池当中开启一个worker thread来执行我们的这个方法,所以这个方法是在工作线程当中执行的,这个方法执行完之后就可以将我们的执行结果发送给我们的最后一个 onPostExecute 方法,在这个方法里,我们可以从网络当中获取数据等一些耗时的操作。
onProgressUpdate(Progess… values): 这个方法也是在主线程当中执行的,我们在异步任务执行的时候,有时候需要将执行的进度返回给我们的UI界面,例如下载一张网络图片,我们需要时刻显示其下载的进度,就可以使用这个方法来更新我们的进度。在调用之前,我们要在 doInBackground 方法中调用publishProgress(Progress) 的方法来将我们的进度时刻传递给onProgressUpdate 方法来更新。
onPostExecute(Result… result): 当我们的异步任务执行完之后,就会将结果返回给这个方法,这个方法也是在UI Thread当中调用的,我们可以将返回的结果显示在UI控件上。
你可以不用说的这么具体,但是如果你能把这一系列都答上来,那面试官应该是不会在继续为难你。我在回答的时候还聊到了多线程可能发生内存泄露的问题,然后被面试官尴尬的说:“不用了不用了,我们继续下一个问题。”其实我也不懂这算不算一种好事吧,但是总归是你对整个知识体系的理解嘛。希望有大佬能指正什么的。感恩!
三. View 及其他控件的使用和优化
其实在这一块,主要还是按照你所做的项目的情况对你进行具体的提问,所以对你的项目所使用的框架、组件、控件必须要烂熟于心。知道为什么使用它,怎么使用它,使用它的结果是优化了些什么?
这也算是老生常谈的一道基本是必考题了,可以先谈谈MotionEvent的几种事件,分别在什么条件下会发生。再谈谈分发的本质、传递顺序、核心方法等,有一点非常关键,因为消息分发机制是一个责任链模式,所以在阐述的时候务必逻辑清晰。如果能结合源码的实现来谈大概也会让面试官好感Up。
ACTION_DOWN:手指接触屏幕
ACTION_MOVE:手指在屏幕上滑动
ACTION_UP:手指在屏幕上松开的一瞬间
ACTION_CANCEL:手指保持按下操作,并从当前控件转移到外层控件时会触发事件分发本质:就是对MotionEvent事件分发的过程。即当一个MotionEvent产生了以后,系统需要将这个点击事件传递到一个具体的View上。点击事件的传递顺序:Activity(Window) -> ViewGroup -> View三个主要方法:
dispatchTouchEvent:进行事件的分发。返回值是 boolean 类型,受当前onTouchEvent和下级view的dispatchTouchEvent影响.
onInterceptTouchEvent:对事件进行拦截。该方法只在ViewGroup中有,一旦拦截,则执行ViewGroup的onTouchEvent,在ViewGroup中处理事件,而不接着分发给View。且只调用一次,所以后面的事件都会交给ViewGroup处理。
onTouchEvent:进行事件处理。另外可以有选择性的记录两段源码,分别是 view 和 viewGroup 的dispatchTouchEvent 方法。以下是 view 的 dispatchTouchEvent()函数的主要部分,主要是三个判断条件的分析。
public boolean dispatchTouchEvent(MotionEvent event){ ...//省略 if(mOnTouchListener != null && (mViewFlags & ENABLED_MASK)==ENABLED && mOnTouchListener.onTouch(this,event)){ return true; } return onTouchEvent(event);}
以下是ViewGroup的dispatchTouchEvent()函数的主要部分:
public boolean dispatchTouchEvent(MotionEvent event){ ...//省略 if(disallowIntercept||!onInterceptTouchEvent(ev)){ child.dispatchEvent()}
你甚至可以和他谈一谈关于 disallowIntercept 去解决滑动冲突的问题。理论搭配源码,食用极佳。关于源码的分析很多大牛都写过了,我也是抱着学习的态度。这里就不贴出来了。
这里当时第一次被问到的时候确实没有准备,之后看了
厘米姑娘
的面试题解(再次强力安利!)就回答上来了,以下直接贴小姐姐所写的解答,可以在这个解答的基础上加上一些自己的看法或者实际开发中遇到的类似问题。(1)处理规则:对于由于外部滑动和内部滑动方向不一致导致的滑动冲突,可以根据滑动的方向判断谁来拦截事件。对于由于外部滑动方向和内部滑动方向一致导致的滑动冲突,可以根据业务需求,规定何时让外部View拦截事件何时由内部View拦截事件。对于上面两种情况的嵌套,相对复杂,可同样根据需求在业务上找到突破点。(2)实现方法:
外部拦截法:指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,否则就不拦截。具体方法:需要重写父容器的 onInterceptTouchEvent 方法,在内部做出相应的拦截。
内部拦截法:指父容器不拦截任何事件,而将所有的事件都传递给子容器,如果子容器需要此事件就直接消耗,否则就交由父容器进行处理。具体方法:需要配合requestDisallowInterceptTouchEvent 方法。
①布局效果:RecyclerView支持线性布局、网格布局、瀑布布局,可以控制横向纵向滚动,从布局效果上来看完爆ListView。②基础使用:ListView需要继承重写BaseAdapter类,自定义ViewHolder和重用ConvertView完成优化工作。RecyclerView继承重写RecyclerView.Adapter和RecyclerView.ViewHolder,设置布局管理器,控制整体布局。规范化了ViewHolder,复用item也不需要像ListView一样SetTag。③空数据处理:ListView提供了setEmptyView这个API来处理Adapter中数据为空的情况,RecyclerView没提供相应API。④HeaderFooter:ListView提供了Add\RemoveHeaderView方法解决,RecyclerView没有提供。⑤局部刷新:ListView刷新notifyDataSetChanged()方法,局部刷新需要自己实现。RecyclerView.Adapter则提供了notifyItemChanged()拥有更新单个itemView的刷新。⑥动画效果:Recycler轻松实现,listview需要自己写属性动画,或者调用第三方库⑦监听item事件:ListView专门提供了用于监听item的回调接口,RecyclerView提供的是addOnItemTouchListener。
①ConvertView重用机制:在getView()方法中使用ConvertView,不需要每次都inflate一个View出来,这样既浪费时间又浪费内存。②Viewholder:使用Viewholder,避免在getView()方法频繁调用去使用findViewById方法,节省时间和内存。③分页加载:每加载一页的数据就覆盖上一页的数据。④数据中有图片:使用第三方库(三级缓存机制)
关于这道题,第一次被问及的时候确实没回答上来,然后面试官就冲我笑笑:“嘿嘿没读过源码吧?”回去以后读了郭霖大佬的博客。做了以下记录:在 Adapter 中的 getView()方法中执行 LayoutInflater.inflate()方法是很消耗资源的,所以ListView通过 RecycleBin 去维护两个数组 mActiveViews 和mScrapViews 用来进行 view 的复用工作。具体是在绘制 view 的(measure->layout->draw)的
layout 过程中实现。主要有三个步骤:①ListView的children->RecycleBin②ListView清空children③Recyclebin->ListView的children举个例子,某一时刻ListView中显示10个子View,position依次是0-9,这时下滑,ListView需要绘制下一帧,这时候ListView在layoutchildren方法中把这10个子View都存入了mActiveViews数组中,然后清空children数组,调用filldown方法,向listview中依次添加position
1到10的子view,在填充1-9时,由于在上一帧position=1-9的view已经被放入了mActiveViews数组中,因此可以直接将其从数组中取出,直接复用。如果没能够从mActivieViews中直接复用View,那么就要调用obtainView方法获取View,该方法尝试间接复用RecycleBin中的mScrapViews中的View,如果不能间接复用,则创建新的View。这边去看郭霖大神的文章:ListView工作原理完全解析。https://blog.csdn.net/guolin_blog/article/details/44996879
四. Java 基础和计网基础
HashMap 基于 AbstractMap类,实现了Map、Cloneable(能被克隆)、Serializable(支持序列化)接口; 非线程安全;允许存在一个为null的key和任意个为null的value;采用链表散列的数据结构,即数组和链表的结合;初始容量为16,填充因子默认为0.75,扩容时是当前容量翻倍,即2capacity。1、Put方法的实现原理:比如hashMap.put(“Java”,0),先使用hash函数来确定这个Entry的插入位置,下标为Index。即index=hash(“java”),存入数组下标为Index的地方,但是因为hashmap长度有限,插入的Entry越来越多,Index值会发生冲突,此时可以用链表的方式解决,通过头插法,发生冲突时,插入对应的链表之中。2、Get方法的实现原理,比如hashMap.get(“apple”),同样对Key值做一次hash映射,算出其对应的index值,即Index
= Hash(“apple”)这时从头结点开始,一个个向下查找,通过keys.equals()方法去找到链表中正确的节点。
可以简单谈谈,使用 Hashtable 或者 ConcurrentHashMap。它们都可以用于多线程的环境,但是当 Hashtable 的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。而 ConcurrentHashMap 引入了分割(segmentation),不论它变得多么大,仅仅需要锁定 map 的某个部分,而其它的线程不需要等到迭代完成才能访问 map。简而言之,在迭代的过程中,ConcurrentHashMap 仅仅锁定 map 的某个部分,而
Hashtable 则会锁定整个map。在 jdk1.8 之后,取消了segments 的方式,而是使用了transient volatile HashEntry
[] table 的方式保存数据,将数组元素作为锁,对每一行数据进行加锁,减少了并发冲突的概率。由数组+单向链表变为了数组+单向链表+红黑树,将查询的时间复杂度降至了O(logn)改进了一些性能。
HashMap 是无序的,而 LinkedHashMap 是有序的 HashMap,默认为插入顺序,还可以是访问顺序,基本原理是其内部通过 Entry 维护了一个双向链表,负责维护 Map的迭代顺序。甚至可以深入的去谈谈 LinkHashMap 的底层实现机制。
四种主要的垃圾回收算法:新生代:大批对象死去,只有少量存活。①复制算法:把可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块用尽时,把还活着的对象复制到另一块上,再将这块给一次性清理掉。老生代:对象存活率高,只需标记少量回收的对象。②标记-清除算法:首先标记出要回收的对象,然后统一清除带标记的对象。③标记-整理算法:首先标记出要回收的对象,然后进行整理,使得存活的对象都向一端移动,直接清理掉端边界以外的对象。④分代收集算法,集成以上的特点,对新生代和老生代分别使用不同的回收算法。
①校验和:在数据传输的过程中,将发送的数据段当做一个16位整数,将整数加起来。进位补到最后,最后取反,得到校验和。发送方和接收方对比校验和,一致不一定传输成功,但不一致一定失败。②确认应答与序列号:TCP传输时每个字节的数据都编了号,这就是序列号seq。每当接收方接受到数据后,都会对传输方进行确认应答,也就是发送ACK报文。这个ACK报文携带者对应的确认序列号,告诉发送方,接收到了哪些数据,下次从哪里发。③超时重传:两种情况没收到确认应答报文,A.数据丢包,接收方没接到。B.ACK报文丢失。发送方会等待一段时间,没收到ACK的情况下重发刚才发送的数据包,如果是情况A,则接收方接到后回ACK,如果是第二种情况则接收方会发现重复,丢弃数据但仍然发送ACK。④连接管理:三次握手、四次挥手,连接是传输的保证。⑤流量控制:TCP根据接收端对数据的处理能力,决定发送端的发送速度,这个机制就是流量控制。在TCP协议的报头信息中,有一个16位的窗口大小,接收方在发送确认应答ACK时,把自己的即时窗口大小填入,接收方根据这个即时窗口大小决定发送的速度。如果为0,则停止发送,等待一个超时重传的时间,并发送窗口侦测数据段,直到接收端更新这个窗口大小。⑥拥塞控制:如果一开始就发送大量的数据,那么可能刚开始就很拥堵,incident引入了慢启动的方式,先发送少量的数据探路,定义拥塞窗口为1,每次收到ACK就+1,然后两拥塞窗口和接受端的窗口大小进行比对,取较小的值作为实际发送的窗口。
TCP
:传输控制协议,面向连接的,使用全双工的可靠信道,提供可靠的服务,无差错,不丢失,不重复且按序到达。提供拥塞控制、流量控制、超时重发、丢弃重复数据等等可靠性检测手段,面向字节流,仅支持一对一,用于传输可靠性要求高的数据。
UDP
:用户数据报协议,无连接的,使用不可靠信道。尽最大努力交付,不保证可靠交付,无拥塞控制等,面向报文,支持一对一、一对多、多对多的通信,用于传输可靠性要求不高的数据。UDP适合于对网络通讯质量要求不高,要求网络通讯速度尽量快的应用。TCP则适合于对网络通讯质量要求高,且可靠的应用。视频播放分为关键帧和普通帧,且是实时应用,丢失一些普通帧并不会有什么影响,使用UDP更能保证其高效性和实时性。
Get
:当客户端要从服务器中读取某个资源时使用Get,一般用于获取、查询资源信息,Get参数通过URL传递,传递的参数有长度限制,不能用来传递敏感信息。
Post
:当客户端给服务器提供信息较多时可以使用Post,Post附带有用户数据,一般用于更新资源信息,Post将请求参数封装在HTTP请求数据中,可以传输大量数据,传参方式也比Get更安全。
TCP建立连接时为了保证连接的可靠需要进行三次握手。客户端向服务端发送建立连接的SYN报文段,一旦包含SYN报文段的数据到达服务端,服务端从中提取出SYN报文段,为该TCP连接分配需要的缓存和变量。并向客户端发送允许连接的报文段ACK以及报文段SYN,在收到报文段ACK之后,客户端也要给连接分配需要的缓存和变量,再发送一个报文段ACK,表示确认。自此完成TCP连接。由于TCP是全双工的,因此两方向需要单独关闭。客户端发起结束连接的数据段FIN,客户端确认后发送确认数据段ACK。此时结束了客户端-服务端的链接。服务端再向客户端发送数据段FIN和数据段ACK,客户端收到后回复ACK数据段。自此双方的连接正式结束。
啊啊啊讲道理,以上问题都出自同一家公司。主要是他问到了我大学期间哪门课比较擅长,我说是计算机网络。所以可见,你说你擅长的,你就一定要对它很了解。覆盖面要广,深度也要够。不然很容易被考倒,问题真的是一个接一个提出来。
请求报文:<request-line> 请求行<headers> 请求头<blank line> 空格<request-body> 请求数据1.请求行:由请求方法字段、URL字段和HTTP协议版本字段3个字段组成,它们用空格分隔。例如,GET /index.html HTTP/1.1。2.请求头部:由关键字/值对组成,每行一对,关键字和值用英文冒号“:”分隔。常见的请求头:User-Agent:产生请求的浏览器类型。Accept:客户端可识别的内容类型列表。Host:请求的主机名,允许多个域名同处一个IP地址,即虚拟主机。3.空行:最后一个请求头之后是一个空行,发送回车符和换行符,通知服务器以下不再有请求头。4.请求数据:请求数据不在GET方法中使用,而是在POST方法中使用。POST方法适用于需要客户填写表单的场合。与请求数据相关的最常使用的请求头是Content-Type和Content-Length。
响应报文<status-line> 状态行<headers> 消息报头<response-body> 响应正文1.状态行:HTTP-Version Status-Code Reason-Phrase CRLF其中,HTTP-Version表示服务器HTTP协议的版本;Status-Code表示服务器发回的响应状态代码;Reason-Phrase表示状态代码的文本描述。状态码一般由三位数字组成,第一位数字表示相应的类型,常用的五大类型:①1xx:表示服务器已接受了客户端请求,客户端可继续发送请求。②2xx:表示服务器已接受了请求并进行处理。200
OK:表示客户端请求成功。③3xx:表示服务器要求客户端重定向。④4xx:表示客户端的请求有非法内容。400 Bad Request:表示客户端请求有语法错误,不能被服务器理解。401 Unauthonzed:表示请求未经授权,该状态码与WWW-Authenticate报头域一起使用。403 Forbidden:表示服务器接受到请求,但是拒绝提供服务,通常会在响应正文中给出不提供服务的原因。404 Not Found:请求的资源不存在,例如,收到了错误的url。⑤5xx:表示服务器未能正确处理客户端的请求而产生意外错误。500
Internal Server Error:表示服务器发生不可预期的错误,导致无法完成客户端的请求。503 Service Unavailable:表示服务器当前不能处理客户端的请求,在一段时间之后,服务器可能恢复正常。2.消息头部:如 Content-Type: text/html等。3.响应正文
Java 反射机制是在运行状态下,对任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取信息的方式称作反射机制。①Object.getClass() (不适用于int、float等)Car car = new Car();Class clazz = car.getClass();②.class标识 (用于未创建该类的实例时)Class clazz = Car.class;③Class.forName() (安卓中使用@hide注解隐藏起来的类)Class
clazz = Class.forName(com.example.test.Car);
序列化主要有两大接口 Srializable 接口和 Parcelable 接口。序列化将一个对象转换可存储可运输的状态,可以通过网络进行传递,也可以存储到本地。应用场景:需要通过 Intent 和 Binder 等传输类的对象都需要完成对象的序列化过程。①平台不同:S 是 Java 的序列化接口,P 是 Android 的序列化接口②序列化原理不同:S将对象转换成可存储可运输的状态,P将对象分解,分解后的每一个部分都是可传递的数据类型。③优缺点:S简单但是效率低,开销大,序列化、反序列化需要大量的IO操作,P高效但是编写麻烦。④使用场景:S主要用于序列化到存储设备或者通过网络设备传输,P主要用于内存的序列化。
这个问题我确实还是处在知识的盲区,还需要不断学习,这里就推荐一篇文章吧。Java注解相关。https://blog.csdn.net/ClAndEllen/article/details/79392453
五.项目相关问题
以下的问题就是仁者见仁、智者见智了。主要是关于你项目的问题,所以你必须对你简历上的项目负责。保证他不问倒你,如果你也有用到以下的这些框架,或者这些控件。你也可以跟下来继续看看。
ViewPager是一个容器类,直接继承自ViewGroup,ViewPager主要就是根据重写OnInterceptTouchEvent()与onTouchEvent()两个事件分发的函数,而实现的手势滑动。有两种滑动方式:
1、在MOVE触摸事件中,页面随手指的拖动而移动。
2、在UP事件后,页面滑动到指定页面(通过Scroller实现的)
先来看第一种情况,onInterceptTouchEvent()主要作用就是判断各种情况是不是在拖拽,是否要拦截此事件。在MOVE事件中,如果在拖拽,会调用performDrag()方法让当前页面移动。
performDrag()方法做了这么几件事:首先得到ViewPager需要滚动的距离,其次得到边界条件leftBound和rightBound,根据边界条件的约束得到真正的滚动距离,最后调用scrollTo()方法滚动到最终的位置。pageScrolled()简单来说就是根据当前的滑动位置,找到当前的页面信息,然后得到viewpager滑动距离,最后调用了onPageScrolled(currentPage, pageOffset, offsetPixels)。首先获得viewpager滑动过的距离比例,然后通过遍历mItems缓存列表,根据每个缓存页面的offset值得到该页面的左右边界,最后就是判断viewpager滑动过的距离比例在哪一个缓存页面的边界之内,这个缓存页面就是当前显示的页面。而如果viewpager显示区域内存在两个页面显示的时候,从缓存列表的遍历顺序就可以看出,返回的必然是最左边的页面。onPageScrolled()做了三件事:将DecorView显示在屏幕中,不移除屏幕、回调接口onPageScrolled()方法、回调接口的transformPage()方法,自定义实现页面转换动画。 简单总结下,就是在onInterceptTouchEvent()方法中根据不同情况对mIsBeingDragged进行赋值,对触摸事件是否进行拦截;如果在MOVE事件中是可滑动的,就调用performDrag()让视图跟着滑动,当然此方法中是调用scrollTo()方法形成拖拽效果,接着调用pageScrolled()对DecorView固定显示,回调接口,回调转换动画接口。 再来看第二种情况,另外一种移动方式在onTouchEvent()的UP事件中,调用setCurrentItemInternal()对平滑滑动进行处理,通过最后调用smoothScrollTo()方法,利用Scroller达到目的,当然最后也调用了pageScrolled()进行接口的回调等操作,在滑动结束的最后,调用completeScroll(boolean
postEvents)完成滑动结束后的相关清理工作。 情况一:onInterceptTouchEvent()--->赋值mIsBeingDragged,判断是否拦截。--->performDrag()->ScrollTo()->pageScrolled()->onPageScrolled()->DecorView固定显示,回调接口,回调转换动画接口。 情况二:onTouchEvent()->UP事件->计算下一个应该显示的nextPage->setCurrentItemInternal()->smoothScrollTo()方法,利用Scroller达到目的
->startScroll()->computeScroll()不断的重绘->completeScroll()完成滑动结束后的相关清理工作。
两种适配器,FragmentPagerAdapter 和 FragmentStateAdapter。前者类内的每一个生成的 Fragment 都将保存在内存之中,因此适用于那些相对静态的页,数量也比较少的场景。如果需要处理有很多页,并且数据动态性较大、占用内存较多的情况,这时候,就需要用到后者。后者会把已经创建的Fragment进行保存。