2017校招面试总结

List:
|–Vector:内部是数组数据结构,是同步的。增删,查询都很慢!
|–ArrayList:内部是数组数据结构,是不同步的。替代了Vector。查询的速度快。
|–LinkedList:内部是链表数据结构,是不同步的。增删元素的速度很快。

Set:元素不可以重复,是无序。
Set接口中的方法和Collection一致。
|–HashSet: 内部数据结构是哈希表 ,是不同步的。
如何保证该集合的元素唯一性呢?
是通过对象的hashCode和equals方法来完成对象唯一性的。
如果对象的hashCode值不同,那么不用判断equals方法,就直接存储到哈希表中。
如果对象的hashCode值相同,那么要再次判断对象的equals方法是否为true。
如果为true,视为相同元素,不存。如果为false,那么视为不同元素,就进行存储。

	记住:如果元素要存储到HashSet集合中,必须覆盖hashCode方法和equals方法。
	一般情况下,如果定义的类会产生很多对象,比如人,学生,书,通常都需要覆盖equals,hashCode方法。
	建立对象判断是否相同的依据。

|--TreeSet:可以对Set集合中的元素进行排序。是不同步的。
			判断元素唯一性的方式:就是根据比较方法的返回结果是否是0,是0,就是相同元素,不存。

			TreeSet对元素进行排序的方式一:
			让元素自身具备比较功能,元就需要实现Comparable接口。覆盖compareTo方法。

			如果不要按照对象中具备的自然顺序进行排序。如果对象中不具备自然顺序。怎么办?
			可以使用TreeSet集合第二种排序方式二:
			让集合自身具备比较功能,定义一个类实现Comparator接口,覆盖compare方法。
			将该类对象作为参数传递给TreeSet集合的构造函数。

    哈希表确定元素是否相同
    1,判断的是两个元素的哈希值是否相同。
    	如果相同,在判断两个对象的内容是否相同。

    2,判断哈希值相同,其实判断的是对象的hashCode的方法。判断内容相同,用的是equals方法。

    注意:如果哈希值不同,是不需要判断equals。

集合的一些技巧:

需要唯一吗?
需要:Set
需要制定顺序:
需要: TreeSet
不需要:HashSet
但是想要一个和存储一致的顺序(有序):LinkedHashSet
不需要:List
需要频繁增删吗?
需要:LinkedList
不需要:ArrayList

如何记录每一个容器的结构和所属体系呢?

看名字!

List
|–ArrayList
|–LinkedList

Set
|–HashSet
|–TreeSet

后缀名就是该集合所属的体系。

前缀名就是该集合的数据结构。

看到array:就要想到数组,就要想到查询快,有角标.
看到link:就要想到链表,就要想到增删快,就要想要 add get remove+frist last的方法
看到hash:就要想到哈希表,就要想到唯一性,就要想到元素需要覆盖hashcode方法和equals方法。
看到tree:就要想到二叉树,就要想要排序,就要想到两个接口Comparable,Comparator 。

而且通常这些常用的集合容器都是不同步的。

Map常用的子类:
|–Hashtable :内部结构是哈希表,是同步的。不允许null作为键,null作为值。
|–Properties:用来存储键值对型的配置文件的信息,可以和IO技术相结合。
|–HashMap : 内部结构是哈希表,不是同步的。允许null作为键,null作为值。
|–TreeMap : 内部结构是二叉树,不是同步的。可以对Map集合中的键进行排序。
HashMap内部是数组
HashMapEntry[] table

@Override
public V put(K key, V value) {
if (key == null) {
return putValueForNullKey(value);
}

int hash = secondaryHash(key.hashCode());
HashMapEntry[] tab = table;
int index = hash & (tab.length - 1);
for (HashMapEntry e = tab[index]; e != null; e = e.next) {
	if (e.hash == hash && key.equals(e.key)) {
		preModify(e);
		V oldValue = e.value;
		e.value = value;
		return oldValue;
	}
}

static class HashMapEntry implements Entry {
final K key;
V value;
final int hash;
HashMapEntry next;

    HashMapEntry(K key, V value, int hash, HashMapEntry next) {
        this.key = key;
        this.value = value;
        this.hash = hash;
        this.next = next;
    }

    public final K getKey()

    public final V getValue()

    public final V setValue(V value)

    @Override public final boolean equals()

    @Override public final int hashCode()

    @Override public final String toString()

}
// No entry for (non-null) key is present; create one
modCount++;
if (size++ > threshold) {
	tab = doubleCapacity();
	index = hash & (tab.length - 1);
}
addNewEntry(key, value, hash, index);
return null;

}

