Android你必须要了解的

266 阅读11分钟

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

优点:简单、高效

缺点:可能会产生内存泄露、过多线程阻塞

  1. 我们一般在组件内部,以内部类的方式创建AsyncTask,而java里面,内部类是默认持有外部类的引用。
  2. 我们的加载数据是在子线程中执行,但是java里面有没有提供很好的中断机制来中断线程 (有中断方法,但是如果你不针对性的处理,这个方法也没什么卵用),这样就导致组件生命周期已经结束,应该被GC回收,但是由于子线程正在执行,内部类无法回收,进而导致组件无法正常回收,所以造成了内存泄露 (Handler导致内存泄露也是类似的道理)
  3. AsyncTask是封装好的线程池,比起Thread+Handler的方式,AsyncTask在操作UI线程上更方便,因为onPreExecute()、onPostExecute()及更新UI方法onProgressUpdate()均运行在主线程中,这样就不用Handler发消息处理了;
  4. 实际的运用中,真正的缺点来自于AsyncTask的全局线程池只有5个工作线程,也就是说,一个APP如果运用AsyncTask技术来执行线程,那么同一时间最多只能有5个线程同时运行,其他线程将被阻塞(注:不运用AsyncTask执行的线程,也就是自己new出来的线程不受此限制),所以AsyncTask不要用于多线程取网络数据,因为很可能这样会产生阻塞,从而降低效率。
  5. 能否同时并发100+asynctask呢? 当执行execute方法时,AsyncTask用的是线程池机制,容量是128,最多同时运行5个core线程,剩下的排队。但是在JDK1.5之后新增executeOnExecutor方法,修改为了顺序执行,即只有当一个的实例的任务完成后在执行下一个实例的任务。

7、进程间通信的方式

常见方式:Bundle通过Intent传递数据,文件共享ContentProviderBroadcastReceiver,基于Binder的AIDLMessenger以及SocketContentProvider是不同应用程序之间进行数据交换的标准API

8、编译器

  • Dalvik 是一个基于 JIT(Just in time)编译引擎
  • ART 是一个基于AOT(Ahead of time)编译引擎

9、EventBus实现原理

EventBus是一款在 Android 开发中使用的发布/订阅事件总线框架,基于观察者模式,将事件的接收者和发送者分开,简化了组件之间的通信,使用简单、效率高、体积小!

img

10、消息机制

  1. 在使用Handler的时候,在Handler所创建的线程需要维护一个唯一的Looper对象, 每个线程对应一个Looper,每个线程的Looper通过ThreadLocal来保证,Looper对象的内部又维护有唯一的一个MessageQueue,所以一个线程可以有多个Handler,但是只能有一个Looper和一个MessageQueue
  2. MessageMessageQueue不是通过一个列表来存储的,而是将传入的Message存入到了上一个 Message的next中,在取出的时候通过顶部的Message就能按放入的顺序依次取出Message
  3. 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事件分发机制由其中三个方法决定分别是:dispatchTouchEventonInterceptTouchEventonTouchEvent分发、拦截、响应,到底View的分发机制是怎么运行?

事件相关 Activity ViewGroup View
dispatchTouchEvent y y y
onInterceptTouchEvent n y n
onTouchEvent y y y
  1. 只有ViewGroup才有拦截事件。
  2. dispatchTouchEvent 返回true:表示改事件在本层不再进行分发且已经在事件分发自身中被消费了。
  3. dispatchTouchEvent 返回 false:表示事件在本层不再继续进行分发,并交由上层控件的onTouchEvent方法进行消费。
  4. onInterceptTouchEvent 返回true:表示将事件进行拦截,并将拦截到的事件交由本层控件 的onTouchEvent 进行处理。
  5. onInterceptTouchEvent 返回false:表示不对事件进行拦截,事件得以成功分发到子View。并由子ViewdispatchTouchEvent进行处理。
  6. onTouchEvent 返回 true:表示onTouchEvent处理完事件后消费了此次事件。此时事件终结,将不会进行后续的传递。
  7. onTouchEvent 返回 false:事件在onTouchEvent中处理后继续向上层View传递,且有上层ViewonTouchEvent进行处理。
  8. 事件的分发顺序: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、安全

