前段时间,在我参加一个活动期间,竟然接到阿里的电话面试,我又没刷新简历,也没去看机会,不知是如何有我的信息,难道之前简历投递过会留存在他们系统中?当时也完全没准备,既然是电话面试,那就既来之则安之,试试就试试。
结果当然是不好的, 很多问题都答不来,但我也不气馁,既然自己水平有限,可以抓紧时间去学习,去弥补自己不足。我有个爱好就是喜欢收集,我在事后反思的时候,觉得应该把这些题目给记下来,回去再解答,看看自己到底哪里是有问题,哪方面不足,不足改进就是了。
事后根据回忆,对方问了以下几个问题:
1,假设让你设计一个图片加载器,你会怎么设计?
ImageLoader的工作原理:在显示图片的时候,它会先在内存中查找,如果没有找到,就去本地查找,如果还没有,就开一个新的线程去下载这张图片,下载成功会把图片同时缓存到内存本地去。
Freso的原理:设计一个Image Pipeline的概念,它负责先后检查内存,磁盘文件,如果都没有就老老实实从网络下载图片。
大致流程如下:
- 检查内存缓存,如有,返回
- 后台线程开始后续工作
- 检查是否在未解码内存缓存中。如有,解码,变换,返回,然后缓存到内存缓存中。
- 检查是否在磁盘缓存中,如果有,变换,返回。缓存到未解码缓存和内存缓存中。
- 从网络或者本地加载。加载完成后,解码,变换,返回。存到各个缓存中。
2,简单讲讲Activity、Window、View三者的关系?
观察Activity源码:Activity.attch() -> PolicyManager -> Policy -> PhoneWindow -> mLayoutInflater.inflate()&mContentParent.addView()
这只是一个简单的跟踪过程描述。通过跟踪源代码,就可以很清晰的看出他们三者的关系。
Activity是整个模型的控制单元,Window属于承载模型,负责承载视图,View是视图显示模型。
View是Android中的视图呈现方式,但是View不能单独存在,它必须附着在Window这个抽象的概念上面,因此有视图的地方就有Window。那些地方有视图呢?Android中可以提供视图的地方有Activity,DIalog,Toast,除此之外,还有一些依托Window而实现的视图,比如PopupWindow,Meun,他们也是视图,有视图的地方就有Window,因此Activity,Dialog,Toast等视图都对应着一个Window。
那View是怎样绑定在Window上的呢?还要介绍下Window和View之间的纽带:ViewRoot。ViewRoot对应于ViewRootImpl类,它是连接WindowManager和Decorview的纽带,View的三大流程(measure,layout,draw)均是通过ViewRoot来完成的。在ActivityThread中,当Activity对象被创建完毕后,会将DecorVidew添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。
一个比喻总结下Activity Window View三只之间的关系:Activity像一个工匠(控制单元),Window像窗户(承载模型),View像窗花(显示视图)。
1)一个Activity构造的时候会初始化一个Window,准确的说是PhoneWindow。
2)这个PhoneWindow有一个“ViewRoot”,引号是说其实这个“ViewRoot”是一个View或者说ViewGroup,是最初始的根视图。
3)“ViewRoot”通过addView方法来一个个的添加View。比如TextView,Button等
4)这些View的事件监听,是由WindowManagerService来接受消息,并且回调Activity函数。比如onClickListener,onKeyDown等。
3,简单讲讲Fragment的生命周期?
这张图是Fragment生命周期和Activity生命周期对比图,可以看到两者还是有很多相似的地方,比如都有onCreate(),onStart(),onPause(),onDestroy()等等,因为Fragment是被托管到Activity中的,所以多了两个onAttach()和onDetach()。这里讲讲与Activity生命周期不一样的方法。
onAttach()
Fragment和Activity建立关联的时候调用,被附加到Activity中去。
onCreate()
系统会在创建Fragment时调用此方法。可以初始化一段资源文件等等。
onCreateView()
系统会在Fragment首次绘制其用户界面时调用此方法。 要想为Fragment绘制 UI,从该方法中返回的 View 必须是Fragment布局的根视图。如果Fragment未提供 UI,您可以返回 null。
onViewCreated()
在Fragment被绘制后,调用此方法,可以初始化控件资源。
onActivityCreated()
当onCreate(),onCreateView(),onViewCreated()方法执行完后调用,也就是Activity被渲染绘制出来后。
onPause()
系统将此方法作为用户离开Fragment的第一个信号(但并不总是意味着此Fragment会被销毁)进行调用。 通常可以在此方法内确认在当前用户会话结束后仍然有效的任何更改(因为用户可能不会返回)。
onDestroyView()
Fragment中的布局被移除时调用。
onDetach()
Fragment和Activity解除关联的时候调用。
但需要注一点是:除了onCreateView,其他的所有方法如果你重写了,必须调用父类对于该方法的实现。
4,自定义View是一种什么样的流程?
当 Activity 接收到焦点的时候,它会被请求绘制布局,该请求由 Android framework 处理.绘制是从根节点开始,对布局树进行 measure 和 draw。整个 View 树的绘图流程在ViewRoot.java类的performTraversals()函数展开,该函数所做 的工作可简单概况为是否需要重新计算视图大小(measure)、是否需要重新安置视图的位置(layout)、以及是否需要重绘(draw),流程图如下:
5,安卓中事件机制如何?
1、基础知识
(1) 所有 Touch 事件都被封装成了 MotionEvent 对象,包括 Touch 的位置、时间、历史记录以及第几个手指(多指触摸)等。
(2) 事件类型分为 ACTION_DOWN, ACTION_UP, ACTION_MOVE, ACTION_POINTER_DOWN, ACTION_POINTER_UP, ACTION_CANCEL,每个事件都是以 ACTION_DOWN 开始 ACTION_UP 结束。
(3) 对事件的处理包括三类,分别为传递——dispatchTouchEvent()函数、拦截——onInterceptTouchEvent()函数、消费——onTouchEvent()函数和 OnTouchListener。
2、传递流程
(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()对事件进行消费。
上面的消费即表示相应函数返回值为 true。
(1) View 不处理事件流程图
(2) View 处理事件流程图
6,安卓的启动模式有了解吗,singleTop,singleTask,singleInstance各在哪些场景使用居多。
- standard:标准模式,系统默认模式。每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否已经存在。在这个模式下,谁启动了Activity,那么这个Activity就运行在启动它的那个Activity所在栈中。
- singleTop:栈顶复用模式。在这种模式下,如果新的Activity已经位于任务栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法会被回调,通过此方法的参数我们可以取出当前的请求信息
- singleTask:栈内复用模式。这是一种单例模式,在这种模式下,只要Activity在一个栈中存在,那么多次启动此Activity都不会创建实例,和singleTop是一样,系统也会调用onNewIntent。还有一点,就是singleTask有clearTop的效果,会导致栈内已有的Activity全部出栈。
- singleInstance:单一实例模式。这是一种加强的singleTask模式,它除了具有singleTask的所有特性以外,还加强了一点,那就是具有此模式的Activity只能单独位于一个任务栈中,比如Activity A是singleInstance模式,当A启动后,系统会为它创建一个新的任务栈,然后A独自在这个新的任务栈中,由于栈内复用的特性,后续均不会创建新的Activity,除非这个独特的任务栈被系统销毁。整个手机操作系统里面只有一个实例存在。不同的应用去打开这个activity 共享公用的同一个activity。他会运行在自己单独,独立的任务栈里面,并且任务栈里面只有他一个实例存在。
standard使用场景:
邮件客户端,在新建一个邮件的时候,适合新建一个新的实例
singleTop使用场景:
消息推送,通知栏弹出Notification,点击Notification跳转到指定Activity,使用singleTop避免生成重复的页面。
登录的时候,登录成功跳转到主页,按下两次登录按钮,使用singleTask避免生成两个主页。
从activity A启动了个service进行耗时操作,或者某种监听,这个时候你home键了,service收集到信息,要返回activityA。
singleTask使用场景:
提供给第三方应用调用的页面,做浏览器、微博之类的应用,浏览器的主界面等等。
程序的主界面,进入多层嵌套之后,一键退回,之前打开的Activity全部出栈。
singleInstance使用场景:
呼叫来电界面,打电话、发短信功能。
闹铃提醒,将闹铃提醒与闹铃设置分离。
7,安卓内存泄露你会怎么处理,如何排查
集合类泄漏
集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。
单例造成的内存泄漏
由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏。
非静态内部类创建静态实例造成的内存泄漏
匿名内部类
Handler 造成的内存泄漏
Handler 的使用造成的内存泄漏问题应该说是最为常见了,很多时候我们为了避免 ANR 而不在主线程进行耗时操作,在处理网络任务或者封装一些请求回调等api都借助Handler来处理,但 Handler 不是万能的,对于 Handler 的使用代码编写一不规范即有可能造成内存泄漏。另外,我们知道 Handler、Message 和 MessageQueue 都是相互关联在一起的,万一Handler发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。
由于 Handler 属于 TLS(Thread Local Storage) 变量, 生命周期和 Activity 是不一致的。因此这种实现方式一般很难保证跟 View 或者 Activity 的生命周期保持一致,故很容易导致无法正确释放。
尽量避免使用 static 成员变量
避免 override finalize()
1、finalize 方法被执行的时间不确定,不能依赖与它来释放紧缺的资源。时间不确定的原因是:
虚拟机调用GC的时间不确定
Finalize daemon线程被调度到的时间不确定
2、finalize 方法只会被执行一次,即使对象被复活,如果已经执行过了 finalize 方法,再次被 GC 时也不会再执行了,原因是:含有 finalize 方法的 object 是在 new 的时候由虚拟机生成了一个 finalize reference 在来引用到该Object的,而在 finalize 方法执行的时候,该 object 所对应的 finalize Reference 会被释放掉,即使在这个时候把该 object 复活(即用强引用引用住该 object ),再第二次被 GC 的时候由于没有了 finalize reference 与之对应,所以 finalize 方法不会再执行。
3、含有Finalize方法的object需要至少经过两轮GC才有可能被释放。
详情见这里 深入分析过dalvik的代码
资源未关闭造成的内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
一些不良代码造成的内存压力
有些代码并不造成内存泄露,但是它们,或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存。
8,反射(Reflection)和注解(Annotation )的作用,知道其中的原理吗,讲讲。
什么是反射
Java类是被Java虚拟机加载,如果Java类不被Java虚拟机加载,就不能正常运行,正常情况下,我们运行所有程序在编译期时候就已经把那个类被加载了。
Java的反射机制是在编译时并不确定是哪个类被加载了,而是在程序运行的时候才被加载、探知、自审。使用的是在编译期并不知道的类,这就是Java反射的特点。
反射的作用
Java反射机制它知道类的基本结构,这种对Java类结构探知的能力,我们称为Java类的“自审”。如eclipse中,一按点,编译工具就会自动的把该对象能够使用的所有的方法和属性全部都列出来,供用户进行选择,这就是利用反射机制,做到代码的智能提示。
举个例子:假如有两个程序员,一个程序员在写程序的时需要使用第二个程序员所写的类,但第二个程序员并没完成他所写的类。那么第一个程序员的代码是不能通过编译的。此时,利用Java反射的机制,就可以让第一个程序员在没有得到第二个程序员所写的类的时候,来完成自身代码的编译。
反射使用场景
1.工厂模式:Factory类中用反射的话,添加了一个新的类之后,就不需要再修改工厂类Factory了
2.数据库JDBC中通过Class.forName(Driver).来获得数据库连接驱动
3.分析类文件:毕竟能得到类中的方法等等
4.访问一些不能访问的变量或属性:破解别人代码
什么是注解
注解是 Java 5 的一个新特性。注解是插入你代码中的一种注释或者说是一种元数据(meta data)。这些注解信息可以在编译期使用预编译工具进行处理(pre-compiler tools),也可以在运行期使用 Java反射机制进行处理。
Annotation 对程式运行没有影响,它的目的在对编译器或分析工具说明程式的某些资讯,您可以在包、类、方法、成员等加上Annotation,每一个Annotation 对应于一个实际的Annotation 型态,您可以从java.lang.Override、java.lang.Deprecated、java.lang.SuppressWarnings 这三个J2SE 5.0 中标准的Annotation 型态开始了解Annotation 的作用。
限定Override 父类方法@Override
java.lang.Override 是J2SE 5.0 中标准的Annotation 型态之一,它对编译器说明某个方法必须是重新定义父类别中的方法,编译器得知这项资讯后,在编译程式时如果发现被@Override 标示的方法并非重新定义父类别中的方法,就会回报错误。
java.lang.Override 是个Marker annotation,简单的说就是用于标示的Annotation,Annotation 名称本身即表示了要给工具程式的资讯,例如Override 这个名称告知编译器,被@Override 标示的方法必须是重新定义父类别中的同名方法。
标示方法为Deprecated @Deprecated
java.lang.Deprecated 也是个Marker annotation,简单的说就是用于标示,Annotation 名称本身即包括了要给工具程式的资讯,例如Deprecated 这个名称在告知编译器,被@Deprecated 标示的方法是一个不建议被使用的方法,如果有开发人员不小心使用了被@Deprecated 标示的方法,编译器要提出提示提醒开发人员。
抑制编译器提示@SuppressWarnings
java.lang.SuppressWarnings 是J2SE 5.0 中标准的Annotation 型态之一,它对编译器说明某个方法中若有提示讯息,则加以抑制,不用在编译完成后出现提示,不过事实上这个功能在Sun JDK 5.0 中没有实现出来。
注解使用场景
1,设计一个原始码分析工具,分析代码等
2,日志信息打
9,进程和线程的关系,在Android中activity A和activity B这两者是不是在同一个线程中
进程一般是指一个执行单元,在PC和移动设备上指的是一个程序或者一个应用,一个进程可以包含多个线程,因此进程和线程是包含和被包含的关系,最简单的情况下,一个进程中可以只有一个线程,即主线程,在Android中主线程也叫做UI线程,只有在UI线程中才能操作界面元素,但不能执行耗时操作任务。
当某个应用组件启动且该应用没有运行其他任何组件时,Android 系统会使用单个执行线程为应用启动新的 Linux 进程。默认情况下,同一应用的所有组件在相同的进程和线程(称为“主”线程)中运行。 如果某个应用组件启动且该应用已存在进程(因为存在该应用的其他组件),则该组件会在此进程内启动并使用相同的执行线程。 但是,您可以安排应用中的其他组件在单独的进程中运行,并为任何进程创建额外的线程。
进程
Android 系统将尽量长时间地保持应用进程,但为了新建进程或运行更重要的进程,最终需要移除旧进程来回收内存。 为了确定保留或终止哪些进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。 必要时,系统会首先消除重要性最低的进程,然后是重要性略逊的进程,依此类推,以回收系统资源。
线程
应用启动时,系统会为应用创建一个名为“主线程”的执行线程。 此线程非常重要,因为它负责将事件分派给相应的用户界面小部件,其中包括绘图事件。 此外,它也是应用与 Android UI 工具包组件(来自 android.widget 和 android.view 软件包的组件)进行交互的线程。因此,主线程有时也称为 UI 线程。
系统不会为每个组件实例创建单独的线程。运行于同一进程的所有组件均在 UI 线程中实例化,并且对每个组件的系统调用均由该线程进行分派。 因此,响应系统回调的方法(例如,报告用户操作的 onKeyDown() 或生命周期回调方法)始终在进程的 UI 线程中运行。
例如,当用户触摸屏幕上的按钮时,应用的 UI 线程会将触摸事件分派给小部件,而小部件反过来又设置其按下状态,并将失效请求发布到事件队列中。 UI 线程从队列中取消该请求并通知小部件应该重绘自身。
activity A和activity B这两者是在同一个线程,因为都需要被加载到UI主线程中,是四大组件之一。
10,hashmap的原理如何。hashcode和equal这两者有什么区别吗
1. 什么时候会使用HashMap?他有什么特点?
是基于Map接口的实现,存储键值对时,它可以接收null的键值,是非同步的,HashMap存储着Entry(hash, key, value, next)对象。
2. 你知道HashMap的工作原理吗?
通过hash的方法,通过put和get存储和获取对象。存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Facotr则resize为原来的2倍)。获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。
3. 你知道get和put的原理吗?equals()和hashCode()的都有什么作用?
通过对key的hashCode()进行hashing,并计算下标( n-1 & hash),从而获得buckets的位置。如果产生碰撞,则利用key.equals()方法去链表或树中去查找对应的节点
4. 你知道hash的实现吗?为什么要这样实现?
在Java 1.8的实现中,是通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度、功效、质量来考虑的,这么做可以在bucket的n比较小的时候,也能保证考虑到高低bit都参与到hash的计算中,同时不会有太大的开销。
5. 如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?
如果超过了负载因子(默认0.75),则会重新resize一个原来长度两倍的HashMap,并且重新调用hash方法。
**为什么String, Interger这样的wrapper类适合作为键? **
String, Interger这样的wrapper类作为HashMap的键是再适合不过了,而且String最为常用。因为String是不可变的,也是final的,而且已经重写了equals()和hashCode()方法了。其他的wrapper类也有这个特点。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。不可变性还有其他的优点如线程安全。如果你可以仅仅通过将某个field声明成final就能保证hashCode是不变的,那么请这么做吧。因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这样就能提高HashMap的性能。
我们可以使用自定义的对象作为键吗?
这是前一个问题的延伸。当然你可能使用任何对象作为键,只要它遵守了equals()和hashCode()方法的定义规则,并且当对象插入到Map中之后将不会再改变了。如果这个自定义对象时不可变的,那么它已经满足了作为键的条件,因为当它创建之后就已经不能改变了。
我们可以使用CocurrentHashMap来代替Hashtable吗?
这是另外一个很热门的面试题,因为ConcurrentHashMap越来越多人用了。我们知道Hashtable是synchronized的,但是ConcurrentHashMap同步性能更好,因为它仅仅根据同步级别对map的一部分进行上锁。ConcurrentHashMap当然可以代替HashTable,但是HashTable提供更强的线程安全性。看看这篇博客查看Hashtable和ConcurrentHashMap的区别。
hashcode和equal这两者有什么区别?
在Java中任何一个对象都具备equals(Object obj)和hashcode()这两个方法,因为他们是在Object类中定义的。
equals(Object obj)方法用来判断两个对象是否“相同”,如果“相同”则返回true,否则返回false。
hashcode()方法返回一个int数,在Object类中的默认实现是“将该对象的内部地址转换成一个整数返回”。
1、如果两个对象equals,Java运行时环境会认为他们的hashcode一定相等。
2、如果两个对象不equals,他们的hashcode有可能相等。
3、如果两个对象hashcode相等,他们不一定equals。
4、如果两个对象hashcode不相等,他们一定不equals。
11,讲讲设计模式六大原则
- 单一原则。就是说一个类只有一个明确的职责,不易过多职责封装在一个类里面。
- 开闭原则。对于修改是关闭的,对于扩展的开放的,这样主要目的是为了提高可扩展性。
- 依赖倒置原则。高层不依赖于低层,两者都依赖于抽象,抽象不依赖于细节实现,具体实现依赖于抽象,也就是说要面向接口编程。
- 里氏替换原则。也就说子类运行的功能,父类也能运行,强调继承的重要性
- 迪米特原则。一个类要了解另外一个类最少的内容,强调低耦合,耦合分解
- 接口隔离原则。一个类不要继承不需要的接口,接口可拆分,不要冗余在一个总接口中,实现自己所需接口即可。
12,动态代理有用过吗,用过的话,你觉得在哪些场景比较合适。
代理:设计模式
代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。Java 动态代理机制以巧妙的方式近乎完美地实践了代理模式的设计理念。
代理的实现分为:
- 静态代理:代理类是在编译时就实现好的。也就是说 Java 编译完成后代理类是一个实际的 class 文件。
- 动态代理:代理类是在运行时生成的。也就是说 Java 编译完之后并没有实际的 class 文件,而是在运行时动态生成的类字节码,并加载到JVM中。
一个典型的动态代理创建对象过程可分为以下四个步骤:
1、通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandler handler = new InvocationHandlerImpl(...);
2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});
3、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型
Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
4、通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入
Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
为了简化对象创建过程,Proxy类中的newInstance方法封装了2~4,只需两步即可完成代理对象的创建。
生成的ProxySubject继承Proxy类实现Subject接口,实现的Subject的方法实际调用处理器的invoke方法,而invoke方法利用反射调用的是被代理对象的的方法(Object result=method.invoke(proxied,args))
Java 实现动态代理的缺点:因为 Java 的单继承特性(每个代理类都继承了 Proxy 类),只能针对接口创建代理类,不能针对类创建代理类。
13,有什么擅长的脚本语言吗,比如Python、Ruby
这里可以自己回答了,说擅长的语言即可。
14,了解JVM吗,讲讲Java内存机制
Java 虚拟机(Java virtual machine,JVM)是运行 Java 程序必不可少的机制。JVM实现了Java语言最重要的特征:即平台无关性。
编译后的 Java 程序指令并不直接在硬件系统的 CPU 上执行,而是由 JVM 执行。JVM屏蔽了与具体平台相关的信息,使Java语言编译程序只需要生成在JVM上运行的目标字节码(.class),就可以在多种平台上不加修改地运行。Java 虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。因此实现java平台无关性。它是 Java 程序能在多平台间进行无缝移植的可靠保证,同时也是 Java 程序的安全检验引擎(还进行安全检查)。
15,你做过最有成就感的事是什么,解决什么样的问题
大家可以讲讲平时在工作中解决了什么样的问题, 碰到什么困难,又是如何解决的,让你印象深刻的几个点就可以了。
16,安卓中消息循环机制如何?
Handler 、 Looper 、Message 这三者都与Android异步消息处理线程相关的概念。那么什么叫异步消息处理线程呢?异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数,执行完成一个消息后则继续循环。若消息队列为空,线程则会阻塞等待。
那么Android消息机制主要是指Handler的运行机制,Handler运行需要底层的MessageQueue和Looper支撑。其中MessageQueue采用的是单链表的结构,Looper可以叫做消息循环。由于MessageQueue只是一个消息存储单元,不能去处理消息,而Looper就是专门来处理消息的,Looper会以无限循环的形式去查找是否有新消息,如果有的话,就处理,否则就一直等待着。
Handler创建的时候会采用当前线程的Looper来构造消息循环系统,需要注意的是,线程默认是没有Looper的,如果需要使用Handler就必须为线程创建Looper,因为默认的UI主线程,也就是ActivityThread,ActivityThread被创建的时候就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。
最后,希望本文能对正在找工作的小伙伴提供一点点帮助吧。