vector 效率低的原因?

If you need synchronization, a Vector will be slightly faster than an ArrayList synchronized with Collections.synchronizedList. But Vector has loads of legacy operations, so be careful to always manipulate the Vector with the List interface or else you won*t be able to replace the implementation at a later time.

HashMap和HashSet的区别和分析

  1. HashSet:实现了Set接口,非同步,它不允许集合中有重复的值,底层是HashMap。当我们提到HashSet时,第一件事情就是在将对象存储在HashSet之前,要先确保对象重写equals()和hashCode()方法,这样才能比较对象的值是否相等,以确保set中没有储存相等的对象。如果我们没有重写这两个方法,将会使用这个方法的默认实现。
    public boolean add(Object o)方法用来在Set中添加元素,当元素值重复时则会立即返回false,如果成功添加的话会返回true。
  2. HashMap:实现了Map接口,Map接口对键值对进行映射。非同步,Map中不允许重复的键。Map接口有两个基本的实现,HashMap和TreeMap。TreeMap保存了对象的排列次序,而HashMap则不能。HashMap允许键和值为null。
  3. Hashtable :实现了Map接口,内部结构是哈希表,是同步的。不允许null作为键,null作为值。
    public Object put(Object Key,Object value)方法用来将元素添加到map中。
  4. HashSet和HashMap的区别
    HashMap HashSet
    HashMap实现了Map接口 HashSet实现了Set接口
    HashMap储存键值对 HashSet仅仅存储对象
    使用put()方法将元素放入map中 使用add()方法将元素放入set中
    HashMap中使用键对象来计算hashcode值 HashSet使用成员对象来计算hashcode值
    HashMap比较快,因为是使用唯一的键来获取对象 HashSet与Hashmap 存取速度基本一致,HashSet把HashMap进行了封装
    ###hashMap的工作原理是什么?
    当系统开始初始化HashMap的时候,系统会创建一个长度为capacity的Entry数组。这个数组存储的元素是一个系列元素的索引,也称为“桶”,当一个元素要增加的时候,会计算他的hashcode,然后再数组中寻找他的位置,比如,他的位置有元素占据了,那么会在该元素上,扩展出一条索引链,将数据插入到这个索引链上。

hashmap已经可以实现hashset的功能,为什么还要有hashset?

hashset底层为hashmap,为了保证单列集合,还能保证元素唯一性,方便应用,出现

面向对象

  1. 在java,c++等面向对象编程的语言中,他们将一切事物都当作对象,而将事物的特征和行为定义为对象的属性和方法,
    这样在编写程序时,重要
  2. 封装是实现面向对象程序设计的第一步,封装就是,具有有相同特性(属性)和相同行为(方法)的对象用一个类来描述。这样对象只要去调用类中方法,就可以实现功能,而不用管具体的实现。
  3. 继承主要实现重用代码,节省开发时间。
  4. 多态就是,多种形态,同一操作作用于不同的对象,可以有不同的解释,
    产生不同的执行结果。在运行时,可以通过指向父类的指针,来调用实现子类中的方法。
    分为编译时多态,运行时多态,
    编译时多态就是重载:系统在编译时,根据传递的参数、返回的类型等信息决定实现何种操作。
    运行时多态就是覆写:直到系统运行时,才根据实际情况决定实现何种操作。
    编译时的多态性为我们提供了运行速度快的特点,而运行时的多态性则带来了高度灵活和抽象的特点。

多态时成员的特点:

  1. 成员变量:编译运行都参考等号的左边。
  2. 非静态成员函数:编译参考等号的左边,运行参考等号的右边。
  3. 静态函数:编译运行都参考等号的左边。其实对于静态方法,是不需要对象的,直接用类名调用。

对象的强、软、弱和虚引用

在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及(reachable)状态,程序才能使用它。从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。

  1. 强引用(StrongReference)
    强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 ps:强引用其实也就是我们平时A a = new A()这个意思。
  2. 软引用(SoftReference)
    如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存(下文给出示例)。
    软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
  3. 弱引用(WeakReference)
    弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
    弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
  4. 虚引用(PhantomReference)
    “虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
    虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。

