Android你必须要了解的
1、requestLayout()、invalidate()和postInvalidate()的区别
在绘制view的过程中会遇到调用重绘方法,系统给出了三个方法。
-
requestLayout()被调用会重新走一遍流程同时会调用onMeasure、onLayout、onDraw三个方法。
-
invalidate()被调用只会调用onDraw方法。
-
postInvalidate()方法和invalidate()的区别:invalidate()需要执行在UI线程中,如果在非UI线程需要使用postInvalidate()。
2、AMS分析-Activity启动流程
系统启动流程 Zygote进程 –> SystemServer进程 –> 各种系统服务 –> 应用进程
3、类加载机制
ClassLoader双亲委派机制,简单来说就是先把加载请求转发到父加载器,父加载器失败了,再自己试着加载。
原理:JVM将class文件字节码文件加载到内存中, 并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class 对象,作为方法区类数据的访问入口。
过程(类的生命周期):JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。其中加载、检验、准备、初始化和卸载这个五个阶段的顺序是固定的,而解析则未必。为了支持动态绑定,解析这个过程可以发生在初始化阶段之后。
4、权限组
同一组的任何一个权限被授权了,其他权限也自动被授权。例如android.permission-group.STORAGE
5、Bitmap如何处理大图,如一张30M的大图,如何预防OOM
Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M。因此我们所能利用的内存空间是有限的。如果我们的内存占用超过了一定的水平就会出现OutOfMemory的错误。
BitmapFactory提供了几种解码方式(decodeByteArray(), decodeFile(), decodeResource()等等),以便从多种资源中创建一个Bitmap(位图)对象。可以根据你的图片数据来源选择最合适的解码方式。这些方法视图为构造Bitmap对象分配内存,因此很容易导致OutOfMemory(OOM)异常。每一种解码方式都有额外的特征,你可以通过BitmapFactory.Options类类指定解码方法。- 尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource直接使用图片路径来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存。改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再调用上述方法将其设为ImageView的 source。decodeStream最大的秘密在于其直接调用JNI>>nativeDecodeAsset()来完成decode,无需再使用java层的createBitmap,从而节省了java层的空间。
private Bitmap backBitmap = null;
try {
InputStream stream = new FileInputStream("");
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inPurgeable = true;
options.inInputShareable = true;
backBitmap = BitmapFactory.decodeStream(stream, null, options);
stream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
if (backBitmap != null){
backBitmap.recycle();
}
6、AsyncTask
优点:简单、高效
缺点:可能会产生内存泄露、过多线程阻塞
- 我们一般在组件内部,以内部类的方式创建
AsyncTask,而java里面,内部类是默认持有外部类的引用。 - 我们的加载数据是在子线程中执行,但是java里面有没有提供很好的中断机制来中断线程 (有中断方法,但是如果你不针对性的处理,这个方法也没什么卵用),这样就导致组件生命周期已经结束,应该被GC回收,但是由于子线程正在执行,内部类无法回收,进而导致组件无法正常回收,所以造成了内存泄露 (Handler导致内存泄露也是类似的道理)。
AsyncTask是封装好的线程池,比起Thread+Handler的方式,AsyncTask在操作UI线程上更方便,因为onPreExecute()、onPostExecute()及更新UI方法onProgressUpdate()均运行在主线程中,这样就不用Handler发消息处理了;- 实际的运用中,真正的缺点来自于
AsyncTask的全局线程池只有5个工作线程,也就是说,一个APP如果运用AsyncTask技术来执行线程,那么同一时间最多只能有5个线程同时运行,其他线程将被阻塞(注:不运用AsyncTask执行的线程,也就是自己new出来的线程不受此限制),所以AsyncTask不要用于多线程取网络数据,因为很可能这样会产生阻塞,从而降低效率。 - 能否同时并发100+asynctask呢? 当执行execute方法时,AsyncTask用的是线程池机制,容量是128,最多同时运行5个core线程,剩下的排队。但是在JDK1.5之后新增executeOnExecutor方法,修改为了顺序执行,即只有当一个的实例的任务完成后在执行下一个实例的任务。
7、进程间通信的方式
常见方式:Bundle通过Intent传递数据,文件共享,ContentProvider,BroadcastReceiver,基于Binder的AIDL和Messenger以及Socket。ContentProvider是不同应用程序之间进行数据交换的标准API。
8、编译器
- Dalvik 是一个基于 JIT(Just in time)编译引擎
- ART 是一个基于AOT(Ahead of time)编译引擎
9、EventBus实现原理
EventBus是一款在 Android 开发中使用的发布/订阅事件总线框架,基于观察者模式,将事件的接收者和发送者分开,简化了组件之间的通信,使用简单、效率高、体积小!
10、消息机制
- 在使用
Handler的时候,在Handler所创建的线程需要维护一个唯一的Looper对象, 每个线程对应一个Looper,每个线程的Looper通过ThreadLocal来保证,Looper对象的内部又维护有唯一的一个MessageQueue,所以一个线程可以有多个Handler,但是只能有一个Looper和一个MessageQueue。 Message在MessageQueue不是通过一个列表来存储的,而是将传入的Message存入到了上一个Message的next中,在取出的时候通过顶部的Message就能按放入的顺序依次取出Message。Looper对象通过loop()方法开启了一个死循环,不断地从looper内的MessageQueue中取出Message, 然后通过Handler将消息分发传回Handler所在的线程。
// 启动子线程
new Thread(new LocalRunnable()).start();
// 向子线程发送消息
Message threadHandlerMessage = Message.obtain();
threadHandlerMessage.what = 0x112;
threadHandlerMessage.obj = "主线程向子线程发送消息";
mThreadHandler.sendMessage(threadHandlerMessage);
private ThreadHandler mThreadHandler;
private class LocalRunnable implements Runnable {
@Override
public void run() {
Looper.prepare();
mThreadHandler = new ThreadHandler();
Looper.loop();
}
}
private static class ThreadHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
String value = (String) msg.obj;
ToastUtils.showShort(value);
}
}
11、事件分发机制
Android事件分发机制由其中三个方法决定分别是:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent分发、拦截、响应,到底View的分发机制是怎么运行?
| 事件相关 | Activity | ViewGroup | View |
|---|---|---|---|
| dispatchTouchEvent | y | y | y |
| onInterceptTouchEvent | n | y | n |
| onTouchEvent | y | y | y |
- 只有ViewGroup才有拦截事件。
- dispatchTouchEvent 返回true:表示改事件在本层不再进行分发且已经在事件分发自身中被消费了。
- dispatchTouchEvent 返回 false:表示事件在本层不再继续进行分发,并交由上层控件的
onTouchEvent方法进行消费。 - onInterceptTouchEvent 返回true:表示将事件进行拦截,并将拦截到的事件交由本层控件 的
onTouchEvent进行处理。 - onInterceptTouchEvent 返回false:表示不对事件进行拦截,事件得以成功分发到子
View。并由子View的dispatchTouchEvent进行处理。 - onTouchEvent 返回 true:表示
onTouchEvent处理完事件后消费了此次事件。此时事件终结,将不会进行后续的传递。 - onTouchEvent 返回 false:事件在
onTouchEvent中处理后继续向上层View传递,且有上层View的onTouchEvent进行处理。 - 事件的分发顺序:
Activity-->ViewGroupA-->ViewGroupB-->View自顶向下分发,事件的响应顺序:View-->ViewGroupB-->ViewGroupA-->Activity自底向上响应消费。
12、Android版本适配和机型适配
- 6.0开始权限方案改为运行时权限需要用户同意才能获取权限。
- 7.0开始应用间共享文件时授予URI临时访问权限,即FileProvider的使用。
- 8.0开始启动图标自适应、权限组取消权限要一个个的申请、通知添加渠道功能、后台执行限制,对系统组件反射有限制
- 适配主要是屏幕尺寸的适配,采用SW方案的dimension,drawable和mipmap的不同像素的图片,以及values-v21等不同版本的style,针对特定型号的机型可通过android.os.build包下的Build拿到设备型号和品牌。
13、安全
分类
- 数据通信安全
- 本地app程序安全
数据通信安全:
- app与服务器通信安全:与服务器进行数据交互,数据肯定要加密。加密主要有对称加密、非对称加密,不可逆加密。从安全性考虑可选择对称加密AES加密方式。AES主要是用在数据本身的加密,即使传输过程中被截取了,也是加密过后的数据。但AES的弊端的是,客户端加密的话,密钥肯定是储存在app中,如果app被成功破解了,数据也就被暴露了。非对称加密最普遍的就是RSA加密,因为RSA加密有个长度限制,这就导致了RSA加密不能用于所有的数据交互。但是可以用到一些短数据,比如用户个人信息之类的。不可逆加密,比如MD5加密、SHA加密等。所谓的不可逆加密就是,只能单向加密,不能反向解密。MD5把数据加密,最后得到固定长度的16进制编码。这个加密的作用一般是匹配验证,验证某个数据是否改变。比如密码,在向服务器存储密码,一般不会存储明文密码。安卓本地存储个标志也一般不会明文存储。在通信上,有时并不一定对数据本身进行加密。比如可以使用令牌的方式,具体做法是:用户登录成功后,服务器生成一个访问令牌给客户端,此服务器设置令牌的有效期。客户端的所有请求都携带这个令牌去请求数据。当令牌时效的时候,客户端用户所有请求都请求不到,客户端用户退出登录状态。令牌时效都是由服务器来判断,时效的方式:1、令牌过期。这个一般是用户长期不登录,服务器设置的过期时间已经到了。2、令牌错误,一搬是黑客拿未知令牌恶意请求数据3、令牌更换,一般是客户端在另一台设备上登录重新获取了最新令牌。另外,令牌也可以使用户不用再次输入密码再次登录。从安全方式来看,请求数据最好使用https进行请求。
- app本地数据通讯安全:主要是指组件之间的通信,有可能被反编译之后smali中查到,比如sharedpreferces存储的xml文件数据。
本地app程序安全:
- APK破解:使用 ProGuard、DexGuard 等工具混淆代码;是给程序加固,第三方加固工具都可以用。重要逻辑用 NDK 实现。.so库相对来说很安全了。做三方库的公司,大都把sdk重要的打成.so库,我认为不光是因为Java不能够完成而只能c++完成,还有保密安全这一措施。
- 数据的存储:本地数据存储是在手机的本地存储data文件下,虽然访问需要系统权限,但对于root的手机,这些在本地的数据很容易暴露出来。不建议全局可读写的内部存储方式,不要把敏感信息放在外部存储上面;在动态加载外部资源的时候验证文件完整性;一些重要数据(用户账号密码等),或者标记存储本地的时候也应该进行加密,或者直接存储hash码,而不能直接存储明文。有些存储本地的数据,比如令牌,就需要进行加密处理。登录成功后的重要信息,如需要存储本地,也需要加密。
- 组件暴露 (Activity, Service, Broadcast Receiver, Content Provider):验证输入信息、验证组件调用等。android:exported 设置为 false。使用 android:protectionLevel="signature" 验证调用来源。如果是非常重要的组建,不要在这里面进行配置,如果可以用fragment完成的,最好用fragment来做。并且重要的信息不要在配置文件里面配置。
- WebView:恶意 App 可以注入 JavaScript 代码进入 WebView 中的网页,网页未作验证。恶意网页可以执行 JavaScript 反过来调用 App 中注册过的方法,或者使用资源。这些恶意程序嵌入 Web App,然后窃取用户信息,远程调用 App 代码。更有甚者,通过 Java Reflection 调用 Runtime 执行任意代码。不使用 WebView 中的 setJavaScriptEnabled(true),或者使用时对输入进行验证。