Android 面试题总结

Posted 疯狂Max

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 面试题总结相关的知识,希望对你有一定的参考价值。

前言

  • 笔者最近离职找工作快两周了,这段时间陆陆续续也见识了北上广这边跟西部城市对待技术理念的差异和学习深度.俗话说:知耻而后勇,在经历了面试被虐得体无完肤的过程后,我也找到了作为一名开发者应有的职业素养和今年的发展规划.
  • 俗话也说的好,王侯将相宁有种乎,我不信我从今天开始认认真真对待每一个技术细节,认真刷题.,在深圳这座城市没有我的立足之地.
  • 好了,鸡汤和废话也不多说了,依旧记录今日面试的题目.

正文

  1. Android内存泄露的解决方案:
    http://blog.csdn.net/Huang_Cai_Yuan/article/details/50443385
    http://my.oschina.net/rengwuxian/blog/181449
    含义:一个对象已经不需要再使用了,但是因为其他的对象持有该对象的引用,导致它的内存不能被回收.
    危害:只有一个,那就是虚拟机占用内存过高,导致OOM(内存溢出),程序出错.
    内存泄露的主要问题可以分为以下几种类型:
    1.静态变量引起的内存泄露:
    在java中静态变量的生命周期是在类加载时开始,类卸载时结束.换句话说,在android中其生命周期是在进程启动时开始,进程死亡时结束.所以在程序的运行期间,如果进程没有被杀死,静态变量就会一直存在,不会被回收掉.如果静态变量强引用了某个Activity中变量,那么这个activity就同样也不会被释放,即便是该activity执行了onDestroy.
    解决方案:
    1.寻找与该静态变量生命周期差不多的替代对象.
    2.若找不到,将强引用方式改成弱引用.
    案例:单例引起的context内存泄露
public static IMManager getInstance(Context context) {
        if (mInstance == null) {
            synchronized (IMManager.class) {
                if (mInstance == null)
                    mInstance = new IMManager(context);
            }
        }
        return mInstance;
    }

当调用getInstance时,如果传入的context是Activity的context。只要这个单例没有被释放,这个Activity也不会被释放。
解决方案:传入Application的context.
2.非静态内部类引起的内存泄露:
在java中,创建一个非静态的内部类实例,就会引用它的外围实例.如果这个非静态内部类实例做了一些耗时的操作.就会造成外围对象不会被回收,从而导致内存泄露.
解决方案:
1.将内部类变成静态内部类
当将内部类变成静态内部类后,便不再持有外部类对象的引用,导致程序不允许你在handler中操作activity的对象了,因此我么需要在handler中增加一个对A/ctivity的弱引用(WeakReference);
注:为什么使用静态内部类就不会造成内存泄露了?
答:静态内部类不会持有对外部对象的引用
2.如果有强引用Activity中的属性,则将该属性的引用方式改为弱引用.
使用WeakReference包装该对象.
3.在业务允许的情况下,当activity执行onDestory时,结束这些耗时任务

static class MyHandler extends Handler {
    WeakReference<Activity > mActivityReference;

    MyHandler(Activity activity) {
        mActivityReference= new WeakReference<Activity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        final Activity activity = mActivityReference.get();
        if (activity != null) {
            mImageView.setImageBitmap(mBitmap);
        }
    }
}

3.资源未关闭引起的内存泄露
当使用了BroadcastReceiver\\Cursor\\Bitmap等资源的时候,当不需要使用时,需要及时释放掉,若没有释放,则会引起内存泄露.

4.**增加:在使用Bitmap时,出现内存溢出的解决方案:**1.调用系统的GC回收,System.gc();2.压缩图片质量大小.

小结

虽然静态类与非静态类之间的区别并不大,但是对于Android开发者而言却是必须理解的.至少我们要清楚,如果一个内部类实例的生命周期比Activity更长,那么我们千万不要使用非静态的内部类.最好的做法是,使用静态内部类,然后在该类里面使用弱引用来指向所在的Activity

2.Java基础之弱引用\\强引用\\软引用\\虚引用:
http://blog.csdn.net/mazhimazh/article/details/19752475这里写链接内容
强引用(StrongReference):是使用最普遍的引用.如果一个对象具有强引用,那GC回收器绝不会回收它.

    Object o=new Object();   //  强引用  
    public void test(){  
        Object o=new Object();  
        // 省略其他操作  
    }  

当内存不足时,java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题.如果不使用时,要通过如下方式来弱化引用.

在方法内部有一个强引用,这个引用保存在栈中,而真正得引用内容(Object )保存在堆中.当这个方法运行完成后,就会推出方法栈,则引用内容的引用不存在,这个Object会被回收.

但是如果这个o是全局的变量时,就需要在不用这个对象时复制为null,因为强引用不会被垃圾回收。
软引用(SoftReference): 如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。

    String str=new String("abc");                                     // 强引用  
    SoftReference<String> softRef=new SoftReference<String>(str);     // 软引用    