Java中final,finally,finalize的区别

  1. final:java中的关键字,修饰符,如果一个类被声明为final,就意味着他不能被继承,所以一个类不能同时被abstract和final修饰;如果一个变量被被声明为final,则以后的引用只能读取不能修改;如果一个方法被声明为final,则他不能被覆盖。
  2. finally:java的一种异常处理机制,finally是对java异常处理的一种补充,finally结构代码总会执行,不管有无异常发生,如果异常处理中有return语句,则在return之前执行。使用finally可以维护对象的内部状态,并可以清理非内存资源,特别是在关闭数据库连接这方面,通常把close()方法放在finally中。
  3. finalize:Object类的方法,使用该方法在垃圾回收器将对象从内存清理出去之前,做必要的清理工作,该方法是由垃圾回收器在确定这个对象没有被引用时对这个对象调用的。子类覆盖finalize()方法以整理系统资源或执行其他清理工作。finalize()是在垃圾回收器删除对象之前对这个对象掉用的。

Java抽象类和接口的区别

  1. 抽象类需要被继承,而且只能单继承;接口只能被实现,而且可以多实现。
  2. 抽象类中可以定义抽象方法和非抽象方法,子类继承后,可以直接使用非抽象方法;接口中只能定义抽象方法,必须由子类去实现。
  3. 抽象类的继承是is a的关系,在定义该体系的基本共性内容;接口的实现是like a的关系,在定义体系额外功能。

线程同步的5种方式

  1. 同步方法
  2. 同步代码快
  3. 使用特殊变量域(volatile)实现线程同步
  4. volatile关键字为域变量的访问提供了一种免锁机制。
  5. 使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新。
  6. 每次使用该域都要重新计算而不是使用寄存器中的值。
  7. volatile不会提供任何原子操作,也不能用来修饰final类型的变量。
  8. XBYTE[2]=0x55;
    XBYTE[2]=0x56;
    XBYTE[2]=0x57;
    XBYTE[2]=0x58;被volatile修饰,则编译器不做优化
  9. lock()

Java创建对象的四种方式

  1. 使用new关键字。
  2. 运用反射机制,调用java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。
  3. 调用对象的clone()方法。
  4. 运用反序列化,调用java.io.ObjectInputStream对象的readObject()方法。

String类中的equals()方法

public boolean equals(Object anObject)
{
       //如果是同一个对象
        if (this == anObject)
        {
            return true;
        }
        //如果传递进来的参数是String类的实例
        if (anObject instanceof String)
        {
            String anotherString = (String)anObject;
            int n = count;//字符串长度
            if (n == anotherString.count) //如果长度相等就进行比较
            {
                char v1[] = value;//取每一个位置的字符
                char v2[] = anotherString.value;
                int i = offset;
                int j = anotherString.offset;
                while (n-- != 0) //对于每一位置逐一比较
                {
                    if (v1[i++] != v2[j++])
                        return false;
                }
                return true;
            }
        }
        return false;
}

object类中的equals()方法:

public boolean equals(Object obj)
{
  //调用equal的对象的地址和参数对象的地址是否相等
        return (this == obj);
}