分类

  1. 数据通信安全
  2. 本地app程序安全

数据通信安全:

  1. app与服务器通信安全:与服务器进行数据交互,数据肯定要加密。加密主要有对称加密、非对称加密,不可逆加密。从安全性考虑可选择对称加密AES加密方式。AES主要是用在数据本身的加密,即使传输过程中被截取了,也是加密过后的数据。但AES的弊端的是,客户端加密的话,密钥肯定是储存在app中,如果app被成功破解了,数据也就被暴露了。非对称加密最普遍的就是RSA加密,因为RSA加密有个长度限制,这就导致了RSA加密不能用于所有的数据交互。但是可以用到一些短数据,比如用户个人信息之类的。不可逆加密,比如MD5加密、SHA加密等。所谓的不可逆加密就是,只能单向加密,不能反向解密。MD5把数据加密,最后得到固定长度的16进制编码。这个加密的作用一般是匹配验证,验证某个数据是否改变。比如密码,在向服务器存储密码,一般不会存储明文密码。安卓本地存储个标志也一般不会明文存储。在通信上,有时并不一定对数据本身进行加密。比如可以使用令牌的方式,具体做法是:用户登录成功后,服务器生成一个访问令牌给客户端,此服务器设置令牌的有效期。客户端的所有请求都携带这个令牌去请求数据。当令牌时效的时候,客户端用户所有请求都请求不到,客户端用户退出登录状态。令牌时效都是由服务器来判断,时效的方式:1、令牌过期。这个一般是用户长期不登录,服务器设置的过期时间已经到了。2、令牌错误,一搬是黑客拿未知令牌恶意请求数据3、令牌更换,一般是客户端在另一台设备上登录重新获取了最新令牌。另外,令牌也可以使用户不用再次输入密码再次登录。从安全方式来看,请求数据最好使用https进行请求。
  2. app本地数据通讯安全:主要是指组件之间的通信,有可能被反编译之后smali中查到,比如sharedpreferces存储的xml文件数据。

本地app程序安全:

  1. APK破解:使用 ProGuard、DexGuard 等工具混淆代码;是给程序加固,第三方加固工具都可以用。重要逻辑用 NDK 实现。.so库相对来说很安全了。做三方库的公司,大都把sdk重要的打成.so库,我认为不光是因为Java不能够完成而只能c++完成,还有保密安全这一措施。
  2. 数据的存储:本地数据存储是在手机的本地存储data文件下,虽然访问需要系统权限,但对于root的手机,这些在本地的数据很容易暴露出来。不建议全局可读写的内部存储方式,不要把敏感信息放在外部存储上面;在动态加载外部资源的时候验证文件完整性;一些重要数据(用户账号密码等),或者标记存储本地的时候也应该进行加密,或者直接存储hash码,而不能直接存储明文。有些存储本地的数据,比如令牌,就需要进行加密处理。登录成功后的重要信息,如需要存储本地,也需要加密。
  3. 组件暴露 (Activity, Service, Broadcast Receiver, Content Provider):验证输入信息、验证组件调用等。android:exported 设置为 false。使用 android:protectionLevel="signature" 验证调用来源。如果是非常重要的组建,不要在这里面进行配置,如果可以用fragment完成的,最好用fragment来做。并且重要的信息不要在配置文件里面配置。
  4. WebView:恶意 App 可以注入 JavaScript 代码进入 WebView 中的网页,网页未作验证。恶意网页可以执行 JavaScript 反过来调用 App 中注册过的方法,或者使用资源。这些恶意程序嵌入 Web App,然后窃取用户信息,远程调用 App 代码。更有甚者,通过 Java Reflection 调用 Runtime 执行任意代码。不使用 WebView 中的 setJavaScriptEnabled(true),或者使用时对输入进行验证。

14、性能优化