转载自:http://blog.csdn.net/hubert_bing/article/details/69964740
Android 名企面试题及涉及知识点整理 http://www.jianshu.com/p/735be5ece9e8
http://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649548612&idx=1&sn=8e46b6dd47bd8577a5f7098aa0889098&chksm=f1180c39c66f852fd955a29a9cb4ffa9dc4d528cab524059bcabaf37954fa3f04bc52c41dae8&scene=21#wechat_redirect
安卓View绘制流程
事件分发机制 JAVA基础思想 多线程和安全问题 安卓性能优化和兼容问题 再问一下常规的组件相关问题
onStart()是activity界面被显示出来的时候执行的,但不能与它交互; onResume()是当该activity与用户能进行交互时被执行,用户可以获得activity的焦点,能够与用户交互。
在onCreate()方法中直接finish或者出现为捕获的异常时。
[干货]让你彻底搞懂Context到底是什么,如果没弄明白,还怎么做Android开发?
在onCreate()方法中直接finish或者出现为捕获的异常时。
Activity: 处理与UI相关的事件,呈现界面给用户并响应用户的请求 Service: 后台服务,一般用于耗时操作,在后台和长时间运行 BoadcastReceiver: 接收广播事件并对事伯点击进行处理,如当收到短信时系统会发现短信到来的广播,能够处理该广播的BoadcastReceiver就会根据自己需要进进处理 ContentProvider: 存储、处理数据并提供给外界一致的处理接口 MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范。用户与视图交互,视图接爱并反馈用户的动作,同时把用户的请求传给控制器,由控制器调用模型进行加工处理,模型把处理后的数据返回给控制器,由控制器调用视图格式化和渲染返回的数据。android中的activity是一个controller,对应的layout.xml则是视图,具体的功能则是model。
sleep()是Thread类的方法,必须传一个long类型的时间,在到指定时间前让出cpu给其他线程,但不释放锁,指定的时间到了又会自动恢复运行状态。 wait()是Object类的方法,可以不传参,当在线程中调用wait()方法时,线程会放弃对象锁进入等待,直到此对象调用notify()、notifyAll()方法或到指定时间,本线程才进入准备阶段,进而获取对象锁进入运行状态。
都不能被实例化。 interface需要实现,要用implements,而abstract class需要继承,要用extend。 一个类可以实现多个interface,但一个类只能继承一个abstract class。 interface强调特定功能的实现,而abstract class强调所属关系。 abstract的成员修饰符可以自定义,可以有方法体,抽象方法需要用abstract修饰。 interface的成员修饰符只能是public,不能定义方法体和声明实例变量,可以声明常量变量。
array是数组,大小固定,不可增删。 arrayList是集合,大小不固定,可以增删。 list是接口,定义list集合框架的通用方法。
hashTable是线程安全的,hashMap是线程不安全的 HashTable不允许null值的存在,HashMap允许null值的存在 HashTable是直接使用对象的哈希值,HashMap是使用处理过的哈希值 hashMap是数组+链表的数据结构。 put()的工作原理: a、先获取key对象的hashcode值进行处理。
static int secondaryHash(Object key) { int hash = key.hashCode(); hash ^= (hash >>> 20) ^ (hash >>> 12); hash ^= (hash >>> 7) ^ (hash >>> 4); return hash; } b、将处理后的hashcode对table的length-1进行取余获得index即在数组中的索引位置。 c、然后对该位置的Entry进行判断,若该位置为空,那么插入新的Entry。 d、若当前Entry不为空,那么根据key.equals()对该链表进行遍历,若是该key对象存在,则用新值代替旧值,否则在链表尾端插入新的Entry。public V put(K key, V value) { if (key == null) { return putValueForNullKey(value); } int hash = secondaryHash(key); HashMapEntry<K, V>[] tab = table; int index = hash & (tab.length - 1); for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) { if (e.hash == hash && key.equals(e.key)) { preModify(e); V oldValue = e.value; e.value = value; return oldValue; } } // No entry for (non-null) key is present; create one modCount++; if (size++ > threshold) { tab = doubleCapacity(); index = hash & (tab.length - 1); } addNewEntry(key, value, hash, index); return null; }
get(key)的工作原理 跟put()里面的原理大致相同,根据key.hashcode()值找到相应的index,再根据key.equals()遍历该Index中的Entry链表,找到则返回对应的value,否则返回null。
public V get(Object key) { if (key == null) { HashMapEntry<K, V> e = entryForNullKey; return e == null ? null : e.value; } int hash = key.hashCode(); hash ^= (hash >>> 20) ^ (hash >>> 12); hash ^= (hash >>> 7) ^ (hash >>> 4); HashMapEntry<K, V>[] tab = table; for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)]; e != null; e = e.next) { K eKey = e.key; if (eKey == key || (e.hash == hash && key.equals(eKey))) { return e.value; } } return null; }
其他方法也是类似的。
StringBuilder是“外人”所以他使用的是copy的方法,而String内部的方法则调用了一个本地的构造函数,它返回的依然是它自己,只是修改了offset! 因此Stringbuilder中的subString在高并发系统里性能会差一些因为他会多分配对象,特别是当你反复使用subString方法的时候一定要记得使用String对象。
单例 减少创建Java实例所带来的系统开销
Android子线程真的不能更新UI么
ANR的全称是application not responding,意思就是程序未响应。出现ANR的三种情况: a.主线程对输入事件5秒内没有处理完毕 b.主线程在执行BroadcastReceiver的onReceive()函数时10秒内没有处理完毕 c.主线程在Service的各个生命周期函数时20秒内没有处理完毕。 那么导致ANR的根本原因是什么呢?简单的总结有以下两点: 1.主线程执行了耗时操作,比如数据库操作或网络编程 2.其他进程(就是其他程序)占用CPU导致本进程得不到CPU时间片,比如其他进程的频繁读写操作可能会导致这个问题。
解决方案: 1.避免在主线程执行耗时操作,所有耗时操作应新开一个子线程完成,然后再在主线程更新UI。 2.BroadcastReceiver要执行耗时操作时应启动一个service,将耗时操作交给service来完成。 3.避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广 播时需要向用户展示什么,你应该使用Notification Manager来实现。
搜索算法: 1.引用计数算法(废弃) 实现简单,效率高,但不能解决循环引用问题,同时计数器的增加和减少带来额外开销,JDK1.1以后废弃了。 2.根搜索算法
根搜索算法是通过一些“GC Roots”对象作为起点,从这些节点开始往下搜索,搜索通过的路径成为引用链(Reference Chain),当一个对象没有被GC Roots 的引用链连接的时候,说明这个对象是不可用的。 回收算法: 1.标记—清除算法(Mark-Sweep) 在标记阶段,确定所有要回收的对象,并做标记。清除阶段紧随标记阶段,将标记阶段确定不可用的对象清除。标记—清除算法是基础的收集算法,标记和清除阶段的效率不高,而且清除后回产生大量的不连续空间,这样当程序需要分配大内存对象时,可能无法找到足够的连续空间。 2.复制算法(Copying): 复制算法是把内存分成大小相等的两块,每次使用其中一块,当垃圾回收的时候,把存活的对象复制到另一块上,然后把这块内存整个清理掉。复制算法实现简单,运行效率高,但是由于每次只能使用其中的一半,造成内存的利用率不高。现在的JVM 用复制方法收集新生代,由于新生代中大部分对象(98%)都是朝生夕死的,所以两块内存的比例不是1:1(大概是8:1)。 3.标记—整理算法(Mark-Compact) 标记—整理算法和复制算法一样,但是标记—整理算法不是把存活对象复制到另一块内存,而是把存活对象往内存的一端移动,然后直接回收边界以外的内存。标记—整理算法提高了内存的利用率,并且它适合在收集对象存活时间较长的老年代。 4.分代收集(Generational Collection) 分代收集是根据对象的存活时间把内存分为新生代和老年代,根据各个代对象的存活特点,每个代采用不同的垃圾回收算法。新生代采用复制算法,老年代采用标记—整理算法。垃圾算法的实现涉及大量的程序细节,而且不同的虚拟机平台实现的方法也各不相同。
Anroid性能优化系列——Improving Layout Performance(一) Anroid性能优化系列——Improving Layout Performance(二) Anroid性能优化系列——Improving Layout Performance(三) 性能优化之布局优化 Android布局优化之实用技巧 Android 高效布局的几点建议
Google I/O 2016 上发布的 ConstraintLayout是什么东东?Android Layout新世界 Android UI布局问题总结 Android中RelativeLayout和LinearLayout性能分析 Android布局优化之过度绘制 http://www.jianshu.com/p/145fc61011cd
1.主线程是android系统创建并维护的,在创建的时候执行了Looper.prepare(),该方法先是new了一个Looper对象,在私有的构造方法中又创建了MessageQueue作为此Looper对象的成员变量,Looper对象通过ThreadLocal绑定MainThread中; 2.当我们创建Handler子类对象时,在构造方法中通过ThreadLocal获取绑定的Looper对象,并获取此Looper对象的成员变量MessageQueue作为该Handler对象的成员变量; 3.在子线程中调用上一步创建的Handler子类对象的sendMesage(msg)方法时,在该方法中将msg的target属性设置为自己本身,同时调用成员变量MessageQueue对象的enqueueMessag()方法将msg放入MessageQueue中; 4.主线程创建好之后,会执行Looper.loop()方法,该方法中获取与线程绑定的Looper对象,继而获取该Looper对象的成员变量MessageQueue对象,并开启一个会阻塞(不占用资源)的死循环,只要MessageQueue中有msg,就会获取该msg,并执行msg.target.dispatchMessage(msg)方法(msg.target即上一步引用的handler对象),此方法中调用了我们第二步创建handler子类对象时覆写的handleMessage()方法,之后将该msg对象存入回收池;
ps:每个线程只能有一个Looper对象。
ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。
handler.post(Runnable r) handler.postDelay(Runnable r) handler.sendMessage(Message msg) handler.sendMessageDelayed(Message msg, long delayMillis) 最终都会调用sendMessageDelayed方法,post方法中会把Runnable对象通过getPostMessage()方法变成message对象:private final Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
handler所有发送message的方法:
1) 发送者的身份认证 由于开发商可能通过使用相同的 Package Name 来混淆替换已经安装的程序,以此保证签名不同的包不被替换 2) 保证信息传输的完整性 签名对于包中的每个文件进行处理,以此确保包中内容不被替换 3) 防止交易中的抵赖发生, Market 对软件的要求 给apk签名可以带来以下好处: 1. 应用程序升级:如果你希望用户无缝升级到新的版本,那么你必须用同一个证书进行签名。这是由于只有以同一个证书签名,系统才会允许安装升级的应用程序。如果你采用了不同的证书,那么系统会要求你的应用程序采用不同的包名称,在这种情况下相当于安装了一个全新的应用程序。如果想升级应用程序,签名证书要相同,包名称要相同! 2.应用程序模块化:Android系统可以允许同一个证书签名的多个应用程序在一个进程里运行,系统实际把他们作为一个单个的应用程序,此时就可以把我们的应用程序以模块的方式进行部署,而用户可以独立的升级其中的一个模块 3.代码或者数据共享:Android提供了基于签名的权限机制,那么一个应用程序就可以为另一个以相同证书签名的应用程序公开自己的功能。以同一个证书对多个应用程序进行签名,利用基于签名的权限检查,你就可以在应用程序间以安全的方式共享代码和数据了。 不同的应用程序之间,想共享数据,或者共享代码,那么要让他们运行在同一个进程中,而且要让他们用相同的证书签名。
15 你觉得安卓开发最关键的技术在哪里?
搜索java面试宝典和安卓面试宝典
2 多线程多点下载的过程
3 http协议的理解和用法
4 安卓解决线程并发问题
5 你知道的数据结构有哪些,说下具体实现机制
7 谈下对Java OOP中多态的理解
8 activty和Fragmengt之间怎么通信,Fragmengt和Fragmengt怎么通信
一、onStartCommand方法,返回START_STICKY 在运行onStartCommand后service进程被kill后,那将保留在开始状态,但是不保留那些传入的intent。不久后service就会再次尝试重新创建,因为保留在开始状态,在创建 service后将保证调用onstartCommand。如果没有传递任何开始命令给service,那将获取到null的intent。 【结论】 手动返回START_STICKY,亲测当service因内存不足被kill,当内存又有的时候,service又被重新创建,比较不错,但是不能保证任何情况下都被重建,比如进程被干掉了…. 二、提升service优先级 在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = “1000”这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播。 三、提升service进程优先级 Android中的进程是托管的,当系统进程空间紧张的时候,会依照优先级自动进行进程的回收。Android将进程分为6个等级,它们按优先级顺序由高到低依次是: 1.前台进程( FOREGROUND_APP) 2.可视进程(VISIBLE_APP ) 3. 次要服务进程(SECONDARY_SERVER ) 4.后台进程 (HIDDEN_APP) 5.内容供应节点(CONTENT_PROVIDER) 6.空进程(EMPTY_APP) 当service运行在低内存的环境时,将会kill掉一些存在的进程。因此进程的优先级将会很重要,可以使用startForeground 将service放到前台状态。这样在低内存时被kill的几率会低一些。
在onStartCommand方法内添加如下代码:Notification notification = new Notification(R.drawable.ic_launcher, getString(R.string.app_name), System.currentTimeMillis()); PendingIntent pendingintent = PendingIntent.getActivity(this, 0, new Intent(this, AppMain.class), 0); notification.setLatestEventInfo(this, "uploadservice", "请保持程序在后台运行", pendingintent); startForeground(0x111, notification);
注意在onDestroy里还需要stopForeground(true),运行时在下拉列表会看到自己的APP在: 【结论】如果在极度极度低内存的压力下,该service还是会被kill掉,并且不一定会restart 四、onDestroy方法里重启service 直接在onDestroy()里startService 或 service +broadcast 方式,就是当service走ondestory的时候,发送一个自定义的广播,当收到广播的时候,重新启动service; 【结论】当使用类似口口管家等第三方应用或是在setting里-应用-强制停止时,APP进程可能就直接被干掉了,onDestroy方法都进不来,所以还是无法保证~.~ 五、监听系统广播判断Service状态 通过系统的一些广播,比如:手机重启、界面唤醒、应用状态改变等等监听并捕获到,然后判断我们的Service是否还存活,别忘记加权限啊。 六、将APK安装到/system/app,变身系统级应用 大招: 放一个像素在前台(手机QQ)
可以熟说下svn和git的细节,和代码规范问题,和一些安全信息的问题等
xml定义应该指的是组合已有的控件达到某种效果。自定义view效率更高。
自定义View 减少了ViewGroup与View之间的测量,包括父量子,子量自身,子在父中位置摆放,当子view变化时,父的某些属性都会跟着变化. 效率差别在自定义View只有一个view的 onMeasure,onLayout,onDraw,而xml则会使多个View的.
静态注册:在AndroidManifest.xml文件中进行注册,当App退出后,Receiver仍然可以接收到广播并且进行相应的处理,但是会耗费cpu、电量等资源。 动态注册:在代码中动态注册,当App退出后,也就没办法再接受广播了,优先级高于静态注册。
Service有两种启动方法 1.在Context中通过public boolean bindService(Intent service,ServiceConnection conn,int flags) 方法来进行Service与Context的关联并启动,并且Service的生命周期依附于Context。 2.通过public ComponentName startService(Intent service)方法去启动一个Service,此时Service的生命周期与启动它的Context无关。
onStartCommand()方法必须返回一个整数。这个整数是描述系统在杀死服务之后应该如何继续运行。onStartCommand()的返回值必须是以下常量之一: START_NOT_STICKY 如果系统在onStartCommand()返回后杀死了服务,则不会重建服务了,除非还存在未发送的intent。 当服务不再是必需的,并且应用程序能够简单地重启那些未完成的工作时,这是避免服务运行的最安全的选项。 START_STICKY 如果系统在onStartCommand()返回后杀死了服务,则将重建服务并调用onStartCommand(),但不会再次送入上一个intent, 而是用null intent来调用onStartCommand() 。除非还有启动服务的intent未发送完,那么这些剩下的intent会继续发送。 这适用于媒体播放器(或类似服务),它们不执行命令,但需要一直运行并随时待命。 START_REDELIVER_INTENT 如果系统在onStartCommand()返回后杀死了服务,则将重建服务并用上一个已送过的intent调用onStartCommand()。任何未发送完的intent也都会依次送入。这适用于那些需要立即恢复工作的活跃服务,比如下载文件。
15 布局优化主要哪些?具体优化?
16 数据库的知识,包括本地数据库优化点。
1 安卓事件分发机制,请详细说下整个流程
view的绘制从ViewRoot的performTraversals方法开始,依次调用performMeasure、performLayout、和performDraw三个方法,其中performMeasure方法会调用measure方法,measure方法会调用onMeasure方法,在onMeasure方法中则会对所有的子元素进行measure,这个时候measure就从父容器传递到了子元素中,完成了依次measure过程,子元素会重复父容器的measure过程,完成整个view树的遍历。performLayout和performDraw与measure过程类似,唯一不同是performDraw的传递过程是在draw方法中通过dispatchDraw来实现。 MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize,用于决定一个view的尺寸规格。 SpecMode有三种:
UNSPECIFIED 父容器不对view有任何限制。 EXACTLY 父容器已经检测出view所需的精确大小,这时候view的最终大小SpecSize所指定的值,相当于match_parent或指定具体数值。 AT_MOST 父容器指定一个可用大小即SpecSize,view的大小不能大于这个值,具体多大要看view的具体实现,相当于wrap_content。 给view设置的LayoutParams会在父容器的约束下转换成对应MeasureSpec,然后根据这个MeasureSpec来确定view测量后的尺寸
无论是startactivity还是launcher启动activity,最终都是调用startActivityForResult,通过binder进程间通信进入到ActivityManagerService进程中,(启动Activity的真正实现是由ActivityManagerNative.getDefault()的startActivity方法实现,而ActivityManagerService继承了ActivityManagerNative,ActivityManagerNative继承自Binder实现了IActivityManager这个Binder接口,所以AMS是IActivityManager的Binder实现类。 在ActivityManagerService中调用startActivity方法,在这个方法中,Activity的启动转移到了ActivityStackSupervisor的startActivityMayWait方法,在 这个方法中又调用了startActivityLocked方法,接着在startActivityLocked方法中调用了startActivityUncheckedLocked方法,然后在startActivityUncheckedLocked 这个方法中调用了ActivityStack的resumeTopActivitiesLocked方法,接着在resumeTopActivitiesLocked中调用了resumeTopActivitiesInnerLocked方法,在 resumeTopActivitiesInnerLocked方法中又调用了ActivityStackSupervisor的startSpecificActivityLocked方法,然后在startSpecificActivityLocked中调用了 realStartActivityLocked方法,然后通知ApplicationThread要进行Activity启动调度了,ApplicationThread并不执行真正的启动操作,它通过调用 ActivityManagerService.activityPaused接口进入到ActivityManagerService进程中,看看是否需要创建新的进程来启动Activity,如果是桌面点击图标启动, 会调用startProcessLocked来创建一个新的进程,而如果是Activity内部调用则不会进行这一步,因为新的Activity就在原来的Activity
所在的进程中进行启动, 然后在ApplicationThread中执行scheduleLaunchActivity方法,发送一个启动Activity的消息交由Handler(名字是’H’)处理,在对消息处理后最终由 performLaunchActivity方法完成Activity对象的创建和启动,并且ActivityThread通过handleResumeActivity方法来调用被启动的Activity的onResume 这一生命周期方法.
4 安卓采用自动垃圾回收机制,请说下安卓内存管理的原理 GC_CONCURRENT 内存将满时触发的并发垃圾回收,paused time基本在10ms以内 GC_FOR_ALLOC 内存已满时尝试分配内存失败时触发,系统会较长时间暂停应用进行内存回收,paused time都在50ms以上,经常为100ms左右 GC_HPROF_DUMP_HEAP 创建HPROF内存分析文件时触发 GC_EXPLICIT 显示调用的垃圾收集,比如调用gc() 5 说下安卓虚拟机和java虚拟机的原理和不同点
6 多线程中的安全队列一般通过什么实现?线程池原理?(java)
7 安卓权限管理,为何在清单中注册权限,安卓APP就可以使用,反之不可以(操作系统)
8 socket短线重连怎么实现,心跳机制又是怎样实现,四次握手步骤有哪些(网络通讯原理)
9 http中TCP和UDP有啥区别,说下HTTP请求的IP报文结构(计算机网络) 10 你知道的安全加密有哪些? (如果你说了一个加密,面试官就会接着跟进提问,所以之前你必须要会,不会的话背也要背下来)(安全加密) 11 你知道的数据存储结构?堆栈和链表内部机制。(数据结构)
12 说下Linux进程和线程的区别。进程调度优先级,和cpu调度进程关系。(操作系统)
13 请你详细说下你知道的一种设计模式,并解释下java的高内聚和低耦合。
14 spring 的反射和代理,在安卓中应用场景(插件和ROM数据框架)
15 JNI 调用过程中 混淆问题
16 看过安卓源码吗,请说出一个你看过的API或者组建内部原理。
17 android 5.0 6.0 以及7.0新特性 https://github.com/helen-x/AndroidInterview/blob/master/android/Android5.0、6.0、7.0新特性.md
android中调用webview中的js脚本非常方便,只需要调用webview的loadUrl方法即可(注意开启js支持)
// 启用javascript contentWebView.getSettings().setJavaScriptEnabled(true); // 从assets目录下面的加载html contentWebView.loadUrl("file:///android_asset/wst.html"); // 无参数调用 contentWebView.loadUrl("javascript:javacalljs()"); 1234567webview中js调用本地java方法,这个功能实现起来稍微有点麻烦,不过也不怎么复杂,首先要对webview绑定javascriptInterface,js脚本通过这个接口来调用java代码。
contentWebView.addJavascriptInterface(this, "wst"); javainterface实际就是一个普通的java类,里面是我们本地实现的java代码, 将object 传递给webview,并指定别名,这样js脚本就可以通过我们给的这个别名来调用我们的方法,在上面的代码中,this是实例化的对象,wst是这个对象在js中的别名。 java代码调用js并传递参数 只需要在待用js函数的时候加入参数即可,下面是传递一个参数的情况,需要多个参数的时候自己拼接及行了,注意str类型在传递的时候参数要用单引号括起来 mWebView.loadUrl("javascript:test('" + aa+ "')"); //aa是js的函数test()的参数 js调用java函数并传参,java函数正常书写,在js脚本中调用的时候稍加注意 然后在html页面中,利用如下代码,即可实现调用 <div id='b'><a οnclick="window.wst.clickOnAndroid(2)">b.c</a></div>webView优化
缓存机制(优化二次启动) Web Storage存储机制 优点:有较大存储空间,使用简单 使用场景:临时,简单数据的缓存,Cookies的扩展webView.getSettings.setDomStorageEnabled(true); IndexedDB 数据库的存储机制,android4.4开始加入对IndexedDB的支持
webView.getSettings().setJavaScriptEnabled(true); 常用资源预加载 H5加载过程会有许多处外部依赖的JS、CSS、图片等资源需要下载,提前加载这些内容可以优化首次启动。 WebView mWebView = (WebView) findViewById(R.id.webview); mWebView.setWebViewClient(new WebViewClient() { @Override public WebResourceResponse shouldInterceptRequest(WebView webView, final String url) { WebResourceResponse response = null; // 检查该资源是否已经提前下载完成。我采用的策略是在应用启动时,用户在 wifi 的网络环境下 // 提前下载 H5 页面需要的资源。 boolean resDown = JSHelper.isURLDownValid(url); if (resDown) { jsStr = JsjjJSHelper.getResInputStream(url); if (url.endsWith(".png")) { response = getWebResourceResponse(url, "image/png", ".png"); } else if (url.endsWith(".gif")) { response = getWebResourceResponse(url, "image/gif", ".gif"); } else if (url.endsWith(".jpg")) { response = getWebResourceResponse(url, "image/jepg", ".jpg"); } else if (url.endsWith(".jepg")) { response = getWebResourceResponse(url, "image/jepg", ".jepg"); } else if (url.endsWith(".js") && jsStr != null) { response = getWebResourceResponse("text/javascript", "UTF-8", ".js"); } else if (url.endsWith(".css") && jsStr != null) { response = getWebResourceResponse("text/css", "UTF-8", ".css"); } else if (url.endsWith(".html") && jsStr != null) { response = getWebResourceResponse("text/html", "UTF-8", ".html"); } } // 若 response 返回为 null , WebView 会自行请求网络加载资源。 return response; } }); private WebResourceResponse getWebResourceResponse(String url, String mime, String style) { WebResourceResponse response = null; try { response = new WebResourceResponse(mime, "UTF-8", new FileInputStream(new File(getJSPath() + TPMD5.md5String(url) + style))); } catch (FileNotFoundException e) { e.printStackTrace(); } return response; } public String getJsjjJSPath() { String splashTargetPath = JarEnv.sApplicationContext.getFilesDir().getPath() + "/JS"; if (!TPFileSysUtil.isDirFileExist(splashTargetPath)) { TPFileSysUtil.createDir(splashTargetPath); } return splashTargetPath + "/"; } JS 本地化 比预加载更粗暴的优化方法是直接将常用的 JS 脚本本地化,直接打包放入 apk 中。比如 H5 页面获取用户信息,设置标题等通用方法,就可以直接写入一个 JS 文件,放入 asserts 文件夹,在 WebView 调用了onPageFinished() 方法后进行加载。需要注意的是,在该 JS 文件中需要写入一个 JS 文件载入完毕的事件,这样前端才能接受都爱 JS 文件已经种植完毕,可以调用 JS 中的方法了。 JS 延迟加载 默认情况html代码下载到WebView后,webkit开始解析网页各个节点,发现有外部样式文件或者外部脚本文件时,会异步发起网络请求下载文件, 但如果在这之前也有解析到image节点,那势必也会发起网络请求下载相应的图片。在网络情况较差的情况下,过多的网络请求就会造成带宽紧张,影响到 css或js文件加载完成的时间,造成页面空白loading过久。解决的方法就是告诉WebView先不要自动加载图片,等页面finish后再发起图 片加载。 故在WebView初始化时设置如下代码: public void int () { if(Build.VERSION.SDK_INT >= 19) { webView.getSettings().setLoadsImagesAutomatically(true); } else { webView.getSettings().setLoadsImagesAutomatically(false); } } 同时在WebView的WebViewClient实例中的onPageFinished()方法添加如下代码: @Override public void onPageFinished(WebView view, String url) { if(!webView.getSettings().getLoadsImagesAutomatically()) { webView.getSettings().setLoadsImagesAutomatically(true); } }Android 的 OnPageFinished 事件会在 Javascript 脚本执行完成之后才会触发。如果在页面中使 用JQuery,会在处理完 DOM 对象,执行完 $(document).ready(function() {}); 事件自会后才会渲染并显示页面。而同样的页面在 iPhone 上却是载入相当的快,因为 iPhone 是显示完页面才会触发脚本的执行。所以我们这边的解决方案延迟 JS 脚本的载入,这个方面的问题是需要Web前端工程师帮忙优化的。
17为啥离职呢 对待加班看法
18 你擅长什么,做了那些东西。
1 说下项目中遇到的棘手问题,包括技术,交际和沟通。
2 说下你进几年的规划
3 给你一个项目,你怎么看待他的市场和技术的关系
4 你一般喜欢从什么渠道获取技术信息,和提高自己的能力
5 你以往的项目中,以你现在的眼光去评价项目的利弊
6 对加班怎么看(不要太浮夸,现实一点哦)
1.单一职责原则 Single Responsibility Principle 2.开闭原则 Open Close Principle 软件中的对象对于扩展是开放,对于修改时封闭的。 3.里氏替换原则 Liskov Substitution Principle 所有引用基类的地方必须能够透明地使用其子类的对象。
4.依赖倒置原则 Dependence Inversion Principle 高层模块不应该依赖低层模块,两种都应该依赖器抽象。 抽象不应该依赖细节,细节应该依赖抽象。 java语言中的表现:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或者抽象类产生的。 5.接口隔离原则 Interface Segregation Principle 类间的依赖关系应该建立在最小的接口上。 接口隔离的目的是系统解开耦合,从而容易重构。更改和重新部署。 6.迪米特原则 Low of Demeter 一个类应该对自己需要耦合或者调用的类知道地最少,类的内部如何实现与调用者或者依赖者没关系,调用者或者依赖者只需要知道它需要的方法即可。 8 你知道的一些开源框架和原理
9 不同语言是否可以互相调用
10 安卓适配和性能调优问题
11 对于非立项(KPI)项目,怎么推进
11 你还要什么了解和要问的吗 你可以问下项目团队多少人,主要以什么方向为主,一年内的目标怎样,团队气氛怎样,等内容着手。
Butterknite原理
可能很多人都觉得ButterKnife在bind(this)方法执行的时候通过反射获取带有@Bind注解的属性并且获得注解中的R.id.xxx值,最后还是通过反射拿到Activity.findViewById()方法获取View,并赋值给对应的属性 。这是一个注解库的实现方式,比较原始,一个很大的缺点就是在Activity运行时大量使用反射会影响App的运行性能,造成卡顿以及生成很多临时Java对象更容易触发GC 。 ButterKnife没有使用这种原始方式,它用了Java Annotation Processing技术,就是在Java代码编译成Java字节码的时候就已经处理了@Bind、@OnClick之类的注解。 Annotation processing 是javac中用于编译时扫描和解析Java注解的工具。这个工具可以让你定义注解,并且自己定义解析器来处理它们。Annotation processing是在编译阶段执行的,它的原理就是读入Java源代码,解析注解,然后生成新的Java代码。
当你编译你的Android工程时,ButterKnife工程中ButterKnifeProcessor类的process()方法会执行以下操作: 开始它会扫描Java代码中所有的ButterKnife注解@Bind、@OnClick、@OnItemClicked等 。 当它发现一个类中含有任何一个注解时,ButterKnifeProcessor会帮你生成一个Java类,名字类似$$ViewBinder,这个新生成的类实现了ViewBinder接口 。 这个ViewBinder类中包含了所有对应的代码,比如@Bind注解对应findViewById(), @OnClick对应了view.setOnClickListener()等等 最后当Activity启动ButterKnife.bind(this)执行时,ButterKnife会去加载对应的ViewBinder类调用它们的bind()方法
glide 图片处理框架
Glide 缓存的图片和 ImageView 的尺寸相同,假如在第一个页面有一个200x200的ImageView,在第二个页面有一个100x100的ImageView,这两个ImageView本来是要显示同一张图片,却需要下载两次,有两个尺寸的缓存; Glide默认的图片质量是RGB_565,节约内存; Glide 不仅是一个图片缓存,它支持 Gif、WebP、缩略图。甚至是 Video,所以也可以叫做一个媒体缓存。 Glide和Activity/Fragment的生命周期是一致的,with(context)会创建RequestManager 用于监控Activity/Fragment的生命周期,从而可以控制Request的pause、resume、clear。 Glide 默认通过 UrlConnection 获取数据,也可以配合 okhttp 或是 Volley 使用。 Glide库的资源复用 Android的内存申请几乎都在new的时候发生,而new较大对象(比如Bitmap时),更加容易触发GC_FOR_ALLOW。所以Glide尽量的复用资源来防止不必要的GC_FOR_ALLOC引起卡顿。 最显著的内存复用就是内存LruResourceCache(第一次从网络或者磁盘上读取到Resource时,并不会保存到LruCache当中,当Resource被release时,也就是View不在需要此Resource时,才会进入LruCache当中) 还有BitmapPool(Glide会尽量用图片池来获取到可以复用的图片,获取不到才会new,而当LruCache触发Evicted时会把从LruCache中淘汰下来的Bitmap回收,也会把transform时用到的中间Bitmap加以复用及回收) Glide库图片池
4.4以前是Bitmap复用必须长宽相等才可以复用 4.4及以后是Size>=所需就可以复用,只不过需要调用reconfigure来调整尺寸 Glide用AttributeStrategy和SizeStrategy来实现两种策略 图片池在收到传来的Bitmap之后,通过长宽或者Size来从KeyPool中获取Key(对象复用到了极致,连Key都用到了Pool),然后再每个Key对应一个双向链表结构来存储。每个Key下可能有很多个待用Bitmap 取出后要减少图片池中记录的当前Size等,并对Bitmap进行eraseColor(Color.TRANSPAENT)操作确保可用 Glide加载发起流程 Glide.with(context)创建RequestManager RequestManager负责管理当前context的所有Request Context 可以传Fragment、Activity或者其他Context,当传Fragment、Activity时,当前页面对应的Activity的生命周 期可以被RequestManager监控到,从而可以控制Request的pause、resume、clear。这其中采用的监控方法就是在当前 activity中添加一个没有view的fragment,这样在activity发生onStart onStop onDestroy的时候,会触发此fragment的onStart onStop onDestroy。 RequestManager用来跟踪众多当前页面的Request的是RequestTracker类,用弱引用来保存运行中的Request,用强引用来保存暂停需要恢复的Request。 Glide.with(context).load(url)创建需要的Request 通常是DrawableTypeRequest,后面可以添加transform、fitCenter、animate、placeholder、error、override、skipMemoryCache、signature等等 如果需要进行Resource的转化比如转化为Byte数组等需要,可以加asBitmap来更改为BitmapTypeRequest Request是Glide加载图片的执行单位 Glide.with(context).load(url).into(imageview) 在Request的into方法中会调用Request的begin方法开始执行 在 正式生成EngineJob放入Engine中执行之前,如果并没有事先调用override(width, height)来指定所需要宽高,Glide则会尝试去获取imageview的宽和高,如果当前imageview并没有初始化完毕取不到高 宽,Glide会通过view的ViewTreeObserver来等View初始化完毕之后再获取宽高再进行下一步 Glide加载资源 GlideBuilder在初始化Glide时,会生成一个执行机EnginEngine中包含LruCache缓存及一个当前正在使用的active资源Cache(弱引用) activeCache辅助LruCache,当Resource从LruCache中取出使用时,会从LruCache中remove并进入acticeCache当中 Cache优先级LruCache>activeCache Engine在初始化时要传入两个ExecutorService,即会有两个线程池,一个用来从DiskCache获取resource,另一个用来从Source中获取(通常是下载) 线程的封装单位是EngineJob,有两个顺序状态,先是CacheState,在此状态先进入DiskCacheService中执行获取,如果没找到则进入SourceState,进到SourceService中执行下载
LruCache LruCache 内部使用的是LinckedHashMap,基于 Lru算法实现的一种缓存机制; Lru算法链表实现原理:每次put和get,都会将该元素移动到链表头部,而链表的尾部则为Least Recently Used,当达到最大缓存时,移出链表最后一个元素; 使用LruCache: 继承LruCache,并复写protected int sizeOf(K key, V value)方法,用于返回每个item的大小,单位与构造方法中参数(最大容量)相同; 复写protected V create(K key) 方法返回当item丢失时,返回对应key的值; 复写protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) 方法,该方法当value被回收释放存储空间时被remove调用,或者替换item值时put调用,evicted 的值为true—表示释放空间被删除,false—表示put或remove导致; LruCache 没有真正的释放内存,只是从 Map中移除掉数据,真正释放内存还是要复写上述方法进行释放。