软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
弱引用:
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

    String str=new String("abc");      
    WeakReference<String> abcWeakRef = new WeakReference<String>(str);  
    str=null;    

当你想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候你就是用弱引用。
虚引用(PhantomReference):
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
这里写图片描述

3.Java中static,final等关键字:
http://blog.csdn.net/mazhimazh/article/details/16805061
static关键字:
被static修饰的成员变量和成员方法独立于该类的任何对象。也就是说,它不依赖类特定的实例,被类的所有实例共享。只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内找到他们。因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象。
1.static修饰方法:
当static关键字修饰方法时,这个方法就称为静态方法。静态方法属于类而不属于实例对象。那么静态方法如何进行调用?可以通过类名.方法名来直接调用,也可以通过实例对象.方法名来调用。
使用static关键字修饰的方法在类加载的时候就会加载到内存中。是被指向所属的类而不是实例。
可以看出,这并不是覆盖,其实静态方法只可以被隐藏,而不可以覆盖。当t1为Person类型时,则调用Person类的静态方法,因为静态方法只属于这个类,而当t2类型为Test02时,调用子类的静态方法,当子类没有时,调用父类的方法,这就叫做继承父类的方法了。
2.static修改属性:
当static修饰属性时,与类一样,都是在类加载时就加载到了内存。
3.static修饰块
加static修饰的块也会随着类的加载而进入内存,所以可以在这里初始化一些静态成员变量。
4.使用static注意事项:
当类被虚拟机加载时,按照声明顺序先后初始化static成员字段和static语句块
static所修饰的方法和变量只属于类,所有对象共享。
在static所修饰的方法和语句块中不能使用非static成员字段。
静态成员不是对象的特性,只是为了找一个容身之处,所以需要放到一个类中而已,把“全局变量”放在内部类中就是毫无意义的事情,所以 被禁止
在Java不能直接定义全局变量,是通过static来实现的
在Java中没有const,不能直接定义常量,通过static final来实现
final 关键字:
1.final关键字的使用:引用本身的不变和引用指向的对象不变。
修饰类:*如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承*。因此一个类不能同时声明为abstract final或者interface final的。
修饰方法 :将方法声明为final,可以保证它们在使用中不被改变。被声明为final的方法也同样只能使用,不能重载。但是子类可以继承父类的final方法。
修饰变量 :表示属性值第一次初始化后不能被修改。final属性可以直接初始化或在构造函数中初始化.如果属性是直接初始化,则其值不能被其它函数(包括构造函数)修改。
修饰方法参数 :参数值不能被修改
修饰方法中的局部变量 : 局部变量被第一次初始化后不能被修改
注:任何在interface里声明的成员变量,默认为public static final。
使用final的意义:
第一,为方法“上锁”,防止任何继承类改变它的本来含义和实现。设计程序时,若希望一个方法的行为在继承期间保持不变,而且不可被覆盖或改写,就可以采取这种做法。
第二,提高程序执行的效率,将一个方法设成final后,编译器就可以把对那个方法的所有调用都置入“嵌入”调用里(内嵌机制)。
finally关键字:
在异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入 finally 块。 所以说finally块是一定会被执行的。
finally 语句块是在 try 或者 catch 中的 return 语句之前执行的。更加一般的说法是,finally 语句块应该是在控制转移语句之前执行,控制转移语句除了 return 外,还有 break 和 continue。 return 和 throw 把程序控制权转交给它们的调用者(invoker),而 break 和 continue 的控制权是在当前方法内转移。
finalize 方法名:
finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。

4.Java多线程线程池 - 线程池原理:
http://blog.csdn.net/mazhimazh/article/details/19243889

    public interface Executor {  
        void execute(Runnable command);  
    }  

Executors方便的创建线程池.
(1).newCachedThreadPool :该线程池比较适合没有固定大小并且比较快速就能完成的小任务,它将为每个任务创建一个线程。那这样子它与直接创建线程对象(new Thread())有什么区别呢?看到它的第三个参数60L和第四个参数TimeUnit.SECONDS了吗?好处就在于60秒内能够重用已创建的线程。下面是Executors中的newCachedThreadPool()的源代码: 

    //基本的无界值(如 Integer.MAX_VALUE),则允许池适应任意数量的并发任务
    public static ExecutorService newCachedThreadPool() {  
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());  
        }  

(2).newFixedThreadPool:使用的Thread对象的数量是有限的,如果提交的任务数量大于限制的最大线程数,那么这些任务将排队,然后当有一个线程的任务结束之后,将会根据调度策略继续等待执行下一个任务。下面是Executors中的newFixedThreadPool()的源代码:  

    public static ExecutorService newFixedThreadPool(int nThreads) {  
          return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());  
      }  