Java GC

  1. 问:垃圾回收器的基本原理是什么?可以马上回收内存吗?
    答:对于gc来说,当程序员创建对象时,gc就开始监控对象的大小,地址和使用情况。通常,使用有向图的方式来纪录和管理堆中的对象,通过有向图,gc可以知道哪些对象可达,哪些对象不可达,再对不可达的对象进行内存回收。通常,程序员可以使用System.gc()或者Runtime.getRuntime().gc()来通知gc,但是gc作为一个单独的低级别线程运行,不能保证一定会执行。

  2. GC的基本原理
    Java的内存管理实际上就是对象的管理,其中包括对象的分配和释放。 对于程序员来说,分配对象使用new关键字;释放对象时,只要将对象所有引用赋值为null,让程序不能够再访问到这个对象,我们称该对象为"不可达的".GC将负责回收所有"不可达"对象的内存空间。 对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象(详见 参考资料1 )。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的".当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。但是,为了保证GC能够在不同平台实现的问题,Java规范对GC的很多行为都没有进行严格的规定。例如,对于采用什么类型的回收算法、什么时候进行回收等重要问题都没有明确的规定。因此,不同的JVM的实现者往往有不同的实现算法。这也给Java程序员的开发带来行多不确定性。本文研究了几个与GC工作相关的问题,努力减少这种不确定性给Java程序带来的负面影响。 增量式GC( Incremental GC ) GC在JVM中通常是由一个或一组进程来实现的,它本身也和用户程序一样占用heap空间,运行时也占用CPU.当GC进程运行时,应用程序停止运行。因此,当GC运行时间较长时,用户能够感到Java程序的停顿,另外一方面,如果GC运行时间太短,则可能对象回收率太低,这意味着还有很多应该回收的对象没有被回收,仍然占用大量内存。因此,在设计GC的时候,就必须在停顿时间和回收率之间进行权衡。一个好的GC实现允许用户定义自己所需要的设置,例如有些内存有限有设备,对内存的使用量非常敏感,希望GC能够准确的回收内存,它并不在意程序速度的放慢。另外一些实时网络游戏,就不能够允许程序有长时间的中断。增量式GC就是通过一定的回收算法,把一个长时间的中断,划分为很多个小的中断,通过这种方式减少GC对用户程序的影响。虽然,增量式GC在整体性能上可能不如普通GC的效率高,但是它能够减少程序的最长停顿时间。 Sun JDK提供的HotSpot JVM就能支持增量式GC.HotSpot JVM缺省GC方式为不使用增量GC,为了启动增量GC,我们必须在运行Java程序时增加-Xincgc的参数。HotSpot JVM增量式GC的实现是采用Train GC算法。它的基本想法就是,将堆中的所有对象按照创建和使用情况进行分组(分层),将使用频繁高和具有相关性的对象放在一队中,随着程序的运行,不断对组进行调整。当GC运行时,它总是先回收最老的(最近很少访问的)的对象,如果整组都为可回收对象,GC将整组回收。这样,每次GC运行只回收一定比例的不可达对象,保证程序的顺畅运行。

  3. 问:垃圾回收的优点和原理,并考虑几种回收机制。
    3.1.标记-清除收集器
       这种收集器首先遍历对象图并标记可到达的对象,然后扫描堆栈以寻找未标记对象并释放它们的内存。这种收集器一般使用单线程工作并停止其他操作。

2.2.标记-压缩收集器
  有时也叫标记-清除-压缩收集器,与标记-清除收集器有相同的标记阶段。在第二阶段,则把标记对象复制到堆栈的新域中以便压缩堆栈。这种收集器也停止其他操作。

3.3.复制收集器
  这种收集器将堆栈分为两个域,常称为半空间。每次仅使用一半的空间,jvm生成的新对象则放在另一半空间中。gc运行时,它把可到达对象复制到另一半空间,从而压缩了堆栈。这种方法适用于短生存期的对象,持续复制长生存期的对象则导致效率降低。

3.4.增量收集器
  增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾。这会造成较小的应用程序中断。

3.5.分代收集器
  这种收集器把堆栈分为两个或多个域,用以存放不同寿命的对象。jvm生成的新对象一般放在其中的某个域中。过一段时间,继续存在的对象将获得使用期并转入更长寿命的域中。分代收集器对不同的域使用不同的算法以优化性能。

3.6.并发收集器
  并发收集器与应用程序同时运行。这些收集器在某点上(比如压缩时)一般都不得不停止其他操作以完成特定的任务,但是因为其他应用程序可进行其他的后台操作,所以中断其他处理的实际时间大大降低。

3.7.并行收集器
  并行收集器使用某种传统的算法并使用多线程并行的执行它们的工作。在多cpu机器上使用多线程技术可以显著的提高Java应用程序的可扩展性。

Java多线程

  • 同步函数和同步代码快比较
  1. 同步函数的锁是固定的this
  2. 同步代码快的锁是任意对象
  3. 建议使用同步代码块
  • 静态同步函数的锁
  1. 静态的同步函数使用的锁是该函数所属字节码文件对象。
  2. 可以用getClass()方法获取,也可以用 当前类名.class 表示。

wait和sleep的区别:

  1. wait可以指定时间也可以不指定,sleep必须指定时间。
  2. wait释放执行权,释放锁;sleep释放执行权,不释放锁。

停止线程的方式:

  1. Thread类中的stop()方法(已过时)。
  2. 定义循环结束标记,线程运行代码都是循环,控制循环即控制了线程。
  3. 使用interrupt(中断)方法,该方法是结束线程的冻结状态,强制使线程回到运行状态中来,让线程具备CPU的执行资格。此时会触发InterruptedException,需要处理。

守护线程(后台线程)-setDaemon()

  1. 设置一个线程为守护线程,随着主线程结束而结束。
  2. 必须在线程启动前调用。

加入线程-join()

  1. 主线程等待子线程的终止
  2. 例如主线程需要子线程的数据进行运算,则可以在运算之前,调用join()。

