Adnroid面试基础

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进行管理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值