目录
-
安卓的多线程
- 进程和线程的区别
- 进程是CPU资源分配的最小单位,线程的CPU调度的最小单位;
- 进程之间不能共享资源,线程之间可以共享所在进程的地址空间和资源;
- 一个进程中可以有多个线程,一个线程只能属于一个进程;
- 进程可以开启线程和其他进程
- 创建线程的三种方式
- 继承Thread类重写run方法;
- 实现Runnable接口重写run方法;
- 实现Callable重写call方法;
可以提供一个返回值
Call方法可以抛出一个异常
可以通过运行Callable得到的Future对象监听目标线程调用call方法的结果,得到返回值;
- 对线程池的理解
- 线程池在任务未到来之前会创建一定数量的线程放在空闲队列中,这些线程都是处于睡眠状态;当请求到来之后,会给其分配一个空闲线程,然后运行;如果没有空闲线程,会创建新线程,如果最大线程数满了,就会抛出异常.
- 作用:
- 是为了尽可能减少对象创建和销毁的次数,提高性能,减少CPU资源的消耗,可以控制线程数量,避免内存消耗过度.
- 当线程调度任务发生异常时,会重新创建一个线程来代替异常线程;
- 如何创建:使用ThreadPoolExecutor
- 线程的状态
- New:新建状态,还没有调用start
- Runnable:可运行状态,调用start进入可运行状态
- Blocked:阻塞状态,被锁阻塞,暂时不活动
- Synchronized:关键字修饰的方法或代码块获取锁时的状态
- Waiting:等待状态,不运行任何代码,等待线程调度器调度,sleep,wait用来暂停当前线程的执行,任何其他线程都可以中断当前线程的睡眠;
- Timed Waiting:超时等待,在指定时间自行返回;
- Terminated:终止状态,包括正常终止和异常终止;
- wait和sleep的区别
- Wait用于线程间的通信,如果等待且其他线程被唤醒时,它会释放锁;
- Sleep仅是释放CPU资源或者让当前线程停止执行一段时间,并不会释放锁;
- 线程中断
- Java中提供了线程中断机制相关的interrupt()方法,他将中断标识位设置为true,具体是否要中断由程序来判断;
- New和Terminal不理会线程中断请求;
- Runnable和Blocked调用interrupt会将标志位设置为true;
- Waiting和Timed Waiting会发生InterruptedException异常;
- Thread为什么不能用stop方法停止线程
- 会抛出ThreadDeath异常;
- 释放该线程持有的所有的锁,数据可能会被破坏;
- 同步方法和同步代码块
当多个线程同时操作一个可共享的资源变量时,就会导致数据不准确,解决方法:
-
- 同步方法.即用synchronized关键字修饰的方法,在调用此方法前,需要获得内置锁,否则处于阻塞状态;synchronized也可以修饰静态方法,调动此静态方法,会锁住整个类;
- 同步代码块:synchronized修饰的语句块,
- 使用特殊域变量volatile实现线程同步,他会保证修改的值立即更新到主存中,即内存可见性;
- 使用重入锁:
- ReentrantLock()方法,创建一个ReentrantLock实例;
- lock()获得锁;
- unlock()释放锁;
- 使用局部变量实现线程同步:使用ThreadLocal来管理变量,每一个线程都有该变量的副本,副本之间独立
- 线程通信机制
- 四大角色
- Message:消息的载体,常用的四个字段:arg1,arg2,what,obj
- 四大角色
Obj携带Object对象,其他三个携带整形数据
-
-
- MessageQueue:消息队列,每个线程只有一个
- Looper:调用loop()方法,进入到一个无限循环(没有新消息就阻塞),每当MessQueue有消息就取出来,每个线程只有一个Looper
- Handler:发送和处理消息
- ThreadLocal:每个线程私有的本地存储区域,存储对象
-
- 原子性,可见性,有序性
- 原子性:某一个操作不可分割,比如a=0是原子操作,a++不是原子操作,非原子操作都存在线程安全问题
- 可见性:指线程之间的可见性,如用volatile修饰的内容具有可见性,但不能保证其原子性,因为volatile只能使用在变量级别,不能对方法进行修饰;
- 有序性:线程执行的顺序按代码的先后顺序,java内存模型中,允许编译器和处理器对指令进行重排序,这样会影响到多线程并发执行的正确性.所以可以通过volatile和synchronized以及Lock保证一定的有序性.
- 死锁产生的四个必要条件
- 互斥条件,一个资源一段时间内仅能够被一个进程所占有;
- 请求和保持条件:进程已经持有了至少一个资源,但又提出了新的资源请求,而该资源被其他进程所占有,此时请求会被阻塞,但自己的资源又保持不放;
- 不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,只能自己主动释放;
- 循环等待条件:若干进程形成循环等待资源的关系,环路中每一个进程所占有的资源被另一个进程所申请.
- 死锁的避免
系统对进程所发出的每一个能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源.
- 控制线程的个数
- 使用semphore来控制,acquire来请求一个线程,信号量-1,release()释放一个信号量,信号量+1
- 使用线程池executor,new一个CachedThreadPool固定线程;
- ThreadPoolExecutor的六个参数:
- CorePoolsize:核心池大小
- Maxnumpoolsize最大池大小,工作队列满了之后放这里,满了就会报异常
- Workqueue:工作队列:核心池放不下的会先放在这里
-
Java基础知识
- Java中堆和栈的理解
- 堆内存:
- 用于存储java中的对象和数组,当我们new一个对象或者创建一个数组,就会在堆内存中开辟一块空间;
- 生存期不必告诉编译器,存取速度较慢;
- 堆是所有线程共享的一块公共区域,对象都在堆里创建,但为了提升效率,线程会从堆中拷贝一个缓存到自己的栈中;
- 栈内存:
- 主要用于执行程序,比如基本类型的变量和对象的引用变量;
- 栈数据数据大小和生存期是确定的,存取速度比堆快;
- 栈是一块和线程相关的内存区域,每个线程都有自己的栈内存,用于存储本地变量,方法参数和栈调用;
- 堆内存:
- JVM运行时的内存分布
- 线程共享:
- 方法区:存放已被加载的类信息,常量,静态常量;
- 堆:java内存最大的一块,所有对象实例,数组都存放在java堆,gc回收的地方;
- 线程私有:
- 虚拟机栈:java虚拟栈:存放基本数据类型,对象的引用,方法出口等;
- 本地方法栈
- 程序计数器
- 线程共享:
- 四种引用:
- 强引用:若内存中的对象具有强引用,即时内存不足,抛异常,垃圾回收器也不会回收;如果显示地设置为null,或超出生命周期范围,就可以考虑回收;
- 软引用:如果内存空间足够,GC不会回收,内存空间不足,就会回收;如果软引用被gc回收,虚拟机就会把这个软引用加入到与之关联的引用队列中;
- 弱引用:gc一旦发现只具有弱引用的对象,就会回收;并把这个弱引用加入到与之相关联的引用队列中;
- 虚引用:并不会决定对象的生命周期,且必须和引用队列联合使用;
- equals和hashCode的区别:
- 两个对象equals,hashCode一定相等
- HashCode不相等,一定不equals
- String,StringBuffer,StringBuilder的区别
- String字符串常量,不可变,每次改变相当于生成一个新的对象;
- StringBuilder字符串变量,线程不安全,效率高于StringBuffer;
- StringBuffer,字符串变量,线程安全;
- 内部类
- 成员内部类:位于外部类成员位置的类,可以使用外部类中的成员变量和成员方法;
- class Outer {
- private int age = 20;
- //成员位置
- class Inner {
- public void show() {
- System.out.println(age);
- }
- }
- }
- 局部内部类:定义在在方法和作用域中
- class Outer {
- public void method(){
- class Inner {
- }
- }
- }
- 匿名内部类:无构造方法
- 静态内部类:由static修饰的类,不能使用外围类的非static的变量和方法
- 作用:每个内部类都能独立地继承一个接口的实现,内部类解决java只能单继承
- 成员内部类:位于外部类成员位置的类,可以使用外部类中的成员变量和成员方法;
- 静态代理和动态代理的区别:
- 静态代理:由程序员创建或者由特定工具自动生成源代码,在程序运行前,代理类的.class文件就存在;
- 在程序运行时,运用反射机制动态创建;
- Java反射机制
- 在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个属性和方法;
-
集合
- HashMap和HashTable的区别
- HashMap支持key和value为null,HashTable不允许,是因为HashMap对null进行了特殊处理,将null的hashCode值定为0
- hashMap不是线程安全,HashTable是线程安全的;
- hashMap实现线程安全的方式为Collections.synchronized(new HashMap());
- HashMap默认长度为16,扩容为原先的2倍,HashTable默认长度为11,扩容为原来的2n+1;
- HashMap继承AbstractMap,HashTable继承自Dictionary
- HashMap和HashSet的区别
- HashMap实现了map接口,hashmap存储键值对;hashset实现了set接口,仅存储对象;
- Hashmap使用put()方法添加,使用键对象来计算hashcode值;hashset是使用add()方法将元素放入set中,使用成员对象来计算hashcode值;
- Hashmap比较快,因为是使用唯一的键来获取对象,hashset比较慢,因为要比较两个对象是否相同;
- Hashmap和Hashset如何判断元素重复
- Hashmap可以判断key是否相同,也可以判断value是否相同;
- Hashset不能添加重复的元素,当调用add()方法时,首先会计算hashCode值,如果相同就调用equals方法看是否返回true,如果为true说明元素已经存在;
- Arraylist和vector的区别
- Vector加入了同步机制,线程安全,arraylist线程不安全;
- Vector加入了synchronized同步机制,效率相对慢一点;
-
网络相关面试题
- TCP/IP五层模型:应用层,传输层,网络层,数据链路层,物理层
- 三次握手
- 第一次:建立连接时,客户端发送syn包(seq=j)到服务器,并进入SYN_SENT状态,等待服务器确认;
- 第二次:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k);
- 客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
- 为什么是三次而不是两次:为了数据传输,TCP协议的通信双方都必须维护一个序列号,三次握手的过程即通信双方互相告知序列号起始值的过程;
- 握手过程中包中不包括数据,握手完毕之后,才开始传送数据.
- 四次挥手
- 第一次:客户端发送报文告诉服务器没有数据要发送了;
- 第二次:服务器收到,告诉客户端收到
- 第三次:服务端向客户端发送报文,请求关闭连接;
- 第四次:客户端收到关闭连接的请求,向服务端发送报文,服务端关闭连接;
- 为什么不是三次:第二次挥手只是确认客户端的结束报文段,不代表服务端的数据已经传输完毕了,这个传输完毕的时间不确定,可能会使得客户端长时间得不到响应,造成资源浪费
- TCP和UDP的区别
- TCP面向连接,UDP无连接;
- TCP提供可靠的服务,UDP不保证可靠交付;
- TCP面向字节流,UDP面向报文;
- TCP全双工可靠信道,UDP是不可靠信道;
- TCP连接是点到点,UDP是支持一对一,多对多;
- http协议
- 基于请求和响应模式的无连接,无状态,应用层协议
- 简单快速:协议简单,通信速度快;
- 灵活:成功传输任意类型的数据对象,由Content-Type标记;
- 无连接:每次处理一个请求,处理完成即断开;
- 无状态:对事物处理没有记忆功能;
- http是应用层的协议,底层基于TCP/IP协议
- get和post的区别
- Get参数通过url传递,post是在requset body中传递;
- Get在url传递的参数有长度限制,post没有;
- Get参数会暴露在url上,安全性不如post;
- Get只接受ASCII字符,post无限制;
- Get回退时无害,post会再次请求;
- Get只能进行url编码,post支持多种编码方式;
- https
- 是http的安全版;
- 应用层http和传输层tcp中间加多了一层TLS/SSL加密套件,https就是应用层将数据给到TLS/SSL,然后将数据加密后,再给到TCP进行传输;
- 加密算法
- 对称加密:加密和解密是同一个密钥;(代表:DES)
- 非对称加密:A和B各有两把钥匙,一把公钥,一把私钥,A用B公钥进行加密,B用B的私钥进行解密,反过来一样;(代表:RSA)
- Volley
- 2.3之前使用HttpClient,之后使用HttpUrlConnection;
- 适合进行数据量不大但通信频繁的网络操作;对于大数据量的操作,比如文件下载,表现很糟糕,因为volley会把返回的流导入内存中,下载大文件会发生内存溢出;
- Volley执行的过程:默认情况下,volley会开启四个网络调度线程和一个缓存调度线程;首先请求会加入缓存队列,缓存调度线程如果找到该请求的缓存就直接读取该缓存并解析,否则,这个请求就加入网络队列;
-
- Volley为什么不适合上传大文件:适合小且频率高的?
- Volley的网络请求线程池默认大小为4,可以并发4个请求,大于4个,会排在队列中,并发量小所以适合数据小频率高的请求;
- 下载文件会把流存入内存中,导致内存溢出;
- Volley为什么不适合上传大文件:适合小且频率高的?
- Okhttp
- 最大并发量为64
- 使用连接池技术,支持5个并发的socket连接,默认keepAlive时间为5分钟,解决tcp握手和挥手的效率问题;
- 利用相应缓存避免重复的网络请求;
- 请求失败,自动重连;
- 支持SPDY协议,是一种基于TCP的应用层协议;
- Okhttp的缺点:
- 消息回来需要切到主线程自己去写;
- 调用比较复杂,需要进行封装;
- 缓存失败:为了信息传输的安全性,对请求进行加密.可能导致缓存系统失效;
- Okhttp用到哪些设计模式:
- Builder设计模式,如构建对象OkhttpClient,参数类型相同且很多参数可以为空时,可以用Builder模式完成;
- 工厂方法模式:如源码中的接口Call;
- 单例模式
- 观察者模式如EventListener,监听请求和响应;
- 策略模式;
- 责任链模式,如拦截器;
- Retrofit
- Retrofit底层是基于okhttp实现的,使用运行时注解的方式工作;
- 通过java接口与注解来描述网络请求,并用动态代理生成网络请求的request,然后通过client来调用相应的网络框架(默认为okhttp)去发起网络请求,并将返回的response通过converterFactory转换为相应的数据model,最后通过callAdapter转换成其他数据方式;
- 流程:
- 解析注解->配置网络请求参数;
- 动态代理->生成请求对象;
- 网络请求适配器->将请求对象进行平台适配;
- 网络请求执行器->发送请求;
- 数据转换器->解析返回数据;
- 回调执行器->切换线程;
- 主线程->处理结果;
- 优点
- 可以配置不同的http client来实现网络请求,如okhttp,httpclient;
- 参数注解可以定制;
- 支持同步,异步,rxjava;
- 超级解耦;
- 可以配置不同的反序列化工具来解析数据,如json,xml;
-
性能优化
- 图片如何三级缓存?
- 三级缓存:加载某张图片时,先去LruCache中寻找图片,没有则去SoftReference中取出图片使用,没有则去文件系统中寻找,没有则去网上加载图片;
- Lrucache是存储最近,最后使用的图片,最近最少使用的会被移除出去;当被移除的时候,会将图片添加到softreference中,软引用不会影响系统运行;
- Bitmap如何处理大图,如何预防OOM?
- 调整图片大小;
- BitMapFactory的Options参数,通过inSampleSize参数可以对一个图片进行采样缩放;
- 及时回收不用的图片资源:将暂时不用的图片recycle();
- 缓存图片到内存中:LruCache专门处理图片缓存;
- 调整图片大小;
- 内存回收机制和GC算法?
- 内存判定对象可回收的两种机制:
- 引用计数算法:被引用计数加一,引用失效,计数减一,计数器为0就是不可以再被使用的,难以解决对象之间相互循环引用;
- 可达性分析法:通过一系列GCRoots的对象作为起始点,从这些节点向下搜索,剩余的就是不可达的;
- GC回收算法:
- 分代收集算法:根据对象存活周期,将Java堆分为新生代和老年代
- 新生代:大批对象死去,只有少量存活,使用复制算法只需复制少量存活对象即可;
- 复制算法:把可用内存把容量划分为大小相同的两块,每次只使用其中一块,当这一块内存用尽后,把还存活着的对象复制到另一块,再把这一块内存空间一次清理掉,
- 老年代:对象存活率高,使用标记-清理算法或者标记整理算法,只需标记较少的回收对象即可
- 标记-清理算法:首先标记出所有需要回收的对象,然后统一清理;但会导致导致大量的内存碎片;
- 标记-整理算法:标记出所有需要回收的对象,然后把所有存活的对象整理到一端,不会产生内存碎片;
- 新生代:大批对象死去,只有少量存活,使用复制算法只需复制少量存活对象即可;
- 分代收集算法:根据对象存活周期,将Java堆分为新生代和老年代
- 内存判定对象可回收的两种机制:
- 内存溢出和内存泄漏的区别:
- 内存溢出:程序在申请内存时,没有足够内存空间供其使用,出现out of memory,Android系统为每一个应用申请到的内存有限;
- 内存泄漏:指程序在申请内存后,被某个对象一直持有,无法释放已经申请的内存空间;内存泄漏的累积后果很严重;
- 内存溢出的原因
- 发生地点:堆内存和java虚拟栈中
- 堆内存溢出:
- 生产者消费者模型,
- 如注册后忘记注销;
- 添加到队列,忘记控制队列大小;
- 循环引用
- Fastjson转json
- 生产者消费者模型,
- 栈内存溢出:递归
- 内存泄漏的原因
- 根本原因:对象的生命存活时间不一致
- 发生地点:方法区,堆,java虚拟栈
- 单例模式
- 案例:单例构造方法中传递的context为某一个activity,在activity中调用了单例的构造方法;
- 泄漏原因:activity生命周期比单例类短,无法释放,导致泄漏;
- 解决:Context传递应用Application的Context,即单例对象的生命周期和Application一样长,不会出现内存泄漏的问题;
- 静态变量导致的内存泄漏:
- 案例:一个activity定义了一个静态变量;
- 泄漏原因:静态变量存储在方法区,生命周期从类加载到整个进程结束,一旦静态变量初始化后,他所持有的引用,知道进程结束后才会释放;
- 解决方法:在onDestroy中将静态变量赋值为null;
- 非静态内部类导致的内存泄漏:
- 案例:
- Handler里面调用Activity的方法,Handler会被底层的ThreadLocal绑定,所以生命周期比Activity周期长;
- activity中开了一个线程去请求数据,然后退出activity ,因为线程持有activity的引用,导致activity不能及时回收;
- 泄漏原因:非静态内部类默认会持有外部类的引用,当非静态内部类对象的生命周期比外部类对象的生命周期长时,就会导致内存泄漏;
- 解决方法:Handler提取出来,引用Activity时用WeakReference包装起来;或者把Handler声明为静态类.因为静态内部类不会持有外部类的引用;
- 案例:
- 未取消注册回调导致的内存泄漏
- 在activity中注册广播,在销毁activity时没有取消注册;
- 泄漏原因:广播会一直存在系统中,一样持有activity的引用,导致内存泄漏;
- 解决方法:及时取消注册;
- 集合中的对象未清理造成内存泄漏:
- 案例:一个对象被添加到了集合中,该对象是不能被gc回收的;
- 资源未关闭导致内存泄漏:
- 案例:WebView
- 没有有效利用已有的对象,容易造成泄漏,例如,使用过的Bitmap对象没有调用recycle()方法释放内存,构造Adapter没有使用缓存的convertView对象;
- 哪些工具检测内存泄漏:
- DDMS-Heap
- 实时查看程序中的内存的使用信息;
- MAT分析方式
- Histogram,列出内存中每个对象的名字,数量和大小;
- Dominator Tree将所有内存中的对象按大小排序;
- DDMS-Heap
- 如何优化ListView
- 复用convertView
- 当convertView不为空时对其进行复用;
- 定义存储控件引用类ViewHolder
- 在第一次创建时convertView将控件找出,在第二次复用时,直接使用convertView的getTag()获得这些控件;
- 数据的分批和分页加载
- 分批加载
- 分页加载
- 图片的处理
- 要用WeakReference来存储与获取图片信息;
- 获取到图片时,对图片进行压缩存在内存中;
- 在getView()方法中做图片转换时,产生的中间变量要及时释放;
- 复用convertView
-
View相关
- View的绘制过程:
- OnMeasure,测量视图大小,从顶层父View到子View递归调用measure方法,measure又回调onMeasure();
- OnLayout():确定View的位置,进行页面布局;从顶层父View向子View递归调用view.layout方法,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上;
- onDraw():绘制视图,
- 绘制背景background.draw(canvas)
- 绘制自己(onDraw)
- 绘制children(dispatchDraw)
- 绘制装饰(onDrawScrollBars)
- View事件分发机制:
- DispatchTouchEvent:用于进行事件的分发;如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent的影响,表示是否消耗当前事件;
- onInterceptTouchEvent:在上诉方法内部调用,对事件进行拦截,该方法只在ViewGroup中有;一旦拦截,则执行ViewGroup中的onTouchEvent,在ViewGroup中处理事件,而不接着分发View.
- onTouchEvent,在dispatchTouchEvent中调用,用来处理点击事件,返回结果表示是否消耗当前事件;
- 如何解决View的事件冲突:
- 外部拦截法:点击事件都要经过父容器的拦截处理,如果父容器需要此事件就拦截,否则不拦截,需要重写父容器的onInterceptTouchEvent方法,在内部做出相应的拦截;
- 内部拦截法:指父容器不拦截任何事件,将所有事件都传递给子容器,如果子容器需要就直接消耗,否则交由父容器进行处理,需要配合requestDisallowInterceptTouchEvent方法;
- Scroller怎么实现View的弹性滑动?
- 在MotionEvent.ACTION_UP事件触发时调用startScroll()方法,该方法并没有进行实际的滑动操作,而是记录滑动相关量
- 接着调用invalidate/postinvalidate()方法,请求view重绘,导致view.draw方法被执行;
- 在view重绘后会在draw方法中调用computeScroll方法,而computeScroll又会向Scroller获取当前的scrollX和scrollY,然后通过scrollTo实现滑动;接着又调用postInvalidate方法进行第二次重绘,和之前一样,反复导致View进行小幅度的滑动;
- Invadate和postinvalidate的区别:
- Invalidate和postinvalidate都用于刷新View,主要区别是:
- invalidate在主线程中调用,若在子线程中需要配合handler;
- Postinvalidate可在子线程中直接调用;
- SurfaceView和View的区别
- View要在UI线程进行刷新,SurfaceView可以在子线程中进行刷新;
- View适合于主动刷新,Surface适合于被动刷新,如频繁刷新,而View频繁刷新可能会阻塞主线程;
- SurfaceView底层双缓冲机制,而View没有,适合于频繁刷新,刷新时数据处理量很大的页面(如视频播放界面)
- 自定义View如何考虑机型适配
- 合理使用wrap_content,match_parent
- 尽可能使用RelativeLayout
- 针对不同的机型,使用不同的布局文件放在对应的目录下,android会自动匹配;
- 尽量使用.9图片;
- 使用与密度无关的像素单位dp,sp;
- 切图的时候切分辨率大的图;
-
进程
- 如何开启多进程?
- 在AndroidManifest中给四大组件指定属性,android:process开启多进程模式;
- 在内存允许的条件下可以开启N个进程;
- 为何需要IPC?多进程通信可能出现的问题
- 通过开启多进程获取更大内存空间,两个或多个应用之间共享数据
- 造成的问题:
- 静态成员和单例模式完全失效;独立的虚拟机造成;
- 线程同步机制完全失效;独立的虚拟机造成;
- SharePreferences的可靠性下降;Sp不支持两个进程并发进行读写,有一定几率导致数据丢失;
- Application会多次创建;新进程会分配独立的虚拟机;
- 进程间通信的方式:
- Intent:只能传输Bundle所支持的数据类型,四大组件之间的通信
- AIDL:Android中最常用的,使用稍微复杂,需要注意线程同步;
- ContentProvider:进程间的大量数据共享,主要对外提供数据的CRUD操作;
- Socket:常用于网络通信中,只能传输原始的字节流;
- Binder的优点
- 传输效率高,可操作性高:传输效率影响因素主要是内存拷贝的次数,Binder只需一次拷贝;
- 实现C/S架构方便:Server和Client端独立,稳定性较好;
- 安全性较高;
-
其他的补充知识点
- Service启动方式
- 通过startService(),会经历onCreate->onStartCommand,stopService的时候会调用onDestroy,如果是调用者直接退出而没有调用stopService时,Service会一直在后台运行,下次调用者起来仍然可以stopService;
- 通过bindService,Service只会运行onCreate,这个时候调用者和Service绑定在一起,调用者退出Service也会onUnbind->onDestroy;
- 如果先是start,然后bind时就直接onbind
- 如果先是bind,再start,就直接startCommand
- 两个同时用,需要先onUnbind -> 然后StopService();
- EventBus的理解
- EventBus是一款Android优化的发布/订阅消息总线,它简化了组件和组件,组件和后台线程间的通信
- 三个元素:
- Event:事件
- Subscriber:事件订阅者,接收特定的Event事件
- Publisher:事件发布者,同于通知Subscriber有事件发生
- 实现了观察者设计模式,优点为代码简洁优雅,使用方便,开销小,有助于代码解耦;
- 生命周期的执行:
- Activity A到Activity B的:A的onPause() -> B 的onCreate() -> onStart() -> onResume() -> A的onStop();
- 从B返回A:B的onPause() -> A的onRestart() -> onStart() -> onResume() -> B的onStop() -> onDestroy();
- 退出A : onPause() -> onStop() ->onDestroy();
- 属性动画(Property Animation)和(Tween Animation)的区别:
- 补间动画只是改变View的显示效果,不会真正改变View的属性;属性动画会改变View的实际属性值;
- 补间动画只是针对于View,属性动画可以不作于View;
- ListView和RecyclerView的区别;
- ViewHolder:ListView中ViewHolder需要自己定义,也可以不定义;recyclerview中viewholder必须定义;
- LayoutManager:R提供了根据了更加丰富的布局管理,包括linearlayoutmanager支持水平和竖直方向;StaggeredGridLayoutManager支持瀑布流;GridLayoutManager支持网格展示;
- ItemAnimator:R中用于添加,删除,移动的动画效果;
- ItemDecoration:R默认不加间隔符,使用这个来实现间隔符;
- Java中的集中变量修饰符
- Public修饰的变量为公有变量,如果公有变量又在一个共有类中,这个变量可以被所有包中的所有类访问;
- Protected保护访问修饰符:若在一个共有类中,可以被所在类本身,同一个包中的所有类,其他包中该类的子类;
- 默认访问修饰符:同一个包中的所有类;
- Private:所在类访问;
- ANR
- Application Not Responsing ,即应用无响应;
- 原因:
- 当前的事件没有机会得到处理;
- 当前事件正在处理,没有及时完成;
- 案例:
- 点击事件或者键盘输入事件在5s内无法得到响应;
- BroadCastReceiver的onReceive()函数运行在主线程中,10s内无法得到处理;
- 前台服务在20s无法完成处理,后台服务200s内没有执行完毕;
- ContentProvider的publish在10s内没有完毕;
- 解决:
- 把io操作放在工作线程中处理,减少耗时操作和错误操作,如网络请求,数据库操作,Socket操作,文件读写等放在子线程;
- 主线程的Looper.loop()一直无限循环为什么不会造成ANR?
- looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop();也就说我们的代码其实就是在这个循环里面去执行的,当然不会阻塞了。
- 一个线程中有几个Handler,几个Looper,几个MessageQueue
- 一个线程只能有一个Looper,因为Thread中使用ThreadLocal<Looper>去存储当前线程的Looper,ThreadLocal底层是hashmap,key值是线程本身,所以只有一个looper;
- 一个messageQueue,因为一个线程只有一个looper
- 一个线程可以存在多个handler,可以在发送消息的时候使用message.setTarget(handler)来设置当前message的target handler;
- 数据库如何高效插入数据
- 在try中开启事务
- 在finally中关闭事务
- Activity的状态保存和恢复
- 保存:当Activity A 在前台时,按下home键,切换其他的应用,横竖屏切换,系统会调用onSavedInstanceState()方法;
- 恢复:onRestoreInstanceState()
- Fragment的add和remove
- Remove是移除fragment,当fragment不加入back stack时,remove会一直调用到onDetach,加入到back stack时,会走到onDestroyView;
- 当fragment被另一个fragment调用replace时,并且压入back stack时,View会销毁,fragment本身没有销毁,也就是调用到了ondestroyView();
- Fragment的oncreateview多次执行,需要用一个rootView记录一下oncreateView返回的View,下一次调用判断一下rootView是否为null
- Fragment之间数据的传递
-
- MainFragment mainFragment =
- (MainFragment) getActivity()
- .getSupportFragmentManager()
- .findFragmentByTag("mainFragment");//另一个fragment的tag
- 接口回调
- EventBus
- 引入EventBus:添加依赖
- 注册事件接收者,在接收Fragment中进行注册EventBus.getDefault().register(this);
- 发送事件: EventBus.getDefault().post(mDatas.get(position));
- 定义事件类型
- 接收事件并处理:
- @Subscribe
- public void onEvent(String data) {
- bt_main.setText(data);
- }
- 注销事件接收:
- @Override
- public void onDestroy() {
- super.onDestroy();
- EventBus.getDefault().unregister(this);
- }
-
- Activity与Service之间
- 通过startService()来启动服务,不过不好掌控
- 使用bindService(),就可以在ServiceConnection中获取Service的实例,就实现了activity中获取到service的实例对象,就可以调用service方法;
- 对ContentProvider的理解
- 结构化方式存放的数据,以相对安全的方式来封装数据并且提供简易的处理机制,提供了不同进程间数据交互的标准化接口;
- 说说ContentProvider、ContentResolver、ContentObserver 之间的关系
- ContentProvider是内容提供者,对其他应用进行数据共享,其他应用可以通过ContentProvider对你应用中的数据进行增删查改;
- ContentResolver内容解析者,作用是按照一定的规则对内容提供者的数据解析;
- ContentObserver:内容观察者,目的是观察特定uri引起的数据库的变化,既而做一些相应的处理,类似于数据库中的触发器;
- 描述一下BroadcastReceiver的理解
- 使用了设计模式中的观察者模式,基于消息的发布/订阅事件模型,将广播的发送者和接收者解耦;
- 注册的方式:
- 静态注册:在AM中注册,不受任何组件的生命周期影响;
- 动态注册:组件结束前,必须注销;
- 最好在onresume中注册,onpause中注销,否则造成内存泄漏;
- 方式广播:intent通过sendBroadCast发送出去;
- 类型:
- 普通广播:开发者自身定义intent的广播
- 系统广播:涉及到手机的开机,网络变化,拍照等都会相应的广播;
- 有序广播:广播接收者按照先后顺序来接收;按照priority属性大小排序,相同的话就是动态注册的优先;
- 本地广播:安全性高,效率高;exported设置为false,非本应用的广播不接收,指定该广播接收器所在的包名,使用封装好的LocalBroadcastManager使用方式与全局广播相同,只是注册和注销的context变为了LocalBroadcastManager的实例;
- 如何导入已有的数据库
- 把源数据库放入res目录下,建立一个dbmanager,然后通过FileinputStream读取数据库,再用FileOutputStream把读取到的数据写入到data/data/包名下;
- LinearLayout和RelativeLayout的区别:
- RelativeLayout比LinearLayout慢是因为会让子View调用两次measure过程,而LinearLayout只需要一次,如果LinearLayout有weight属性时,也需要两次;
- 不影响层级深度的情况下,使用LinearLayout和FrameLayout而不是Relat..;
- 尽量使用padding而不是margin
- 屏幕适配的知识点
- 屏幕尺寸:指对角线的长度,单位是英寸,一英寸=2.54厘米
- 屏幕分辨率:1px=1像素,屏幕分辨率越高,显示效果越好;
- 屏幕像素密度:每英寸的像素点数;计算公式为竖的分辨率的平方+横的分辨率的平方开根号然后除以英寸数;
- 常见的
- Mdpi:像素密度:120-160 hdpi 160 -240 xhdpi 240 -320 xxhdpi 320 - 480 xxxdpi 480-640
- 常用适配方案:
- 使用wrap_content,match,weight
- 自定义像素适配,以美工的设计尺寸为原始尺寸,根据不同设备的密度来计算出宽和高;
- 百分比适配
- 插值器和估值器
- 插值器:Interpolator:一个接口,设置属性值从初始值到结束值的变化规律,
- 在动画效果的XML代码中设置插值器属性
- 在java代码中设置
- 估值器:TypeEvaluator,一个接口,插值器决定值的变化规律,即决定的是变化趋势,接下来的具体变化数值交给估值器;
- 属性动画特有的属性;
- 协助插值器实现非线性运动的动画效果;
- 插值器:Interpolator:一个接口,设置属性值从初始值到结束值的变化规律,
- Android的数据存储方式
- SharePreference,SQLite,Content Provider,File,网络存储
- SharePreference本质是一个xml文件,存储一些参数设置
- SQLite轻量级的数据库支持SQL语法,Android还提供了一个SQLIteDataBaseHelper的类,提供了一些操作数据库的api;
- Content Provider 实现数据共享;
- FIle: IO存储方式,用于存储数量大的数据,更新数据很难;