synchronized和lock,volitile区别

  1. synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,
  2. lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
  3. volitile没有加锁,volatile的语义, 其实是告诉处理器, 不要将我放入工作内存, 请直接在主存操作我.(工作内存详见
  4. 因此当多核或多线程在访问该变量时, 都将直接 操作 主存, 这从本质上, 做到了变量共享.
  5. synchronize线程会一直等,lock超过一定时间就不等了,干别的去了。

更改线程优先级-setPriority()

  1. 优先级总共10级(1-10)。
  2. MIN_PRIORITY = 1;
  3. NORM_PRIORITY = 5;
  4. MAX_PRIORITY = 10;

暂停线程-yield()

  1. 暂停当前正在执行的线程,并执行其他线程,当前线程仍处在可运行状态。
  2. 先检测当前是否有相同优先级的线程处于可运行状态,如有,则把CPU执行权交给此线程,否则,继续运行原来的线程。
  3. 执行yield()方法的线程有可能在进入到可执行状态后,马上又被执行。

Notification面试点:

  1. Notification运用了什么设计模式?
  2. Notification点击进入应用重新打开了新应用,怎么解决?

两种上下文的区别?

  1. getApplicationContext():生命周期长,即使Activity结束上下文也存在。
  2. Activity.this:一般=this:生命周期短。随着Activity结束而消失。
    对话框时Activity的一部分,挂在在Activity上,如果Activity不存在,对话框也不存在。
    Activity是Context的子类,而对话框是Activity的,父类不一定有,故报错。

阿里笔试题

1. 启动时间长,页面白屏,滑动和点击时卡顿是客户端程序常见的低性能表现,请阐述导致程序性能恶化的几方面原因以及优化方案。

① 当IO操作或者绘制Bitmap在主线程运行时,容易阻塞主线程,会使用户体验差,并容易引发ANR异常。

    解决方案:耗时的操作不能放在主线程,而应该放在子线程上,通过Handler来向主线程发消息,主线程再刷新UI,或使用AsyncTask来完成耗时操作。

② ListView在使用Adapter时,未使用convernView来缓存,如果用户频繁上下滑动,会浪费大量内存,影响用户体验。

    解决方案:建立ViewHolder类并把所有组建对象封装至其中,再将viewHolder封装至View中,这样在滑动时,如果已经加载过,就不用频繁创建item,而是从convernView获取,提高了用户体验。

③ 当ViewPager切换到当前的Fragment时,Fragment会加载布局并显示内容,如果用户这时快速切换ViewPager,频繁销毁和加载Fragment,而Fragment需要加载UI内容,容易产生卡顿现象。

   解决方案:监听DrawerLayout和viewpager滑动的接口,保证滑动完成后在开始加载数据,就可以解决卡顿问题,和阻止Fragment的销毁,就可以缓解卡顿问题。

Activity的四种启动模式

  1. standard:系统默认启动模式,每次激活Activity时都会创建Activity,并放入任务栈中。
  2. singleTop:如果在任务的栈顶正好存在该Activity的实例,就重用该实例,否则就会创建新的实例并放入栈顶(只要不在栈顶都会创建实例)。
  3. singleTask:如果任务栈中已有该Activity的实例,就重用该实例(会调用该实例的onNewIntent())。重用时,会让该实例返回栈顶,所以在该实例上面的实例都会被移除栈;如果栈中不存在该实例,将会创建新的实例存入栈中。
  4. singleInstance:在一个栈中创建该Activity的实例,并让多个应用共享该栈中的该Activity实例,一旦该模式的Activity的存在与某个栈中,任何的应用再次激活该Activity时都会重用该Activity的实例,其效果相当于多个应用程序共享一个应用,不管谁激活该Activity都会进入同一个应用中。(浏览器的工作原理))。

Android从任意Activity退出应用

  • 需要建立一个ActivityHelp类,实现3个静态方法,addActivity(),removeActivity(),finishAll()。当调用Acitivity的onCreate()方法时,调用该类的add方法,当调用onDestory()时,调用该类的remove方法,如果想从某个Acitivity退出应用,则覆写onBackPressed()方法,并且调用该类finishAll()方法,则实现。