(3).newSingleThreadExecutor:就是线程数量为1的FixedThreadPool,如果提交了多个任务,那么这些任务将会排队,每个任务都会在下一个任务开始之前运行结束,所有的任务将会使用相同的线程。下面是Executors中的newSingleThreadExecutor()的源代码: 

    public static ExecutorService newSingleThreadExecutor() {  
            return new FinalizableDelegatedExecutorService  
                (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));  
        }  

(4).newScheduledThreadPool:创建一个固定长度的线程池,而且以延迟或定时的方式来执行任务。
ThreadPoolExecutor构造方法:

 public ThreadPoolExecutor(int corePoolSize,  //核心线程
                     int maximumPoolSize,  // 线程池维护的最大线程数量 
                     long keepAliveTime,  //线程池维护线程所允许的空闲时间
                     TimeUnit unit,  
                     BlockingQueue<Runnable> workQueue  阻塞队列  ) {  
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,  
        Executors.defaultThreadFactory(), defaultHandler);  
}  

ExecutorService:为了解决执行服务的生命周期问题,Executor扩展了EecutorService接口,添加了一些用于生命周期管理的方法。

ThreadPoolExecutor线程池实现类:

    private final BlockingQueue<Runnable> workQueue;              // 阻塞队列  
    private final ReentrantLock mainLock = new ReentrantLock();   // 互斥锁  
    private final HashSet<Worker> workers = new HashSet<Worker>();// 线程集合.一个Worker对应一个线程  
    private final Condition termination = mainLock.newCondition();// 终止条件  
    private int largestPoolSize;           // 线程池中线程数量曾经达到过的最大值。  
    private long completedTaskCount;       // 已完成任务数量  
    private volatile ThreadFactory threadFactory;     // ThreadFactory对象,用于创建线程。  
    private volatile RejectedExecutionHandler handler;// 拒绝策略的处理句柄  
    private volatile long keepAliveTime;   // 线程池维护线程所允许的空闲时间  
    private volatile boolean allowCoreThreadTimeOut;  
    private volatile int corePoolSize;     // 线程池维护线程的最小数量,哪怕是空闲的  
    private volatile int maximumPoolSize;  // 线程池维护的最大线程数量  

处理任务的优先级为:

  • 核心线程corePoolSize > 任务队列workQueue > 最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
  • 当池中的线程数大于corePoolSize的时候,多余的线程会等待keepAliveTime长的时间,如果无请求可处理就自行销毁。

workQueue :线程池所使用的缓冲队列,该缓冲队列的长度决定了能够缓冲的最大数量,缓冲队列有三种通用策略:
(1) 直接提交,工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们.
(2) 无界队列,使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列.
(3) 有界队列,当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折中:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量.

ThreadFactory:使用 ThreadFactory 创建新线程。如果没有另外说明,则在同一个 ThreadGroup 中一律使用 Executors.defaultThreadFactory() 创建线程,并且这些线程具有相同的 NORM_PRIORITY 优先级和非守护进程状态。通过提供不同的 ThreadFactory,可以改变线程的名称、线程组、优先级、守护进程状态等等。如果从 newThread 返回 null 时 ThreadFactory 未能创建线程,则执行程序将继续运行,但不能执行任何任务。
在DefaultThreadFactory类中实现了ThreadFactory接口并对其中定义的方法进行了实现,如下:

    static class DefaultThreadFactory implements ThreadFactory {  
        private static final AtomicInteger poolNumber = new AtomicInteger(1);  
        private final ThreadGroup group;  
        private final AtomicInteger threadNumber = new AtomicInteger(1);  
        private final String namePrefix;  

        DefaultThreadFactory() {  
            SecurityManager s = System.getSecurityManager();  
            group = (s != null) ? s.getThreadGroup() :  Thread.currentThread().getThreadGroup();  
            namePrefix = "pool-" +  poolNumber.getAndIncrement() +  "-thread-";  
        }  
        // 为线程池创建新的任务执行线程  
        public Thread newThread(Runnable r) {  
            // 线程对应的任务是Runnable对象r  
            Thread t = new Thread(group, r,namePrefix + threadNumber.getAndIncrement(), 0);  
            // 设为非守护线程  
            if (t.isDaemon())  
                t.setDaemon(false);  
            // 将优先级设为Thread.NORM_PRIORITY  
            if (t.getPriority() != Thread.NORM_PRIORITY)  
                t.setPriority(Thread.NORM_PRIORITY);  
            return t;  
        }  
    }  

RejectedExecutionHandler:
当Executor已经关闭(即执行了executorService.shutdown()方法后),并且Executor将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法execute()中提交的新任务将被拒绝.
  在以上述情况下,execute 方法将调用其 RejectedExecutionHandler 的 RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 方法。下面提供了四种预定义的处理程序策略:

    1) 在默认的 ThreadPoolExecutor.AbortPolicy 处理程序遭到拒绝将抛出运行时 RejectedExecutionException;
    2) 在 ThreadPoolExecutor.CallerRunsPolicy 线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度

    3) 在 ThreadPoolExecutor.DiscardPolicy 不能执行的任务将被删除;

    4) 在 ThreadPoolExecutor.DiscardOldestPolicy 如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。

