安卓编程:可否用try-catch捕获Out Of Memory Error以避免其发生?
只有在一种情况下,这样做是可行的:
在try语句中声明了很大的对象,导致OOM,并且可以确认OOM是由try语句中的对象声明导致的,那么在catch语句中,可以释放掉这些对象,解决OOM的问题,继续执行剩余语句。
但是这通常不是合适的做法。
Java中管理内存除了显式地catch OOM之外还有更多有效的方法:比如SoftReference, WeakReference, 硬盘缓存等。
在JVM用光内存之前,会多次触发GC,这些GC会降低程序运行的效率。
如果OOM的原因不是try语句中的对象(比如内存泄漏),那么在catch语句中会继续抛出OOM
如果保证app的网络安全(瑞幸咖啡面试题)
Tip 1:尽量使用https
https可以过滤掉大部分的安全问题。https在证书申请,服务器配置,性能优化,客户端配置上都需要投入精力,所以缺乏安全意识的开发人员容易跳过https,或者拖到以后遇到问题再优化。https除了性能优化麻烦一些以外其他都比想象中的简单,如果没精力优化性能,至少在注册登录模块需要启用https,这部分业务对性能要求比较低。
Tip 2:不要传输密码
不知道现在还有多少app后台是明文存储密码的。无论客户端,server还是网络传输都要避免明文密码,要使用hash值。客户端不要做任何密码相关的存储,hash值也不行。存储token进行下一次的认证,而且token需要设置有效期,使用refresh token去申请新的token。
Tip 3:Post并不比Get安全
事实上,Post和Get一样不安全,都是明文。参数放在QueryString或者Body没任何安全上的差别。在Http的环境下,使用Post或者Get都需要做加密和签名处理。
Tip 4:不要使用301跳转
301跳转很容易被Http劫持攻击。移动端http使用301比桌面端更危险,用户看不到浏览器地址,无法察觉到被重定向到了其他地址。如果一定要使用,确保跳转发生在https的环境下,而且https做了证书绑定校验。
Tip 5:http请求都带上MAC
所有客户端发出的请求,无论是查询还是写操作,都带上MAC(Message Authentication Code)。MAC不但能保证请求没有被篡改(Integrity),还能保证请求确实来自你的合法客户端(Signing)。当然前提是你客户端的key没有被泄漏,如何保证客户端key的安全是另一个话题。MAC值的计算可以简单的处理为hash(request params+key)。带上MAC之后,服务器就可以过滤掉绝大部分的非法请求。MAC虽然带有签名的功能,和RSA证书的电子签名方式却不一样,原因是MAC签名和签名验证使用的是同一个key,而RSA是使用私钥签名,公钥验证,MAC的签名并不具备法律效应。
GC算法描述(美柚)
-
引用计数算法,此对象有一个引用,+1,删除一个引用,则-1,只收集计数为0的对象
缺点是无法处理循环引用的问题2.引用计数的方法需要编译器的配合,编译器需要为对象生成额外的代码 -
根搜索算法,设定若干个根对象,当任何一个根对象到某一个对象均不可达时,则这个对象可以被回收
还要解决什么时候回收和如何回收的问题标记-清除算法,复制算法和标记-整理算法
当堆中的有效内存空间被耗尽的时候,就会停止整个程序,然后进行两项工作,一项是标记,一项是清除
Leakcannary:
工作机制
-
RefWatcher.watch() 创建一个 KeyedWeakReference 到要被监控的对象。
-
然后在后台线程检查引用是否被清除,如果没有,调用GC。
-
如果引用还是未被清除,把 heap 内存 dump 到 APP 对应的文件系统中的一个 .hprof 文件中。
-
在另外一个进程中的 HeapAnalyzerService 有一个 HeapAnalyzer 使用HAHA 解析这个文件。
-
得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄露。
-
HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并确定是否是泄露。如果是的话,建立导致泄露的引用链。
-
引用链传递到 APP 进程中的 DisplayLeakService, 并以通知的形式展示出来。
请简要描述一下事件分发机制,并说明onInterceptTouchEvent和onTouchEvent的作用和关系
Android的事件分发机制也是View和ViewGroup的事件的分发和处理,当用户手指接触到屏幕以后,所产生的一系列事件中,都被android包装成MotionEvent,由三种事件类型组成,ACTION_DOWN,ACTION_MOVE,ACTION_UP,
顺序是activity,再到viewGroup,再传递到View
由三种方法dispatchTouchEvent(),onInterceptTouchEvent,onTouchEvent所处理,dispatchTouchEvent的作用是分发传递点击事件,当点击事件能够传递给当前的View的时候,该方法都会被调用,onInterceptTouchEvent的作用是拦截事件,只存在ViewGroup中,在ViewGroup的dispatchTouchEvent中调用,如果onInterceptTouchEvent return true那么事件不会再传递下去,就让当前view的ontouchEvent来处理
onTouchEvent的作用的是处理点击事件
谈谈对android内存管理的了解,如何GC回收对象
App的进程的空间是虚拟内容,可是它的运行需要在物理内存RAM中,操作系统会将程序运行中申请的内容映射到
RAM中,让进程可以使用物理内存
android中的进程有native进程和java进程,Java进程指的是Android运行在dalvik上的进程,Android系统对dalvik的vm heampsize做了硬性规定,当java进程申请的java空间超过阙值时候,就会抛出OOM异常,为了让程序继续运行,可以用下列方法来解决
1.创建子线程,通过android:process的方法
2.使用jni在native heap上申请空间
GC回收对象
GC会选择一些它了解还存活的对象作为内存遍历的根节点,然后开始对heap进行遍历,到最后
部分没有直接或是间接引用到的GC Roots就是需要回收的垃圾,会被GC回收掉
可以作为GCRoots对象包括下面几种:
1.虚拟机栈中引用的对象
2.方法区中类静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中JNI引用的对象
软引用 softReference,在系统将要发生内存溢出异常之前,
将会对这些对象列进回收范围进行二次回收弱引用,被弱引用的对象只能生存到下一次垃圾回收之前
如何写一个contentProvider
contentProvider作为不同软件之间的数据共享,提供统一的接口,
contentProvider 主要功能是封装对数据库的增删改查,并可以对外提供结果,供其它应用程序访问本应用数据库里面的数据,首先我们要创建一个类继承ContentProvider,并且需要实现query,getType,insert,delste,update几个函数,在注册contentProvider指定一个authorities这个是唯一的,我们写contentProvider的时候需要一个操作数据库的类,
2.创建UriMatcher对象以及数据库操作对象,在增删改查的时候回从调用端传递Uri对象过来,在ContentProvider的增删改查的函数里,我们需要根据该uri来判断,是要操作一条数据,还是操作所有符合条件的数据
我们可以用个contentProvider知道数据的变化,使用registerContentObserver来注册一个观察者实例,当指定的uri发生改变时候,该实例会回调实例对象进行相应处理
HandlerThread的特点
HandlerThread将loop转到子线程中处理,说白了就是将分担MainLooper的工作量,降低了主线程的压力,使主界面更流畅。
开启一个线程起到多个线程的作用。处理任务是串行执行,按消息发送顺序进行处理。HandlerThread本质是一个线程,在线程内部,代码是串行处理的。
但是由于每一个任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。
HandlerThread拥有自己的消息队列,它不会干扰或阻塞UI线程。
对于网络IO操作,HandlerThread并不适合,因为它只有一个线程,还得排队一个一个等着。
DexClassLoader 和 PathClassLoader
在Android中,ClassLoader是一个抽象类,实际开发过程中,我们一般是使用其具体的子类DexClassLoader、PathClassLoader这些类加载器来加载类的,它们的不同之处是:
DexClassLoader可以加载jar/apk/dex,可以从SD卡中加载未安装的apk;
PathClassLoader只能加载系统中已经安装过的apk;
减少应用的启动时间的耗时
1)不要在Application的构造方法、attachBaseContext()、onCreate()里面进行初始化耗时操作。
2)MainActivity,由于用户只关心最后的显示的这一帧,对我们的布局的层次要求要减少,自定义控件的话测量、布局、绘制的时间,不要在onCreate、onStart、onResume当中做耗时操作。
3)对于SharedPreference的初始化,因为他初始化的时候是需要将数据全部读取出来放到内存当中。
优化1:可以尽可能减少sp文件数量(IO需要时间);
优化2:像这样的初始化最好放到线程里面;
优化3:大的数据缓存到数据库里面。
retrofit源码解析
1.在retrofit中通过一个接口
动态代理
1.首先通过method把它转换成ServiceMethod
2.通过serviceMethod和参数获取到okHttpCall对象
3.最后把okHttpCall做进一步封装传给call
Retrofit create实现动态代理,newProxyIntance返回类的实例,所有的操作在InvocationHandler来执行Invoke
ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
ServiceMethod.Build方法构建ServiceMethod,并放到serviceMethodCache的缓存中
callAdapter是数据转换用的是retrofit的核心
从callAdapterFactory中获取call adapter对象
1.通过build方法创建一个retrofit对象
2.通过Retrofit.create()方法是怎么把我们所定义的接口转化为接口实例并使用接口中的方法
3.最终所有的网络请求调用okHttp
Message,Handler,MessageQueue,Looper之间的关系
Looper是一个消息分发器,在主线程创建的时候会创建一个Looper对象,MessageQueue是消息队列,是由Mesage组成的一个队列,Handler:可以获取Message,然后执行动作,可以在主线程和子线程之间互相传递数据
在主线程创建之后会创建一个Looper对象,创建Looper对象的时候会创建一个MessageQueue,Looper是一个轮询器,会不停的轮询MessageQueue中的消息,在获取消息之后会把这个消息交给对应的Handler来处理
SingleTop和SingleTask各自的行为
这两个都是android的activity的加载模式
singleTop,当跳转对象位于栈顶的activity,那程序将不会生成一个新的activity实例,而是直接跳到现存于栈顶的那个activity实例
singletask只是会创建一个实例额,无论跳转对象是不是位于栈顶,程序都不会生成一个实例,如果该实例不在栈顶,将清空这个实例到栈顶的activity
装饰者模式和外观模式
装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
外观模式(Facade),他隐藏了系统的复杂性,并向客户端提供了一个可以访问系统的接口
子线程如何跟主线程通信
最常用就是thread+handler
1.Activity.runOnUiThread(Runnable)
2.View.post(Runnable)
3.View.postDelayed(Runnable, long)
4.Handler
5.AsyncTask
HTTP与HTTPS的区别
HTTPS:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。
- https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
- http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
- http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
- http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。