Activity和Fragment的区别

  1. fragment更加灵活,可以在xml文件中直接添加fragment标签,而Acitivity不能;
  2. fragment可以在一个界面上灵活的替换一部分页面,替换的时候要将该fragment放在返回栈中;
  3. fragment与Activity进行通信:
    1. fragment控制fragment:fragment得到一个Activity,然后通过该Activity的getFragmentManager()获得该fragment实例;
    2. fragment控制Activity:每个fragment都有getActivity()的方法得到Activity;
    3. Activity控制fragment:通过getFragmentManager(),然后通过getFragmentById()或者getFragmentByTag();
    4. Activity控制Activity:通过Intent来通信和控制
  4. fragment和Activity控件加载不同:
    • fragment是在onCreatView()方法中,通过inflater.inflate()加载布局的,然后通过修改main.xml文件,在main.xml添加注册fragment标签,然后通过android:name来载入通过inflater加载的隐藏布局。也可以通过View.findViewById()来操作fragment上具体的控件。
  5. 动态加载Fragment
  • 实现动态加载,我们需要先了解Fragment事务。熟悉数据库的同学都知道,事务指的就是一种原子性、不可拆分的操作。所谓的Fragment事务就是:对Fragment进行添加、移除、替换或执行其它动作,提交给Activity的每一个变化。这就是Fragment事务。

  • Fragment是UI模块,自然在一个Activity中可以不只有一个模块,所以Android提供了FragmentManage类来管理Fragment,FragmentTransaction类来管理事务。我们对Fragment的动态加载就是先将添加、移除等操作提交到事务,然后通过FragmentManage完成的。

  • 通过FragmentManager.beginTransaction()我们可以开始一个事务。在事务中,我们可以对Fragment进行的操作以及对应的方法如下:

  1. 添加:add()
  2. 移除:remove()
  3. 替换:replace()
  4. 提交事务:commit()
  • 上面几个是比较常用的,还有attach()、detach()、hide()、addToBackStack()等方法。
  • 我们需要注意的是,Fragment以ID或Tag作为唯一标识,所以remove和replace的参数是Fragment,这个Fragment目标Fragment一致。在下面的示例里,我使用了一个栈记录所有添加的Fragment,然后在移除时使用。

Android进程间通信

  1. Bundle/Intent:可传递基本类型数据,String。实现了Serializable或者Parcellable,前者是java序列化方法,代码量少,但I/O消耗较大,一般用于输出到磁盘或网卡;后者是Android的序列化方法,代码较多,效率高,一般用户内存间序列化和反序列化传输。
  2. 对同一个文件先写再读,从而实现传输,Linux机制下允许对文件并发写,所以要注意同步,windows下不支持并发读或写。
  3. Messenger:Messenger是基于AIDL实现的,服务端提供一个Service来处理客户端的的连接,维护一个Handler来创建Messager,客户端持有这个Messenger就可以与服务端进行通信了,在onBinder()时返回Messenger的binder。双方使用Messenger来发送数据,使用Handler来处理数据,如果数据多就需要排队处理。
  4. AIDL:AIDL通过定义服务端暴露的接口,以提供给客户端来调用,编译后会自动生成java文件,服务器将接口的具体实现写在Stub中,用iBinder对象传递给客户端,客户端在bindService的时候,用asInterface的形式将iBinder还原成接口,再调用其中的方法。
  5. ContentProvider:底层也是Binder。
  6. Socket

Android异步任务和Handler+Message的优缺点

AsynTask

  • 优点:
  1. 简单,快;
  2. 过程可控.
  • 缺点:
  1. 在使用多个异步操作和并需要进行Ui变更时,就变得复杂起来;
  2. AsyncTask用的是线程池机制,最多处理5个线程,其余排队等候.

Handler+Message

在Handler异步实现时,涉及到Handler, Looper, Message,Thread四个对象,实现异步的流程是每启动一个Thread,同时也会创建Looper,Looper创建MessageQueue,每个Thread的Handler发送的消息,都存在自己的MessageQueue中,Looper无限循环,从MessageQueue读取消息。

  • 优点:
  1. 结构清晰,功能定义明确;
  2. 对于多个后台任务时,简单,清晰.
  • 缺点:
  1. 在单个后台异步处理时,相对显得代码过多,结构过于复杂;

LRUCache实现代码

//获取当前进程可用内存单位(字节B)除以1024转化为KB
int maxMemory=(int)Runtime.getRuntime().maxMemory()/1024;
//设置缓存大小为可用内存1/8
int cacheSize=maxMemory/8;
//初始化LruCache对象
mMemoryCache=new LruCache<String,Bitmap>(cacheSize){
    @override
    protected int sizeOf(String key,Bitmap bitmap){
        return bitmap.getRowBytes()*bitmap.getHeight()/1024;
    }
};

获取Android系统默认给每个app分配的内存上限

