1:android 启动模式
1.1:standard 模式
默认的启动模式,每次创建都会生成新的activity实例。没有特殊需求直接使用默认的就行。
1.2 singleTop 模式
根据栈顶是否有该activity实例,如果有则直接重用该实例,这时,intent的参数应该从onNewIntent()方法中获取。
否则,则生成新的实例。
该模式可适用与阅读类或者商品类详情页,亦或者推送的跳转页面。
1.3singleTask 模式
根据栈中是否有实例,如果有则重用该实例,并将该activity上面的实例清出栈,使其位于栈顶,否则就重新生成实例。
主页面可使用该mode。
1.4singleInstance 模式
生成一个新的栈,且这个栈中只有这一个activity实例。
2:单例模式
2.1:懒汉式
public class SingleTon {
private static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
从上数代码我们可以看到,实例化被延迟加载,从而节约资源,但是当处于多线程环境时,如果多个线程同时进入uniqueInstance == null时,就会多次实例化对象。所以,懒汉式是线程不安全的。
2.2:线程安全的懒汉式
那么如何保证同一时间,只有一个一个线程进入该方法呢,答案是:加锁。
public static synchronized Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
但是这时候会有一个问题,当一个线程进入后,别的想要进入这个方法的线程都需要等待,即使是uniqueInstance 已经实例化。所以这样会造成阻塞时间过长,从而造成性能问题。
2.3:双重校验锁
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
首先判断uniqueInstance 是否初始化,只有初始化了才需要加锁判断。
如果两个线程同时满足uniqueInstance == null ,进入if语句,虽然语句添加了同步锁,但是只是满足了线程的先后,还是会初始化两次。
所以,在同步语句中依然需要判断 uniqueInstance ==null。
也就是说
第一个判断条件避免初始化后的加锁操作,
第二个则判断条件则避免出现多次初始化的问题。
接着我们看下uniqueInstance = new Singleton()。
初始化对象是非原子操作,可以抽象成三步jvm指令来看:
1:分配对象的内存空间
memory =allocate();
2:初始化对象
ctorInstance(memory);
3:设置uniqueInstance 指向刚分配的内存地址
uniqueInstance =memory;
其中,2初始化对象依赖于1,但是3指向内存地址则不依赖于2.
所以,jvm是可以对其进行指令重排的。
改完后顺序变成1-3-2、
也就是将uniqueInstance 指向分配内存地址放到了初始化的前面,这时,如果线程1在初始化之前,就将其分配给uniqueInstance 引用,而正好线程2进入方法判断uniqueInstance 引用不为null,直接返回就会出错。
所以对于这种情况,我们需要使用volatile关键字修饰uniqueInstance 。
private volatile static Singleton uniqueInstance;
volatile关键字可以禁止指令重排。
2.4:饿汉式
饿汉式单例是直接对对象进行初始化,所以也就避免了多次初始化的线程不安全问题,但是却不能做到延迟实例化节约资源。
public class SingleTon {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {
}
public static Singleton getUniqueInstance() {
return uniqueInstance;
}
}
3:双缓冲机制
问题由来:
cpu访问内存的速度要远远高于访问屏幕的速度。如果要绘制多个复杂的布局,每次都访问内存获取图形,在一次次绘制到屏幕从而多次访问屏幕,会十分耗时。
第一层缓冲:
先在内存中将所有图形绘制到bitmap中,然后再将其一次性绘制到屏幕。
android 中onDraw()就实现了这种缓冲,因为它不是绘制一下就显示下,而是绘制完成后全部显示出来。
第二层缓冲:
因为onDraw是在ui线程,所以如果这里进行大量操作,是十分耗时的,所以第二层缓冲,则是创建另一个缓存对象存储第一层缓冲的bitmap,然后将这个缓存对象在传到ondraw中。
类似surfaceView的实现,则是使用创建子线程,然后通过lockCanvas获取canvas来绘制,然后通过
SurfaceHolder的unlockCanvasAndPost方法释放canvas并提交更改。
4:handler内存泄漏
首先我们先看下常用的handler的一段代码:
Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
//更新UI
super.handleMessage(msg);
}
};
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
//耗时操作
handler.sendEmptyMessage(0);
}
}).start();
如上述代码,如果当网络请求或者耗时操作正在进行,用户就直接退出activity,那么这时,虽然界面销毁,但是后台任务仍然进行,由于线程中引用当前activity对象handler,所以activity并不会被回收,这时就有两个问题,第一:当后台任务执行完毕,更新ui操作时,会报空指针。第二:由于activity不能被回收,一直占用资源。当用户来回切换activity时,资源占用越来越大,最终必会导致oom。
那么如何解决呢?
首先:当activity销毁时,我们可以直接停止后台线程任务。
其次:应该使用handler的removeCallbacks方法,把消息对象从消息队列移除。
另外,我们可以使用静态内部类,这样就不持有外部类的对象,activity就可以回收了,
private MyHandler mHandler=new TestHandler();
static class MyHandler extends Handler{
@Override
public void handleMessage(Message msg) {
//更新界面操作
super.handleMessage(msg);
}
}
如上代码,虽然解决了activity回收的问题,但是我们无法更新ui了。
这时,我们可以使用弱引用,传入当前的activity。
private WeakReference<xxx> mContext;
public MyHandler(xxx activity){
mContext=new WeakReference<>(activity);
}
5:android进程
前台进程:
正在与用户交互的activity或者activity用到的进程,此进程在内存不足的情况下最后被杀死。
可见进程:
处于onPaused状态的activity或者绑定在上面的service,也就是说被用户可见但是失去焦点,无法交互。
服务进程:
正在运行但不可见,例如:正在下载但不在下载界面。
后台进程
运行执行者onstop的service,用户不在关心,如退出app。
空进程
不包含任何应用组件的进程,系统需要内存时,最先杀死。
android app默认只有一个进程,进程名称就是包名。
6:数组和链表区别
1:数组内部是连续的一块内存;链表在内存中可以存在任何地方,不要求连续,是通过指针指到下一个元素的。
2:数组需要预留空间,所以有可能会浪费内存空间。
3:数组插入删除慢,查询快,可以通过下标迅速访问元素;链表插入删除快,查询慢,不能随机查找。
4:数组指定大小,不利于扩展。链表大小不用定义,数据随意增删。
7:类中方法顺序
静态代码块—>非静态代码块—>构造方法
public class MyTest {
public static void main(String[] args) {
Father father = new Child();
father = new Child();
}
}
class Father{
public Father(){
System.out.println("父类:构造");
}
static {
System.out.println("父类:静态初始化块");
}
}
class Child extends Father{
public Child(){
System.out.println("子类:构造");
}
static {
System.out.println("子类:静态初始化块");
}
执行顺序:
8:TCP三次握手和四次挥手
三次握手:
第一次:client将标志位SYN(同步序号)设置为1,同时产生一个随机序号seq =k,
然后将数据包发给server
第二次:server将SYN=1,同时也生成一个随机seq=j,ACK=1(确认序号标志),ack=k+1,发送给client。
第三次:client将ACK=1,ack=j+1,发送给server。
当第一次握手后,如果时间过长,客户端不需要这次连接了,服务端就建立了连接,无疑是浪费资源的。
四次挥手:
第一次:client发送请求,FIN=1,seq=k;
第二次:server接收到seq=k后,发回client的是ACK=1,ack=k+1,同时生成seq=j。这时server如果有数据继续传送。
第三次:当没有数据传输时,server继续发送ACK=1,ack=k+1,seq=w,FIN=1.
第四次:client收到seq=w,发送新的到server:ack=w+1,seq=k+1,ACK=1.
9:jvm内存管理
1:方法区:
方法区存放了类的相关信息,如类名,修饰符,常量,方法等,另外还有静态变量,构造函数,final修饰的常量等。方法区是全局共享的,当方法区超过允许的大小时,就会抛出OOM.
2:堆
堆区由所有线程共享,当虚拟机启动时创建。
这里主要存放对象实例和数组,所有new出来的对象,都存放在堆区。
3:虚拟机栈
虚拟机栈占用的是系统内存,每个线程私有。虚拟机相关的异常有栈溢出和OOM.当线程调度的栈深度大于虚拟机限定的值时,就会抛出栈溢出。不过大多数虚拟机可以动态扩展,当扩展到一定程度,超出内存时,就会引发OOM.
4:本地方法栈
本地方法栈用于支持native方法,记录了native方法的执行状态。
5:程序计数器
程序计数器是一块很小的内存区域,位于cpu上,每个程序计数器只记录一个线程的行号,所以它是线程私有的。字节码解释器工作时,就是通过修改程序计数器的值来取得下一条指令的。
10:GC相关
当垃圾回收执行的时候,其余线程都会被停止,所以尽可能让垃圾回收执行的时间短。
运行的条件:
1:GC在优先级最低的线程中执行,当没有应用运行时被调用。
2:java堆内存不足时,jvm会强制调用gc线程,
判断对象是否存活:引用计数算法和可达性分析算法(根搜索算法)
经典算法:引用计数算法将对象添加进计数器,当对象被引用,计数器+1,失去引用则-1,一段时间内该对象的计数为0时,则该对象可以被回收。
缺点:当两个对象相互引用,则无法回收。
Object obj1=new Object();//obj1引用计数=1;
Object obj2=obj1;//obj1引用计数+1;
Object obj3=new Object();
obj2=null;//obj1引用计数-1;
obj1=obj3;//obj1引用计数=0;可以回收。
根搜索算法:从GC roots(根节点)向下搜索,如果有一个对象无法到达gc roots,则说明这个对象可以回收。
java中作为gc root的对象包括:
1:虚拟机栈中引用的对象
2: 方法区中静态属性引用的对象
3:方法区中常量引用的对象
4:本地方法栈中native引用的对象。
gc分类:
新生代:新生代的目标就是尽可能的快速的收集掉生命周期短的对象。一般情况下,新创建的对象都会存入新生代。
新生代的内存按照8:1:1的比例分为一个eden区(伊甸园
)和两个survior(幸存者)区。
大部分的对象在eden中生成,当触发mimor gc(新生代的gc),先使用复制算法,将eden区的对象复制到survior0中,然后清空eden区,如果survior0也满了时。就将eden区和survior0中存活的对象转入survior1中,然后交换survior0和survior1(下次触发gc时,扫描eden和survior1),此时survior0位空,如此循环、
当survior1不足以存放eden和survior0中存活的对象后,则将存活对象直接存入老年代。
新生代的minor gc比较频繁。full gc频率较低、
老年代:存放生命周期比较长的对象,如在minor gc触发了多次都没有清理掉的对象,老年代的内存也相对年轻代较大,差不多2:1。当老年代的内存满时就会触发full gc。
持久代:用于存放静态文件,如java类,方法。
类加载器:
启动类加载器:负责将java_home\lib下,并且虚拟机识别的类库加载到虚拟机内存中。
扩展类加载器:负责将java_home\llib\ext的所有类库加载到虚拟机内存,扩展类记载器开发者可以直接使用。
应用程序加载器:由App-ClassLoader实现,这个类加载器可以通过getSystemClassLoader()获取,所以也可以叫做系统类加载器。
它负责将classPath下的类库加载。
类加载机制:
双亲委派:当一个加载器收到了类加载的请求,首先不会自己尝试加载这个类,而是抛给父类加载器,每一个层次的类加载器都是如此,所以最后都是传送到顶层的启动类加载器中,只有父类无法加载或者加载失败,才会自己尝试加载。
11:序列化
序列化:就是将对象转成二进制流,便于存储和传输。
serializable:可能会频繁的触发io操作,存储在磁盘上,效率较慢。
Parcelable:android 给出的一套序列化方法,将对象转成二进制流存入共享内存,别的对象读取流,反序列化成对象即可;效率高。
12:android 的内存缓存和磁盘缓存
内存缓存基于Lrucache实现;
磁盘缓存基于DiskLrucache实现;
这两个都又基于lru算法和linkHashMap实现。
lru算法:最近最久未使用。也就是说,一条数据在最近的一段时间没有被使用,则他在将来的一段时间被访问的可能性也很小,则优先清除这类数据。
linkHashMap:继承自hashmap,数组+链表的结构。
所有节点组成双向链表;
有一个accessOrder的boolean值,如果是false,则默认是FIFO算法,如果是true,则是lru访问。
13:java线程
线程的调度:协同式线程调度和抢占式线程调度。
协同式线程调度:线程的执行时间由线程控制,线程结束后,会主动通知系统切换线程。所以,实现简单,不存在线程同步,不过这样也存在一个线程导致整个进程阻塞的问题。
抢占式线程调度:java使用的就是这种方式,每个线程由系统来分配执行时间,线程的切换也不由线程本身来决定。
线程状态:
创建(new):创建后尚未启动的线程
运行(runable):这里包括操作系统线程状态的Running和ready两个状态,也就是说,处于此状态的线程可能正在执行,也可能等待cpu分配执行时间。
无限期等待(waiting):处于此状态的线程,cpu不会给他分配执行时间,需要等待其他线程唤醒。调用没有设置timeout的Object.wait()或者Thread.join()可以进入无限期等待状态。
限期等待(Timed waiting):cpu同样不会分配执行时间,不过此状态不需要其他线程显式唤醒。在一定时间后由系统自动唤醒。
Thread.sleep;设置了timeout的Object.wait()和Thread.join()方法;可以使线程进入限期等待状态。
阻塞(blocked):线程被阻塞,阻塞状态与等待状态的区别是:阻塞状态在等待获取到一个排它锁,这个事件在另一个线程放弃这个锁的时间发生。
在程序等待进入同步状态的时候,线程将进入这个状态。
结束(Terminated):线程结束执行。
14:hashmap相关
hashmap底层数组+链表;当链表长度大于8时,链表会转为红黑树.
hash碰撞:当key计算得到的hashcode相同时,就会产生碰撞。
hashmap进行put操作时,首先计算key在bucket(hash桶)中的index(如取模算法),如果index相同就会产生碰撞,以链表形式存在bucket中,如果没有碰撞并且不存在这个节点,就直接插入,否则就替换。如果bucket超过阀值,负载因子0.75,默认长度16.也就是12时会扩容至2倍.
hashmap进行get操作时,首先通过key进行hash运算,获取bucket位置,如果发生冲突在通过equals方法获取真正的value。
使用Map m = Collections.synchronizeMap(hashMap);实现hashmap的同步、
15:activity,window和view的关系
activity->phonewindow->decorView->titlebar+contentview.
activity通过setcontentview来将phonewindow和activity关联起来。
phonewindow内部包含一个viewgroup,setcontentview就是将layout设置到这个viewgroup上面,而View通过WindowManager的addView()、removeView()、updateViewLayout()对View进行管理。