线程池默认会采用的是defaultHandler策略。
具体案例见博客:(线程池源码的相关博客)
http://blog.csdn.net/mazhimazh/article/details/19243889
http://blog.csdn.net/mazhimazh/article/details/19283171
http://blog.csdn.net/mazhimazh/article/details/19291965
Callable接口代表一段可以调用并返回结果的代码;Future接口表示异步任务,是还没有完成的任务给出的未来结果。所以说Callable用于产生结果,Future用于获取结果。

以下内容来自http://www.trinea.cn/android/java-android-thread-pool/,再此简单记录.
new Thread的弊端:
a.每次new Thread新建对象性能差.
b.线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom.
c.缺乏更多功能,如定时执行、定期执行、线程中断.
Java提供的四种线程池的好处:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。
(1). newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
(2). newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()。
(3) newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下
(4)、newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。现行大多数GUI程序都是单线程的。Android中单线程可用于数据库操作,文件操作,应用批量安装,应用批量删除等不适合并发但可能IO阻塞性及影响UI线程响应的操作。

5.Java线程的生命周期和相关方法:
http://blog.csdn.net/cl05300629/article/details/12999399
http://blog.csdn.net/LonelyRoamer/article/details/7949969
线程生命周期中,要经过“新建(New)”、“就绪(Runnable)”、“运行(Running’)”、“阻塞(Blocked)”和“死亡(Dead)”五种状态.
新建和就绪状态:也就是new和start().调用start()方法之后,线程就进入了就绪状态。当不能立刻进入运行状态,要等待JVM里线程调度器的调度.
运行和阻塞状态:如果处于就绪状态的线程获得了CPU,就开始执行run方法,处于了运行状态。当分配的时间用完后,又进入了就绪状态,等待下次分配到CPU在进入运行状态.
线程进入阻塞状态的几种情况:
1.线程调用sleep()方法主动放弃所占用的处理器资源
2.线程调用了一个阻塞式IO方法,在该方法返回之时,该线程被阻塞
3.线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有.
4.现成在等待某个通知(notify)
5.线程调用了线程的suspend()方法将该线程挂起。该方法容易造成死锁,应尽量避免使用该方法。
进入阻塞状态的程序遇到以下情况才能恢复进入就绪状态,等待分配到资源进入运行状态:
1.调用sleep()方法的线程经过了指定时间
2.线程调用的阻塞式IO方法已经返回
3.线程成功的获得了试图取得的同步监视器
4.线程正在等待的某个通知时,其他线程发出了一个通知(signal)
5.处于挂起状态的线程被调用了resume()恢复方法
线程死亡状态:
1.run()或call()方法执行完成,线程正常结束
2.线程抛出一个未捕获的Exception或Error.
3.直接调用该线程的stop()方法来结束该线程———该方法容易导致死锁,不推荐.
注意:不要试图对一个已经死亡的线程调用start()方法使它重新启动,该线程不可再次作为线程执行。start方法只能对处于新建状态的线程多使用,且只能使用一次!
线程状态的控制:
sleep():在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
sleep是静态方法,最好不要用Thread的实例对象调用它,因为它睡眠的始终是当前正在运行的线程,而不是调用它的线程对象,它只对正在运行状态的线程对象有效。
因为使用sleep方法之后,线程是进入阻塞状态的,只有当睡眠的时间结束,才会重新进入到就绪状态,而就绪状态进入到运行状态,是由系统控制的,我们不可能精准的去干涉它,所以如果调用Thread.sleep(1000)使得线程睡眠1秒,可能结果会大于1秒。
yieled():暂停当前正在执行的线程对象,并执行其他线程
yield()方法和sleep()方法有点相似,它也是Thread类提供的一个静态的方法,它也可以让当前正在执行的线程暂停,让出cpu资源给其他的线程。但是和sleep()方法不同的是,它不会进入到阻塞状态,而是进入到就绪状态。yield()方法只是让当前线程暂停一下,重新进入就绪的线程池中,让系统的线程调度器重新调度器重新调度一次,完全可能出现这样的情况:当某个线程调用yield()方法之后,线程调度器又将其调度出来重新进入到运行状态执行。
关于sleep()方法和yield()方的区别如下:
1.sleep方法暂停当前线程后,会进入阻塞状态,只有当睡眠时间到了,才会转入就绪状态。而yield方法调用后 ,是直接进入就绪状态,所以有可能刚进入就绪状态,又被调度到运行状态。
2.sleep方法声明抛出了InterruptedException,所以调用sleep方法的时候要捕获该异常,或者显示声明抛出该异常。而yield方法则没有声明抛出任务异常。
3.sleep方法比yield方法有更好的可移植性,通常不要依靠yield方法来控制并发线程的执行。
join():等待该线程终止
线程的合并的含义就是将几个并行线程的线程合并为一个单线程执行,应用场景是当一个线程必须等待另一个线程执行完毕才能执行时,Thread类提供了join方法来完成这个功能,注意,它不是静态方法.
setDaemon(true):设置其为守护线程
守护线程的用途:
守护线程通常用于执行一些后台作业
守护线的好处就是你不需要关心它的结束问题
interrupt():使用interrupt结束一个线程。
注:关于interrupt中断线程的使用,可以参考此篇文章http://www.codeceo.com/article/java-thread-suspend.html
多线程同步的问题(synchronized关键字):
**同步锁的原理:**Java中每个对象都有一个内置同步锁。Java中可以使用synchronized关键字来取得一个对象的同步锁。synchronized的使用方式,是在一段代码块中,加上synchronized(object){ … }
这其中的object可以使任何对象,表示当前线程取得该对象的锁。一个对象只有一个锁,所以其他任何线程都不能访问该对象的所有由synchronized包括的代码段,直到该线程释放掉这个对象的同步锁(释放锁是指持锁线程退出了synchronized同步方法或代码块)。
注意:synchronized使用方式有几个要注意的地方:
1.取得同步锁的对象为this,即当前类对象,这是使用的最多的一种方式

    public void show() {  
        synchronized(this){  
           ......  
        }  
    }  