google原生os默认分配16m,但各大手机厂商的os对这个值进行修改,所以不一样

ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
int memorySize = activityManager.getMemoryClass();
  1. Android4.0以后,可以通过在application节点中设置属性android:largeHeap=”true”来突破这个上限。
  2. 设置largeHeap的确可以增加内存的申请量。但不是系统有多少内存就可以申请多少,而是由dalvik.vm.heapsize限制。
    你可以在app manifest.xml加 largetHeap=true。

Android中为什么主线程不会因为Looper.loop里的死循环卡死

epoll+pipe:有消息就依次执行,没消息就block住,让出cpu,当再次有消息时,epoll会往pipe写一个字符,把主线程从block唤醒,主线程循环,有消息就处理,没消息就挂起休眠。

Android View事件分发机制

  • Android中view的事件传递是从上往下传递的,即事件总是先传递给父元素,然后父元素在把事件分发给子View。
  • 事件分发传递过程中有一个很重要的对象:MotionEvent,MotionEvent有个方法getAction,该方法返回int类型,我们可以从该返回值中判断事件类型。
  • 事件的分发过程由三个很重要的方法来共同完成的:
  1. public boolean dispatchTouchEvent(MotionEvent event)
    View的分发事件,一个View只要能接收到事件,首先执行的是该方法,而且该方法是一定会执行​,这里我们就称它为分发事件。
  2. public boolean onInterceptTouchEvent​(MotionEvent event)
    1. View的拦截事件,正常情况下dispatchTouchEvent分发事件会调用该方法,该方法的返回值用以判断当前View是否要拦截该事件,返回true,则当前View的onTouchEvent方法将被调用,并且事件不会传递下去。这里我们需要注意一下,如果一个View决定拦截事件,也就是onInterceptTouchEvent返回true,那么在同一个事件序列的其余事件执行过程中并不会在调用该方法。这里我们就称它为拦截事件。
    2. 如果一个View(如TextView,直接继承于View而不是ViewGroup,(不能添加子View),那么该View是没有onInterceptTouchEvent方法,而且一旦有事件传递给它,onToucheEvent方法将会被调用,除非设置不可点击的(clickable和longClickable同时设为false);​[备注:activity也没有onInterceptTouchEvent事件]
  3. public boolean onTouchEvent(MotionEvent event)
  4. View用来处理点击事件,返回值表示当前View是否消费了该事件,如果事件传递到当前 View 的 onTouchEvent 方法,而该方法返回了 false,那么这个事件会从当前 View 向上传递,并且都是由上层 View 的 onTouchEvent 来接收,而且接收不到下一次事件。换句话说就是:如果没消费,也就是说返回false,那么在同一事件序列的其余事件将不会在传递过来。​
  5. 如果一个View设置了onTouchListener事件,那么会先执行onTouchListener,然后根据返回值在判断是否要执行onTouchEvent方法(返回true则不执行onToucheEvent,反之亦然).​​
  • 伪代码描述事件分发机制
public boolean dispatchTouchEvent(MotionEvent event){
    boolean consume = false;
    if(onInterceptTouchEvent(event)){
			  consume = onTouchEvent(event);
    }else{
        consume = child.dispatchTouchEvent(event);}
    return consume;}

Android dvm的进程和Linux的进程,应用程序的进程,是否为同一概念。

  • Dalvik虚拟机允许多个实例存在,实际android每一个app都运行在自己的VM实例中,而每一个VM实例在Linux中又是一个单独的进程,所以可以认同为同一个概念。运行在自己的DVM进程之中,不同app不会互相干扰,且不会因为一个DVM的崩溃导致所有app进程都崩溃。
    • java运行需要JVM,同样android中士永饿了java语言,也需要一个VM,针对手机处理器、内存等资源不足,而推出的一款VM,为android提供运行环境。

Android开源框架

  1. Okhttp:网络访问
  2. Volley:图片加载
  3. Gson:json解析
  4. ZXing:二维码
  5. Otto:消息队列
  6. Afinal:图片加载
  7. universal-image-loader:图片加载

DVM和JVM的区别

  1. 基于架构不同,JVM基于栈的架构,DVM基于寄存器架构。
  • 为什么JVM基于栈架构
    1. 基于栈架构,指令集更容易生成;
    2. 节省资源,其零地址比其他指令更紧凑;
    3. 可移植性高,JVM的使用场景大多是pc和服务器,这类机器的处理器中通用寄存器的数量不尽相同,如果使用基于寄存器,其功能提升不多。而基于栈架构,可以自由分配实际的1寄存器,这样可移植性较高,也符合java的设计理念(一次编写,到处运行)。
    • 为什么DVM基于寄存器架构
    1. android手机制造商的处理器绝大部分是基于寄存器架构;
    2. 栈架构中有更多的的指令分派和内存访问,这些比较耗时,相比来说,在手机上dvm效率更高一些;
    3. DVM就是为android运行而设计的,无需考虑其他平台的通用。
  1. JVM运行的是字节码文件,而DVM运行的是自己定义的dex文件
  • JVM:.java->.class->.jar
    • DVM:.java->.class->.dex

Dalvik的设计规则

  1. 每个app都运行在自己的DVM实例中与应用隔离;
  2. 启动一个app进程,一个DVM也就诞生了,该app下的代码在DVM实例下解释运行;
  3. 有几个app进程就有几个DVM实例;
  4. DVM对对象的生命周期、堆栈、线程、异常以及垃圾回收器进行管理;
  5. 不支持j2se,j2me的API,也就不支持awt和swing。

Dalvik和ART的比较

  1. 在Dalvik中,app每一次运行都要通过及时编译器转化成机器码,这要多一个编译过程,会降低运行速度。
  2. ART是在app安装是,直接将字节码编译成本机机器码,实现了真正的本地应用,在以后的运行不需要每次都翻译,这样速度快,但是占用的存储空间更大(5.0新特性)。

DDMS和traceview的区别。

  1. DDMS: dalvilk debug manager system,是一个集调试、浏览、控制等操作为一体的工具箱。
  2. traceview:只是一个性能调优工具,可通过它查看程序中方法的执行效率等指标。
  • 启动方式:

第一种

  • 直接打开DDMS,选择一个进程,然后按上面的“Start Method Profiling”按钮,等红色小点变成黑色以后就表示TraceView已经开始工作了。然后进行手机操作,操作最好不要超过5s,因为最好是进行小范围的性能测试。然后再按一下刚才按的按钮,等一会就会出现上面这幅图,然后就可以开始分析了。

第二种

  1. 在应用的主activity的onCreate方法中加入Debug.startMethodTracing(“要生成的traceview文件的名字”);
  2. 同样在主activity的onStop方法中加入Debug.stopMethodTracing();
  3. 同时要在AndroidManifest.xml文件中配置权限
  4. 重新编译,安装,启动服务,测试完成取对应的traceview文件(adb pull /sdcard/xxxx.trace)。
  5. 直接在命令行输入traceview xxxxtrace,弹出traceview窗口,分析对应的应用即可。

Android面试项目之WechatTalk

  1. 闪屏页面:没有使用Activity关联xml文件然后再将闪屏页面图片加载到xml文件里

JumpBuing

  1. 首先对整个游戏场景分析,分析出需要建立的对象。
  2. 分析后得出需要桥,竹子,人物,场景

LRUCache

  1. 其中用到的数据对象是LinkedHashMap,所以不要把这个类想的多么深不可测,还是数据结构 + 算法。既然用到了这个map,自然就要有添加修改和删除操作了,用到了最近最少使用算法,自然就要用到优先级了。
  2. 作为缓存,肯定有一个缓存的大小,这个大小是可以设定的(自定义sizeOf())。当你访问了一个item(需要缓存的对象),这个item应该被加入到内存中,然后移动到一个队列的顶部,如此循环后这个队列的顶部应该是最近访问的item了,而队尾部就是很久没有访问的item,这样我们就应该对队尾部的item优先进行回收操作。
  3. 因为用到了HashMap,那么就有这个数据存储对象的特点(KEY-VALUE),放入这个map的item应该会被强引用,要回收这个对象的时候是让这个key为空,这样就让有向图找不到对应的value,最终被GC。
  4. 缓存的最大特点是不做重复的劳动,如果你之前已经缓存过这个item了,当你再次想要缓存这个item时,应该会先判断是否已经缓存好了,如果已经缓存,那么就不执行添加的操作。
  5. 我们应该能通过某个方法来清空缓存,这个缓存在app被退出后就自动清理,不会常驻内存。
  6. sizeof()方法。这个方法默认返回的是你缓存的item数目,如果你想要自定义size的大小,直接重写这个方法,返回自定义的值即可。
  7. 如果你cache的某个值需要明确释放,重写entryRemoved()方法。这个方法会在元素被put或remove时调用,源码默认是空实现的。

红黑树

红黑树的特性 :

  1. 每个节点或者是黑色,或者是红色。
  2. 根节点是黑色。
  3. 每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点。
  4. 如果一个节点是红色的,则它的子节点必须是黑色的。
  5. 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

你可能感兴趣的:(面试)