2.将synchronized加到方法上,这叫做同步方法,相当于第一种方式的缩写

    public synchronized void show() {  

    }  

3.静态方法的同步

    public static synchronized void show() {  

    }  

相当于取得类对象的同步锁,注意它和取得一个对象的同步锁不一样
当甲线程执行run方法的时候,它使用synchronized (account)取得了account对象的同步锁,那么只要它没释放掉这个锁,那么当乙线程执行到run方法的时候,它就不能获得继续执行的锁,所以只能等甲线程执行完,然后释放掉锁,乙线程才能继续执行。
synchronized关键字使用要注意以下几点:
1.只能同步方法和代码块,而不能同步变量和类.
2.每个对象只有一个同步锁;当提到同步时,应该对对象进行同步
3.不必同步类中所有的方法,类可以同时拥有同步和非同步方法
4.如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放.
5.如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制
6.线程睡眠时,它所持的任何同步锁都不会释放
7.线程可以获得多个同步锁
8.同步损害并发性,应该尽可能缩小同步范围
9.编写线程安全的代码会使系统的总体效率会降低,要适量使用
一个线程取得了同步锁,那么在什么时候才会释放掉呢?
1.同步方法或代码块正常结束
2.使用return或 break终止了执行,或者跑出了未处理的异常
3.当线程执行同步方法或代码块时,程序执行了同步锁对象的wait()方法
死锁:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放.
**线程的协调运行:**Object中有3种方法来控制线程的协调运行。notify()、notifyAll()、wait().
这三个方法必须由同步监视器对象(即线程获得的锁对象)来调用,这可分为两种情况:
1.对于使用synchronized修饰的同步代码块,因为当前的类对象(this)就是同步监视器,所以可以再同步方法中直接调用这三个方法.
2.对于使用synchronized修饰的同步代码块,同步监视器是synchronized后括号的对象,所以必须使用该对象调用这三个方法.
wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或notifyAll()方法来唤醒该线程.调用wait()方法的当前线程会释放对该同步监视器的锁定.
notify():唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程。选择是任意性。只有当前线程放弃对该同步监视器的锁定后(使用wait()方法),才可以执行被唤醒的其他线程
notifyAll():唤醒在此同步监视器上等待的所有线程。只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程.
使用wait、notify和notifyAll三个方法一定是在同步代码块中使用的,所以一定要明白下面几点:
1.如果两个线程是因为都要得到同一个对象的锁,而导致其中一个线程进入阻塞状态。那么只有等获得锁的线程执行完毕,或者它执行了该锁对象的wait方法,阻塞的线程才会有机会得到锁,继续执行同步代码块
2.使用wait方法进入等待状态的线程,会释放掉锁。并且只有其他线程调用notify或者notifyAll方法,才会被唤醒。要明白,线程因为锁阻塞和等待是不同的,因为锁进入阻塞状态,会在其他线程释放锁的时候,得到锁在执行。而等待状态必须要靠别人唤醒,并且唤醒了也不一定会立刻执行,有可能因为notifyAll方法使得很多线程被唤醒,多个线程等待同一个锁,而进入阻塞状态。还可能是调用notify的线程依然没有释放掉锁,只有等他执行完了,其他线程才能去争夺这个锁
线程池补充:
JDK1.5中提供Executors工厂类来产生连接池,该工厂类中包含如下的几个静态工程方法来创建线程池
newFixedThreadPool(int nThreads):创建一个可重用的、具有固定线程数的线程池
newSingleThreadExecutor():创建一个只有单线程的线程池,它相当于newFixedThreadPool方法是传入的参数为1
newCachedThreadPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中
newSingleThreadScheduledExecutor():创建只有一条线程的线程池,他可以在指定延迟后执行线程任务
newScheduledThreadPool(int corePoolSize):建具有指定线程数的线程池,它可以再指定延迟后执行线程任务,corePoolSize指池中所保存的线程数,即使线程是空闲的也被保存在线程池内
**ExecutorService:**ExecutorService对象就代表着一个尽快执行线程的线程池(只要线程池中有空闲线程立即执行线程任务),程序只要将一个Runnable对象或Callable对象提交给该线程池即可,该线程就会尽快的执行该任务.
submit方法是对 Executor接口execute方法的更好的封装,建议使用submit方法.
ThreadLocal:线程的本地变量,为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突.
对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,但大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度.
ThreadLocal的使用比synchronized要简单得多
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离
ThreadLocal的使用步骤:
1、在多线程的类(如ThreadDemo类)中,创建一个ThreadLocal对象threadXxx,用来保存线程间需要隔离处理的对象xxx。
2、在ThreadDemo类中,创建一个获取要隔离访问的数据的方法getXxx(),在方法中判断,若ThreadLocal对象为null时候,应该new()一个隔离访问类型的对象,并强制转换为要应用的类型。
3、在ThreadDemo类的run()方法中,通过getXxx()方法获取要操作的数据,这样可以保证每个线程对应一个数据对象,在任何时刻都操作的是这个对象。
以上转自网络,看了源码后自己的理解:ThreadLocal叫“线程的本地变量”最合适,它为每个线程单独维护一个复本,这个复本实际上存储在当前线程实例的threadLocals变量中,即:t.threadLocals中,而这个threadLocals变量实际又是ThreadLocal的内部类ThreadLocal.ThreadLocalMap的实例。ThreadLocalMap实际上就是个Map,它是以当前ThreadLcoal实例为key来存储。
volatile变量 :
Java™ 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量。这两种机制的提出都是为了实现代码线程的安全性。其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低),而且其使用也更容易出错
Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值

6.Android三大图片缓存原理、特性对比:
http://www.trinea.cn/android/android-image-cache-compare/这里写链接内容
Universal ImageLoader:是很早开源的图片缓存,在早期被很多应用使用
Picasso:是 Square 开源的项目,且他的主导者是 JakeWharton,所以广为人知
Glide:是 Google 员工的开源项目,被一些 Google App 使用,在去年的 Google I/O 上被推荐,不过目前国内资料不多
Fresco:是 Facebook 在今年上半年开源的图片缓存,主要特点包括:
(1) 两个内存缓存加上 Native 缓存构成了三级缓存
(2) 支持流式,可以类似网页上模糊渐进式显示图片
(3) 对多帧动画图片支持更好,如 Gif、WebP
基本概念:
RequestManager:请求生成和管理模块
Engine:引擎部分,负责创建任务(获取数据),并调度执行
GetDataInterface:数据获取接口,负责从各个数据源获取数据
Displayer:资源(图片)显示器,用于显示或操作资源
Processor:资源(图片)处理器
ImageLoader 设计及优点:
这里写图片描述
优点:
1.支持下载进度监听
2.可以在 View 滚动中暂停图片加载.通过 PauseOnScrollListener 接口可以在 View 滚动中暂停图片加载
3.默认实现多种内存缓存算法:这几个图片缓存都可以配置缓存算法,不过 ImageLoader 默认实现了较多缓存算法,如 Size 最大先删除、使用最少先删除、最近最少使用、先进先删除、时间最长先删除等。
4.支持本地缓存文件名规则定义
Picasso 设计及优点:
这里写图片描述
Picasso 收到加载及显示图片的任务,创建 Request 并将它交给 Dispatcher,Dispatcher 分发任务到具体 RequestHandler,任务通过 MemoryCache 及 Handler(数据获取接口) 获取图片,图片获取成功后通过 PicassoDrawable 显示到 Target 中.
需要注意的是上面 Data 的 File system 部分,Picasso 没有自定义本地缓存的接口,默认使用 http 的本地缓存,API 9 以上使用 okhttp,以下使用 Urlconnection,所以如果需要自定义本地缓存就需要重定义 Downloader.
优点:
1.自带统计监控功能.支持图片缓存使用的监控,包括缓存命中率、已使用内存大小、节省的流量等
2.支持优先级处理每次任务调度前会选择优先级高的任务,比如 App 页面中 Banner 的优先级高于 Icon 时就很适用
3.支持延迟到图片尺寸计算完成加载
4.支持飞行模式、并发线程数根据网络类型而变手机切换到飞行模式或网络类型变换时会自动调整线程池最大并发数,比如 wifi 最大并发为 4, 4g 为 3,3g 为 2。这里 Picasso 根据网络类型来决定最大并发数,而不是 CPU 核数
5.“无”本地缓存无”本地缓存,不是说没有本地缓存,而是 Picasso 自己没有实现,交给了 Square 的另外一个网络库 okhttp 去实现,这样的好处是可以通过请求 Response Header 中的 Cache-Control 及 Expired 控制图片的过期时间.
Glide 设计及优点:
这里写图片描述
Glide 收到加载及显示资源的任务,创建 Request 并将它交给RequestManager,Request 启动 Engine 去数据源获取资源(通过 Fetcher ),获取到后 Transformation 处理后交给 Target
Glide 依赖于 DiskLRUCache、GifDecoder 等开源库去完成本地缓存和 Gif 图片解码工作
优点:
1.**图片缓存->媒体缓存**Glide 不仅是一个图片缓存,它支持 Gif、WebP、缩略图。甚至是 Video,所以更该当做一个媒体缓存
2.支持优先级处理
3.**与 Activity/Fragment 生命周期一致,支持 trimMemory**Glide 对每个 context 都保持一个 RequestManager,通过 FragmentTransaction 保持与 Activity/Fragment 生命周期一致,并且有对应的 trimMemory 接口实现可供调用
4.**支持 okhttp、Volley**Glide 默认通过 UrlConnection 获取数据,可以配合 okhttp 或是 Volley 使用。实际 ImageLoader、Picasso 也都支持 okhttp、Volley
5.Glide 的内存缓存有个 active 的设计,内存缓存更小图片, 图片默认使用默认 RGB_565 而不是 ARGB_888

7.Https和http:
http://mp.weixin.qq.com/s?__biz=MzAxNjI3MDkzOQ==&mid=405475595&idx=1&sn=5bb0c2a6f1da40ee12048e5093d78c96#rd
http://www.codeceo.com/article/https-make-safe.html
Https在HTTP的之下加入了SSL (Secure Socket Layer),安全的基础就靠这个SSL了。SSL位于TCP/IP和HTTP协议之间,它能够:
1.认证用户和服务器,确保数据发送到正确的客户机和服务器;(验证证书)
2.加密数据以防止数据中途被窃取;(加密)
3.维护数据的完整性,确保数据在传输过程中不被改变。(摘要算法)
服务器端会有一个证书,在交互过程中客户端需要去验证证书的合法性,对于权威机构颁发的证书当然我们会直接认为合法。对于自己造的证书,那么我们就需要去校验合法性了,也就是说我们只需要让OkhttpClient去信任这个证书就可以畅通的进行通信了
我们还可以将证书中的内容提取出来,写成字符串常量,这样就不需要证书根据着app去打包了.
还是极少数的应用需要双向证书验证,比如银行、金融类app.首先对于双向证书验证,也就是说,客户端也会有个“kjs文件”,服务器那边会同时有个“cer文件”与之对应
———-分割线———————————————————
Android 手机有一套共享证书的机制,如果目标 URL 服务器下发的证书不在已信任的证书列表里,或者该证书是自签名的,不是由权威机构颁发,那么会出异常。对于我们这种非浏览器 App 来说,如果提示用户去下载安装证书,可能会显得比较诡异。幸好还可以通过自定义的验证机制让证书通过验证。验证的思路有两种:
1.不论是权威机构颁发的证书还是自签名的,打包一份到 App 内部,比如存放在 asset 里。通过这份内置的证书初始化一个 KeyStore,然后用这个 KeyStore去引导生成的 TrustManager 来提供验证.
则会抛出那个 SSLHandshakeException 异常,也就是说对于特定证书生成的 TrustManager,只能验证与特定服务器建立安全链接,这样就提高了安全性.
2.打包一份到证书到 App 内部,但不通过KeyStore 去引导生成的 TrustManager,而是干脆直接自定义一个 TrustManager,自己实现校验逻辑;校验逻辑主要包括:
(1) 服务器证书是否过期
(2) 证书签名是否合法
———-分割线———————————————————
HTTPS和HTTP的区别:
https协议需要到CA申请证书。
http是超文本传输协议,信息是明文传输;https 则是具有安全性的ssl加密传输协议。
http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
SSL与TLS握手过程:
这里写图片描述
8.Android dex分包方案:
https://m.oschina.net/blog/308583
什么情况下使用dex分包?
1. Android2.3及以前版本用来执行dexopt(用于优化dex文件)的内存只分配了5M
2. 一个dex文件最多只支持65536个方法。
针对上述问题,也出现了诸多解决方案,使用的最多的是插件化,即将一些独立的功能做成一个单独的apk,当打开的时候使用DexClassLoader动态加载,然后使用反射机制来调用插件中的类和方法。这固然是一种解决问题的方案:但这种方案存在着以下两个问题:
1. 插件化只适合一些比较独立的模块;
2. 必须通过反射机制去调用插件的类和方法,因此,必须搭配一套插件框架来配合使用;
由于上述问题的存在,通过不断研究,便有了dex分包的解决方案。简单来说,其原理是将编译好的class文件拆分打包成两个dex,绕过dex方法数量的限制以及安装时的检查,在运行时再动态加载第二个dex文件中
但是使用dex分包方案仍然有几个注意点:
1. 由于第二个dex包是在Application的onCreate中动态注入的,如果dex包过大,会使app的启动速度变慢,因此,在dex分包过程中一定要注意,第二个dex包不宜过大。
2. 由于上述第一点的限制,假如我们的app越来越臃肿和庞大,往往会采取dex分包方案和插件化方案配合使用,将一些非核心独立功能做成插件加载,核心功能再分包加载。
9.android热修复:
https://mp.weixin.qq.com/s?__biz=MzI1MTA1MzM2Nw==&mid=400118620&idx=1&sn=b4fdd5055731290eef12ad0d17f39d4a&scene=1&srcid=1106Imu9ZgwybID13e7y2nEi#wechat_redirect这里写链接内容
http://blog.csdn.net/lmj623565791/article/details/49883661
什么时热修复?
有没有办法以补丁的方式动态修复紧急Bug,不再需要重新发布App,不再需要用户重新下载,覆盖安装,在以前只能紧急换包,重新发布。成本非常高,也影响用户的口碑。最终决定使用热补丁动态修复技术,向用户下发Patch,在用户无感知的情况下,修复了外网问题,取得非常好的效果.
简单的概括一下,就是把多个dex文件塞入到app的classloader之中,但是android dex拆包方案中的类是没有重复的,如果classes.dex和classes1.dex中有重复的类,当用到这个重复的类的时候, 理论上,如果在不同的dex中有相同的类存在,那么会优先选择排在前面的dex文件的类.
如果引用者(也就是ModuleManager)这个类被打上了CLASS_ISPREVERIFIED标志,那么就会进行dex的校验。
概括一下就是如果以上方法中直接引用到的类(第一层级关系,不会进行递归搜索)和clazz都在同一个dex中的话,那么这个类就会被打上CLASS_ISPREVERIFIED
其中AntilazyLoad类会被打包成单独的hack.dex,这样当安装apk的时候,classes.dex内的类都会引用一个在不相同dex中的AntilazyLoad类,这样就防止了类被打上CLASS_ISPREVERIFIED的标志了,只要没被打上这个标志的类都可以进行打补丁操作
———-分割线———————————————————
对于热修复的原理,Android的ClassLoader体系,android中加载类一般使用的是PathClassLoader和DexClassLoader.
PathClassLoader:可以看出,Android是使用这个类作为其系统类和应用类的加载器。并且对于这个类呢,只能去加载已经安装到Android系统中的apk文件。
DexClassLoader:可以看出,该类呢,可以用来从.jar和.apk类型的文件内部加载classes.dex文件。可以用来执行非安装的程序代码。
一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回,如果找不到从下一个dex文件继续查找。
那么这样的话,我们可以在这个dexElements中去做一些事情,比如,在这个数组的第一个元素放置我们的patch.jar,里面包含修复过的类,这样的话,当遍历findClass的时候,我们修复的类就会被查找到,从而替代有bug的类.
CLASS_ISPREVERIFIED的问题:阻止该类打上CLASS_ISPREVERIFIED的标志.
其实就是两件事:1、动态改变BaseDexClassLoader对象间接引用的dexElements;2、在app打包的时候,阻止相关类去打上CLASS_ISPREVERIFIED标志
———-分割线———————————————————
技术的原理很简单,其实就是用ClassLoader加载机制,覆盖掉有问题的方法。所以我们的补丁其实就是有问题的类打成的一个包,
那么我们只需要将修复过的类编译后打包成dex即可
步骤如下:
1.将补丁类提取出来到一个文件夹里
2.将class文件打入一个jar包中 jar cvf path.jar *
3.将jar包转换成dex的jar包 dx –dex –output=path_dex.jar path.jar
这样就生成了补丁包path_dex.jar
实现javassist动态代码注入,从服务端发送补丁包到客户端,客户端咋gradle配置插入,并且在用户重启时生效

10.View的绘制流程:
http://a.codekk.com/detail/Android/lightSky/%E5%85%AC%E5%85%B1%E6%8A%80%E6%9C%AF%E7%82%B9%E4%B9%8B%20View%20%E7%BB%98%E5%88%B6%E6%B5%81%E7%A8%8B
这里写图片描述
layout 相关概念及核心

以上是关于Android 面试题总结的主要内容,如果未能解决你的问题,请参考以下文章

Android 面试题总结之Android 基础

Android面试题基础集锦总结《二》

Android 面试题总结之Android 基础

Android 面试题总结之Android 基础

Android 面试题总结之Android 基础

Android 面试题总结之Android 基础