Repository: francistao/LearningNotes Branch: master Commit: aa04b68b728a Files: 135 Total size: 509.4 KB Directory structure: gitextract_e96lsqs2/ ├── Part1/ │ ├── Android/ │ │ ├── AIDL.md │ │ ├── ANR问题.md │ │ ├── APP启动过程.md │ │ ├── Activity启动过程全解析.md │ │ ├── Android关于oom的解决方案.md │ │ ├── Android内存泄漏总结.md │ │ ├── Android几种进程.md │ │ ├── Android图片中的三级缓存.md │ │ ├── Android基础知识.md │ │ ├── Android开机过程.md │ │ ├── Android性能优化.md │ │ ├── Android系统机制.md │ │ ├── Art和Dalvik区别.md │ │ ├── Asynctask源码分析.md │ │ ├── Binder机制.md │ │ ├── Bitmap的分析与使用.md │ │ ├── EventBus用法详解.md │ │ ├── Fragment.md │ │ ├── Git操作.md │ │ ├── Handler内存泄漏分析及解决.md │ │ ├── Listview详解.md │ │ ├── MVC,MVP,MVVM的区别.md │ │ ├── MVP.md │ │ ├── Recyclerview和Listview的异同.md │ │ ├── SurfaceView.md │ │ ├── Zygote和System进程的启动过程.md │ │ ├── 事件分发机制.md │ │ ├── 开源框架源码分析.md │ │ ├── 插件化技术学习.md │ │ ├── 查漏补缺.md │ │ ├── 热修复技术.md │ │ ├── 线程通信基础流程分析.md │ │ └── 自定义控件.md │ └── DesignPattern/ │ ├── Builder模式.md │ ├── 代理模式.md │ ├── 单例模式.md │ ├── 原型模式.md │ ├── 外观模式.md │ ├── 常见的面向对象设计原则.md │ ├── 策略模式.md │ ├── 简单工厂.md │ ├── 观察者模式.md │ ├── 责任链模式.md │ └── 适配器模式.md ├── Part2/ │ ├── JVM/ │ │ ├── JVM.md │ │ ├── JVM类加载机制.md │ │ ├── Java内存区域与内存溢出.md │ │ └── 垃圾回收算法.md │ ├── JavaConcurrent/ │ │ ├── Java并发基础知识.md │ │ ├── NIO.md │ │ ├── Synchronized.md │ │ ├── Thread和Runnable实现多线程的区别.md │ │ ├── thread与runable如何实现多线程.md │ │ ├── volatile变量修饰符.md │ │ ├── 使用wait notify notifyall实现线程间通信.md │ │ ├── 可重入内置锁.md │ │ ├── 多线程环境中安全使用集合API.md │ │ ├── 守护线程与阻塞线程.md │ │ ├── 实现内存可见的两种方法比较:加锁和volatile变量.md │ │ ├── 死锁.md │ │ ├── 生产者和消费者问题.md │ │ ├── 线程中断.md │ │ └── 线程挂起、恢复与终止的正确方法.md │ └── JavaSE/ │ ├── ArrayList 、 LinkedList 、 Vector 的底层实现和区别.md │ ├── ArrayList源码剖析.md │ ├── Arraylist.md │ ├── Arraylist和Hashmap如何扩容等.md │ ├── Collection.md │ ├── HashMap源码剖析.md │ ├── HashTable源码剖析.md │ ├── Hashmap的hashcode的作用等.md │ ├── Java中的内存泄漏.md │ ├── Java基础知识.md │ ├── Java集合框架.md │ ├── LinkedHashMap源码剖析.md │ ├── LinkedList源码剖析.md │ ├── Linkedlist.md │ ├── List.md │ ├── Queue.md │ ├── Set.md │ ├── String源码分析.md │ ├── Vector源码剖析.md │ ├── hashmap和hashtable的底层实现和区别,两者和concurrenthashmap的区别。.md │ ├── 从源码分析HashMap.md │ ├── 反射机制.md │ └── 如何表达出Collection及其子类.md ├── Part3/ │ ├── Algorithm/ │ │ ├── LeetCode/ │ │ │ ├── two-sum.md │ │ │ └── zigzag-conversion.md │ │ ├── Lookup/ │ │ │ ├── 折半查找.md │ │ │ └── 顺序查找.md │ │ ├── Sort/ │ │ │ ├── 冒泡排序.md │ │ │ ├── 归并排序.md │ │ │ ├── 快速排序.md │ │ │ ├── 选择排序.md │ │ │ └── 面试中的 10 大排序算法总结.md │ │ ├── 剑指Offer/ │ │ │ ├── 1.七种方式实现singleton模式.md │ │ │ ├── 2.二维数组中的查找.md │ │ │ ├── 合并两个排序的链表.md │ │ │ ├── 旋转数组的最小数字.md │ │ │ ├── 面试题11:数值的整数次方.md │ │ │ ├── 面试题12:打印1到最大的n位数.md │ │ │ ├── 面试题44:扑克牌的顺子.md │ │ │ ├── 面试题45:圆圈中最后剩下的数字.md │ │ │ └── 面试题6:重建二叉树.md │ │ └── 程序员代码面试指南(左程云)/ │ │ ├── 1.设计一个有getMin功能的栈.md │ │ ├── 2.由两个栈组成的队列.md │ │ └── 3.如何仅用递归函数和栈操作逆序一个栈.md │ └── DataStructure/ │ ├── 数据结构(Java).md │ ├── 数组.md │ ├── 栈和队列.md │ └── 递归和非递归方式实现二叉树先、中、后序遍历.md ├── Part4/ │ ├── Network/ │ │ ├── Http协议.md │ │ ├── Socket.md │ │ ├── TCP与UDP.md │ │ └── 计算机网络基础汇总.md │ └── OperatingSystem/ │ ├── Linux系统的IPC.md │ └── 操作系统.md ├── Part5/ │ └── ReadingNotes/ │ ├── 《APP研发录》第1章读书笔记.md │ ├── 《APP研发录》第2章读书笔记.md │ ├── 《Android开发艺术探索》第一章笔记.md │ ├── 《Android开发艺术探索》第三章笔记.md │ ├── 《Android开发艺术探索》第二章笔记.md │ ├── 《Android开发艺术探索》第八章笔记.md │ ├── 《Android开发艺术探索》第十五章笔记.md │ ├── 《Android开发艺术探索》第四章笔记.md │ ├── 《Java编程思想》第一章读书笔记.md │ ├── 《Java编程思想》第二章读书笔记.md │ └── 《深入理解java虚拟机》第12章.md ├── Part6/ │ └── InterviewExperience/ │ ├── Alibaba.md │ ├── 新浪微博.md │ ├── 网易杭研.md │ ├── 美团.md │ ├── 蜻蜓FM.md │ └── 豌豆荚.md └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: Part1/Android/AIDL.md ================================================ #AIDL --- 1. 创建一个接口,再里面定义方法 ``` package com.example.taidl; interface ICalcAIDL { int add(int x , int y); int min(int x , int y ); } ``` build一下gen目录下会生成ICalcAIDL.java文件 ``` /* * This file is auto-generated. DO NOT MODIFY. * Original file: /Users/dream/Downloads/android/androidProject/TAIDL/src/com/example/taidl/ICalcAIDL.aidl */ package com.example.taidl; public interface ICalcAIDL extends android.os.IInterface { /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.example.taidl.ICalcAIDL { private static final java.lang.String DESCRIPTOR = "com.example.taidl.ICalcAIDL"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.example.taidl.ICalcAIDL interface, * generating a proxy if needed. */ public static com.example.taidl.ICalcAIDL asInterface(android.os.IBinder obj) { if ((obj==null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin!=null)&&(iin instanceof com.example.taidl.ICalcAIDL))) { return ((com.example.taidl.ICalcAIDL)iin); } return new com.example.taidl.ICalcAIDL.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_add: { data.enforceInterface(DESCRIPTOR); int _arg0; _arg0 = data.readInt(); int _arg1; _arg1 = data.readInt(); int _result = this.add(_arg0, _arg1); reply.writeNoException(); reply.writeInt(_result); return true; } case TRANSACTION_min: { data.enforceInterface(DESCRIPTOR); int _arg0; _arg0 = data.readInt(); int _arg1; _arg1 = data.readInt(); int _result = this.min(_arg0, _arg1); reply.writeNoException(); reply.writeInt(_result); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.example.taidl.ICalcAIDL { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public int add(int x, int y) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); int _result; try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeInt(x); _data.writeInt(y); mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0); _reply.readException(); _result = _reply.readInt(); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public int min(int x, int y) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); int _result; try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeInt(x); _data.writeInt(y); mRemote.transact(Stub.TRANSACTION_min, _data, _reply, 0); _reply.readException(); _result = _reply.readInt(); } finally { _reply.recycle(); _data.recycle(); } return _result; } } static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_min = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); } public int add(int x, int y) throws android.os.RemoteException; public int min(int x, int y) throws android.os.RemoteException; } ``` 2. 新建一个Service ``` package com.example.taidl; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; public class CalcService extends Service{ private static final String TAG = "server"; public void onCreate() { Log.e(TAG, "onCreate"); } public IBinder onBind(Intent t) { Log.e(TAG, "onBind"); return mBinder; } public void onDestroy() { Log.e(TAG, "onDestroy"); super.onDestroy(); } public boolean onUnbind(Intent intent) { Log.e(TAG, "onUnbind"); return super.onUnbind(intent); } public void onRebind(Intent intent) { Log.e(TAG, "onRebind"); super.onRebind(intent); } private final ICalcAIDL.Stub mBinder = new ICalcAIDL.Stub() { @Override public int min(int x, int y) throws RemoteException { return x + y; } @Override public int add(int x, int y) throws RemoteException { // TODO Auto-generated method stub return x - y; } }; } ``` 创建了一个mBinder对象,并在Service的onBind方法中返回 注册: ``` ``` 我们一会会在别的应用程序中通过Intent来查找此Service;这个不需要Activity,所以我也就没写Activity,安装完成也看不到安装图标,悄悄在后台运行着。服务端编写完毕。下面开始编写客户端: ``` package com.example.tclient; import com.example.taidl.ICalcAIDL; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.util.Log; import android.view.View; import android.widget.Toast; public class MainActivity extends Activity { private ICalcAIDL mCalcAidl; private ServiceConnection mServiceConn = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { Log.e("client", "onServiceDisconnected"); mCalcAidl = null; } @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.e("client", "onServiceConnected"); mCalcAidl = ICalcAIDL.Stub.asInterface(service); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } /** * 点击BindService按钮时调用 * @param view */ public void bindService(View view) { Intent intent = new Intent(); intent.setAction("com.example.taidl.calc"); bindService(intent, mServiceConn, Context.BIND_AUTO_CREATE); } /** * 点击unBindService按钮时调用 * @param view */ public void unbindService(View view) { unbindService(mServiceConn); } /** * 点击12+12按钮时调用 * @param view */ public void addInvoked(View view) throws Exception { if (mCalcAidl != null) { int addRes = mCalcAidl.add(12, 12); Toast.makeText(this, addRes + "", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "服务器被异常杀死,请重新绑定服务端", Toast.LENGTH_SHORT) .show(); } } /** * 点击50-12按钮时调用 * @param view */ public void minInvoked(View view) throws Exception { if (mCalcAidl != null) { int addRes = mCalcAidl.min(50, 12); Toast.makeText(this, addRes + "", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "服务器未绑定或被异常杀死,请重新绑定服务端", Toast.LENGTH_SHORT) .show(); } } } ``` 将服务端的aidl文件完整的复制过来,包名一定要一致。 ##分析AIDL生成的代码 1. 服务端 ``` private final ICalcAIDL.Stub mBinder = new ICalcAIDL.Stub() { @Override public int add(int x, int y) throws RemoteException { return x + y; } @Override public int min(int x, int y) throws RemoteException { return x - y; } }; ``` ICalcAILD.Stub来执行的,让我们来看看Stub这个类的声明: ``` public static abstract class Stub extends android.os.Binder implements com.zhy.calc.aidl.ICalcAIDL ``` 清楚的看到这个类是Binder的子类,是不是符合我们文章开通所说的服务端其实是一个Binder类的实例 接下来看它的onTransact()方法: ``` @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_add: { data.enforceInterface(DESCRIPTOR); int _arg0; _arg0 = data.readInt(); int _arg1; _arg1 = data.readInt(); int _result = this.add(_arg0, _arg1); reply.writeNoException(); reply.writeInt(_result); return true; } case TRANSACTION_min: { data.enforceInterface(DESCRIPTOR); int _arg0; _arg0 = data.readInt(); int _arg1; _arg1 = data.readInt(); int _result = this.min(_arg0, _arg1); reply.writeNoException(); reply.writeInt(_result); return true; } } return super.onTransact(code, data, reply, flags); } ``` 文章开头也说到服务端的Binder实例会根据客户端依靠Binder驱动发来的消息,执行onTransact方法,然后由其参数决定执行服务端的代码。 可以看到onTransact有四个参数 code , data ,replay , flags * code 是一个整形的唯一标识,用于区分执行哪个方法,客户端会传递此参数,告诉服务端执行哪个方法 * data客户端传递过来的参数 * replay服务器返回回去的值 * flags标明是否有返回值,0为有(双向),1为没有(单向) 我们仔细看case TRANSACTION_min中的代码 data.enforceInterface(DESCRIPTOR); 与客户端的writeInterfaceToken对用,标识远程服务的名称 ``` int _arg0; _arg0 = data.readInt(); int _arg1; _arg1 = data.readInt(); ``` 接下来分别读取了客户端传入的两个参数 ``` int _result = this.min(_arg0, _arg1); reply.writeNoException(); reply.writeInt(_result); ``` 然后执行this.min,即我们实现的min方法;返回result由reply写回。 add同理,可以看到服务端通过AIDL生成Stub的类,封装了服务端本来需要写的代码。 ###客户端 客户端主要通过ServiceConnected与服务端连接 ``` private ServiceConnection mServiceConn = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { Log.e("client", "onServiceDisconnected"); mCalcAidl = null; } @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.e("client", "onServiceConnected"); mCalcAidl = ICalcAIDL.Stub.asInterface(service); } }; ``` 如果你比较敏锐,应该会猜到这个onServiceConnected中的IBinder实例,其实就是我们文章开通所说的Binder驱动,也是一个Binder实例 在ICalcAIDL.Stub.asInterface中最终调用了: ``` return new com.zhy.calc.aidl.ICalcAIDL.Stub.Proxy(obj); ``` 这个Proxy实例传入了我们的Binder驱动,并且封装了我们调用服务端的代码,文章开头说,客户端会通过Binder驱动的transact()方法调用服务端代码 直接看Proxy中的add方法 ``` @Override public int add(int x, int y) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); int _result; try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeInt(x); _data.writeInt(y); mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0); _reply.readException(); _result = _reply.readInt(); } finally { _reply.recycle(); _data.recycle(); } return _result; } ``` 首先声明两个Parcel对象,一个用于传递数据,一个用户接收返回的数据 ``` _data.writeInterfaceToken(DESCRIPTOR);与服务器端的enforceInterfac对应 _data.writeInt(x); _data.writeInt(y);写入需要传递的参数 mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0); ``` 终于看到了我们的transact方法,第一个对应服务端的code,_data,_repay分别对应服务端的data,reply,0表示是双向的 ``` _reply.readException(); _result = _reply.readInt(); ``` 最后读出我们服务端返回的数据,然后return。可以看到和服务端的onTransact基本是一行一行对应的。 我们已经通过AIDL生成的代码解释了Android Binder框架的工作原理。Service的作用其实就是为我们创建Binder驱动,即服务端与客户端连接的桥梁。 ================================================ FILE: Part1/Android/ANR问题.md ================================================ #ANR --- 1、ANR排错一般有三种类型 1. KeyDispatchTimeout(5 seconds) --主要是类型按键或触摸事件在特定时间内无响应 2. BroadcastTimeout(10 seconds) --BroadcastReceiver在特定时间内无法处理完成 3. ServiceTimeout(20 secends) --小概率事件 Service在特定的时间内无法处理完成 2、哪些操作会导致ANR 在主线程执行以下操作: 1. 高耗时的操作,如图像变换 2. 磁盘读写,数据库读写操作 3. 大量的创建新对象 3、如何避免 1. UI线程尽量只做跟UI相关的工作 2. 耗时的操作(比如数据库操作,I/O,连接网络或者别的有可能阻塞UI线程的操作)把它放在单独的线程处理 3. 尽量用Handler来处理UIThread和别的Thread之间的交互 4、解决的逻辑 1. 使用AsyncTask 1. 在doInBackground()方法中执行耗时操作 2. 在onPostExecuted()更新UI 2. 使用Handler实现异步任务 1. 在子线程中处理耗时操作 2. 处理完成之后,通过handler.sendMessage()传递处理结果 3. 在handler的handleMessage()方法中更新UI 4. 或者使用handler.post()方法将消息放到Looper中 5、如何排查 1. 首先分析log 2. 从trace.txt文件查看调用stack,adb pull data/anr/traces.txt ./mytraces.txt 3. 看代码 4. 仔细查看ANR的成因(iowait?block?memoryleak?) 6、监测ANR的Watchdog 最近出来一个叫LeakCanary #FC(Force Close) ##什么时候会出现 1. Error 2. OOM,内存溢出 3. StackOverFlowError 4. Runtime,比如说空指针异常 ##解决的办法 1. 注意内存的使用和管理 2. 使用Thread.UncaughtExceptionHandler接口 ================================================ FILE: Part1/Android/APP启动过程.md ================================================ #APP启动过程 --- ![](http://7xntdm.com1.z0.glb.clouddn.com/activity_start_flow.png) * 上图就可以很好的说明App启动的过程 * ActivityManagerService组织回退栈时以ActivityRecord为基本单位,所有的ActivityRecord放在同一个ArrayList里,可以将mHistory看作一个栈对象,索引0所指的对象位于栈底,索引mHistory.size()-1所指的对象位于栈顶 * Zygote进程孵化出新的应用进程后,会执行ActivityThread类的main方法.在该方法里会先准备好Looper和消息队列,然后调用attach方法将应用进程绑定到ActivityManagerService,然后进入loop循环,不断地读取消息队列里的消息,并分发消息。 * ActivityThread的main方法执行后,应用进程接下来通知ActivityManagerService应用进程已启动,ActivityManagerService保存应用进程的一个代理对象,这样ActivityManagerService可以通过这个代理对象控制应用进程,然后ActivityManagerService通知**应用进程**创建入口Activity的实例,并执行它的生命周期方法 ================================================ FILE: Part1/Android/Activity启动过程全解析.md ================================================ #Activity启动过程 --- ###一些基本的概念 * ActivityManagerServices,简称AMS,服务端对象,负责系统中所有Activity的生命周期 * ActivityThread,App的真正入口。当开启App之后,会调用main()开始运行,开启消息循环队列,这就是传说中的UI线程或者叫主线程。与ActivityManagerServices配合,一起完成Activity的管理工作 * ApplicationThread,用来实现ActivityManagerService与ActivityThread之间的交互。在ActivityManagerService需要管理相关Application中的Activity的生命周期时,通过ApplicationThread的代理对象与ActivityThread通讯。 * ApplicationThreadProxy,是ApplicationThread在服务器端的代理,负责和客户端的ApplicationThread通讯。AMS就是通过该代理与ActivityThread进行通信的。 * Instrumentation,每一个应用程序只有一个Instrumentation对象,每个Activity内都有一个对该对象的引用。Instrumentation可以理解为应用进程的管家,ActivityThread要创建或暂停某个Activity时,都需要通过Instrumentation来进行具体的操作。 * ActivityStack,Activity在AMS的栈管理,用来记录已经启动的Activity的先后关系,状态信息等。通过ActivityStack决定是否需要启动新的进程。 * ActivityRecord,ActivityStack的管理对象,每个Activity在AMS对应一个* ActivityRecord,来记录Activity的状态以及其他的管理信息。其实就是服务器端的Activity对象的映像。 * TaskRecord,AMS抽象出来的一个“任务”的概念,是记录ActivityRecord的栈,一个“Task”包含若干个ActivityRecord。AMS用TaskRecord确保Activity启动和退出的顺序。如果你清楚Activity的4种launchMode,那么对这个概念应该不陌生。 ###回答一些问题 **zygote是什么?有什么作用?** zygote意为“受精卵“。Android是基于Linux系统的,而在Linux中,所有的进程都是由init进程直接或者是间接fork出来的,zygote进程也不例外。 在Android系统里面,zygote是一个进程的名字。Android是基于Linux System的,当你的手机开机的时候,Linux的内核加载完成之后就会启动一个叫“init“的进程。在Linux System里面,所有的进程都是由init进程fork出来的,我们的zygote进程也不例外。 我们都知道,每一个App其实都是 * 一个单独的dalvik虚拟机 * 一个单独的进程 所以当系统里面的第一个zygote进程运行之后,在这之后再开启App,就相当于开启一个新的进程。而为了实现资源共用和更快的启动速度,Android系统开启新进程的方式,是通过fork第一个zygote进程实现的。所以说,除了第一个zygote进程,其他应用所在的进程都是zygote的子进程,这下你明白为什么这个进程叫“受精卵”了吧?因为就像是一个受精卵一样,它能快速的分裂,并且产生遗传物质一样的细胞! **SystemServer是什么?有什么作用?它与zygote的关系是什么?** 首先我要告诉你的是,SystemServer也是一个进程,而且是由zygote进程fork出来的。 知道了SystemServer的本质,我们对它就不算太陌生了,这个进程是Android Framework里面两大非常重要的进程之一——另外一个进程就是上面的zygote进程。 为什么说SystemServer非常重要呢?因为系统里面重要的服务都是在这个进程里面开启的,比如 ActivityManagerService、PackageManagerService、WindowManagerService等等,看着是不是都挺眼熟的? 那么这些系统服务是怎么开启起来的呢? 在zygote开启的时候,会调用ZygoteInit.main()进行初始化 ``` public static void main(String argv[]) { ...ignore some code... //在加载首个zygote的时候,会传入初始化参数,使得startSystemServer = true boolean startSystemServer = false; for (int i = 1; i < argv.length; i++) { if ("start-system-server".equals(argv[i])) { startSystemServer = true; } else if (argv[i].startsWith(ABI_LIST_ARG)) { abiList = argv[i].substring(ABI_LIST_ARG.length()); } else if (argv[i].startsWith(SOCKET_NAME_ARG)) { socketName = argv[i].substring(SOCKET_NAME_ARG.length()); } else { throw new RuntimeException("Unknown command line argument: " + argv[i]); } } ...ignore some code... //开始fork我们的SystemServer进程 if (startSystemServer) { startSystemServer(abiList, socketName); } ...ignore some code... } ``` 我们看下startSystemServer()做了些什么 ``` /**留着这个注释,就是为了说明SystemServer确实是被fork出来的 * Prepare the arguments and fork for the system server process. */ private static boolean startSystemServer(String abiList, String socketName) throws MethodAndArgsCaller, RuntimeException { ...ignore some code... //留着这段注释,就是为了说明上面ZygoteInit.main(String argv[])里面的argv就是通过这种方式传递进来的 /* Hardcoded command line to start the system server */ String args[] = { "--setuid=1000", "--setgid=1000", "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1032,3001,3002,3003,3006,3007", "--capabilities=" + capabilities + "," + capabilities, "--runtime-init", "--nice-name=system_server", "com.android.server.SystemServer", }; int pid; try { parsedArgs = new ZygoteConnection.Arguments(args); ZygoteConnection.applyDebuggerSystemProperty(parsedArgs); ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs); //确实是fuck出来的吧,我没骗你吧~不对,是fork出来的 -_-||| /* Request to fork the system server process */ pid = Zygote.forkSystemServer( parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, parsedArgs.debugFlags, null, parsedArgs.permittedCapabilities, parsedArgs.effectiveCapabilities); } catch (IllegalArgumentException ex) { throw new RuntimeException(ex); } /* For child process */ if (pid == 0) { if (hasSecondZygote(abiList)) { waitForSecondaryZygote(socketName); } handleSystemServerProcess(parsedArgs); } return true; } ``` **ActivityManagerService是什么?什么时候初始化的?有什么作用?** ActivityManagerService,简称AMS,服务端对象,负责系统中所有Activity的生命周期。 ActivityManagerService进行初始化的时机很明确,就是在SystemServer进程开启的时候,就会初始化ActivityManagerService。从下面的代码中可以看到 ``` public final class SystemServer { //zygote的主入口 public static void main(String[] args) { new SystemServer().run(); } public SystemServer() { // Check for factory test mode. mFactoryTestMode = FactoryTest.getMode(); } private void run() { ...ignore some code... //加载本地系统服务库,并进行初始化 System.loadLibrary("android_servers"); nativeInit(); // 创建系统上下文 createSystemContext(); //初始化SystemServiceManager对象,下面的系统服务开启都需要调用SystemServiceManager.startService(Class),这个方法通过反射来启动对应的服务 mSystemServiceManager = new SystemServiceManager(mSystemContext); //开启服务 try { startBootstrapServices(); startCoreServices(); startOtherServices(); } catch (Throwable ex) { Slog.e("System", "******************************************"); Slog.e("System", "************ Failure starting system services", ex); throw ex; } ...ignore some code... } //初始化系统上下文对象mSystemContext,并设置默认的主题,mSystemContext实际上是一个ContextImpl对象。调用ActivityThread.systemMain()的时候,会调用ActivityThread.attach(true),而在attach()里面,则创建了Application对象,并调用了Application.onCreate()。 private void createSystemContext() { ActivityThread activityThread = ActivityThread.systemMain(); mSystemContext = activityThread.getSystemContext(); mSystemContext.setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar); } //在这里开启了几个核心的服务,因为这些服务之间相互依赖,所以都放在了这个方法里面。 private void startBootstrapServices() { ...ignore some code... //初始化ActivityManagerService mActivityManagerService = mSystemServiceManager.startService( ActivityManagerService.Lifecycle.class).getService(); mActivityManagerService.setSystemServiceManager(mSystemServiceManager); //初始化PowerManagerService,因为其他服务需要依赖这个Service,因此需要尽快的初始化 mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class); // 现在电源管理已经开启,ActivityManagerService负责电源管理功能 mActivityManagerService.initPowerManagement(); // 初始化DisplayManagerService mDisplayManagerService = mSystemServiceManager.startService(DisplayManagerService.class); //初始化PackageManagerService mPackageManagerService = PackageManagerService.main(mSystemContext, mInstaller, mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore); ...ignore some code... } } ``` 经过上面这些步骤,我们的ActivityManagerService对象已经创建好了,并且完成了成员变量初始化。而且在这之前,调用createSystemContext()创建系统上下文的时候,也已经完成了mSystemContext和ActivityThread的创建。注意,这是系统进程开启时的流程,在这之后,会开启系统的Launcher程序,完成系统界面的加载与显示。 你是否会好奇,我为什么说AMS是服务端对象?下面我给你介绍下Android系统里面的服务器和客户端的概念。 其实服务器客户端的概念不仅仅存在于Web开发中,在Android的框架设计中,使用的也是这一种模式。服务器端指的就是所有App共用的系统服务,比如我们这里提到的ActivityManagerService,和前面提到的PackageManagerService、WindowManagerService等等,这些基础的系统服务是被所有的App公用的,当某个App想实现某个操作的时候,要告诉这些系统服务,比如你想打开一个App,那么我们知道了包名和MainActivity类名之后就可以打开 ``` Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_LAUNCHER); ComponentName cn = new ComponentName(packageName, className); intent.setComponent(cn); startActivity(intent); ``` 但是,我们的App通过调用startActivity()并不能直接打开另外一个App,这个方法会通过一系列的调用,最后还是告诉AMS说:“我要打开这个App,我知道他的住址和名字,你帮我打开吧!”所以是AMS来通知zygote进程来fork一个新进程,来开启我们的目标App的。这就像是浏览器想要打开一个超链接一样,浏览器把网页地址发送给服务器,然后还是服务器把需要的资源文件发送给客户端的。 知道了Android Framework的客户端服务器架构之后,我们还需要了解一件事情,那就是我们的App和AMS(SystemServer进程)还有zygote进程分属于三个独立的进程,他们之间如何通信呢? 知道了Android Framework的客户端服务器架构之后,我们还需要了解一件事情,那就是我们的App和AMS(SystemServer进程)还有zygote进程分属于三个独立的进程,他们之间如何通信呢? App与AMS通过Binder进行IPC通信,AMS(SystemServer进程)与zygote通过Socket进行IPC通信。 那么AMS有什么用呢?在前面我们知道了,如果想打开一个App的话,需要AMS去通知zygote进程,除此之外,其实所有的Activity的开启、暂停、关闭都需要AMS来控制,所以我们说,AMS负责系统中所有Activity的生命周期。 在Android系统中,任何一个Activity的启动都是由AMS和应用程序进程(主要是ActivityThread)相互配合来完成的。AMS服务统一调度系统中所有进程的Activity启动,而每个Activity的启动过程则由其所属的进程具体来完成。 这样说你可能还是觉得比较抽象,没关系,下面有一部分是专门来介绍AMS与ActivityThread如何一起合作控制Activity的生命周期的。 **Launcher是什么?什么时候启动的?** 当我们点击手机桌面上的图标的时候,App就由Launcher开始启动了。但是,你有没有思考过Launcher到底是一个什么东西? Launcher本质上也是一个应用程序,和我们的App一样,也是继承自Activity packages/apps/Launcher2/src/com/android/launcher2/Launcher.java ``` public final class Launcher extends Activity implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks, View.OnTouchListener { } ``` Launcher实现了点击、长按等回调接口,来接收用户的输入。既然是普通的App,那么我们的开发经验在这里就仍然适用,比如,我们点击图标的时候,是怎么开启的应用呢?如果让你,你怎么做这个功能呢?捕捉图标点击事件,然后startActivity()发送对应的Intent请求呗!是的,Launcher也是这么做的,就是这么easy! 那么到底是处理的哪个对象的点击事件呢?既然Launcher是App,并且有界面,那么肯定有布局文件呀,是的,我找到了布局文件launcher.xml ``` ...ignore some code... ``` 为了方便查看,我删除了很多代码,从上面这些我们应该可以看出一些东西来:Launcher大量使用标签来实现界面的复用,而且定义了很多的自定义控件实现界面效果,dock_divider从布局的参数声明上可以猜出,是底部操作栏和上面图标布局的分割线,而paged_view_indicator则是页面指示器,和App首次进入的引导页下面的界面引导是一样的道理。当然,我们最关心的是Workspace这个布局,因为注释里面说在这里面包含了5个屏幕的单元格,想必你也猜到了,这个就是在首页存放我们图标的那五个界面(不同的ROM会做不同的DIY,数量不固定)。 接下来,我们应该打开workspace_screen布局,看看里面有什么东东。 workspace_screen.xml ``` ``` 里面就一个CellLayout,也是一个自定义布局,那么我们就可以猜到了,既然可以存放图标,那么这个自定义的布局很有可能是继承自ViewGroup或者是其子类,实际上,CellLayout确实是继承自ViewGroup。在CellLayout里面,只放了一个子View,那就是ShortcutAndWidgetContainer。从名字也可以看出来,ShortcutAndWidgetContainer这个类就是用来存放快捷图标和Widget小部件的,那么里面放的是什么对象呢? 在桌面上的图标,使用的是BubbleTextView对象,这个对象在TextView的基础之上,添加了一些特效,比如你长按移动图标的时候,图标位置会出现一个背景(不同版本的效果不同),所以我们找到BubbleTextView对象的点击事件,就可以找到Launcher如何开启一个App了。 除了在桌面上有图标之外,在程序列表中点击图标,也可以开启对应的程序。这里的图标使用的不是BubbleTextView对象,而是PagedViewIcon对象,我们如果找到它的点击事件,就也可以找到Launcher如何开启一个App。 其实说这么多,和今天的主题隔着十万八千里,上面这些东西,你有兴趣就看,没兴趣就直接跳过,不知道不影响这篇文章阅读。 BubbleTextView的点击事件在哪里呢?我来告诉你:在Launcher.onClick(View v)里面。 ``` /** * Launches the intent referred by the clicked shortcut */ public void onClick(View v) { ...ignore some code... Object tag = v.getTag(); if (tag instanceof ShortcutInfo) { // Open shortcut final Intent intent = ((ShortcutInfo) tag).intent; int[] pos = new int[2]; v.getLocationOnScreen(pos); intent.setSourceBounds(new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight())); //开始开启Activity咯~ boolean success = startActivitySafely(v, intent, tag); if (success && v instanceof BubbleTextView) { mWaitingForResume = (BubbleTextView) v; mWaitingForResume.setStayPressed(true); } } else if (tag instanceof FolderInfo) { //如果点击的是图标文件夹,就打开文件夹 if (v instanceof FolderIcon) { FolderIcon fi = (FolderIcon) v; handleFolderClick(fi); } } else if (v == mAllAppsButton) { ...ignore some code... } } ``` 从上面的代码我们可以看到,在桌面上点击快捷图标的时候,会调用 ``` startActivitySafely(v, intent, tag); ``` 那么从程序列表界面,点击图标的时候会发生什么呢?实际上,程序列表界面使用的是AppsCustomizePagedView对象,所以我在这个类里面找到了onClick(View v)。 com.android.launcher2.AppsCustomizePagedView.java ``` /** * The Apps/Customize page that displays all the applications, widgets, and shortcuts. */ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements View.OnClickListener, View.OnKeyListener, DragSource, PagedViewIcon.PressedCallback, PagedViewWidget.ShortPressListener, LauncherTransitionable { @Override public void onClick(View v) { ...ignore some code... if (v instanceof PagedViewIcon) { mLauncher.updateWallpaperVisibility(true); mLauncher.startActivitySafely(v, appInfo.intent, appInfo); } else if (v instanceof PagedViewWidget) { ...ignore some code.. } } } ``` 可以看到,调用的是 ``` mLauncher.startActivitySafely(v, appInfo.intent, appInfo); ``` 殊途同归 不管从哪里点击图标,调用的都是Launcher.startActivitySafely()。 下面我们就可以一步步的来看一下Launcher.startActivitySafely()到底做了什么事情。 ``` boolean startActivitySafely(View v, Intent intent, Object tag) { boolean success = false; try { success = startActivity(v, intent, tag); } catch (ActivityNotFoundException e) { Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e); } return success; } ``` 调用了startActivity(v, intent, tag) ``` boolean startActivity(View v, Intent intent, Object tag) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { boolean useLaunchAnimation = (v != null) && !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION); if (useLaunchAnimation) { if (user == null || user.equals(android.os.Process.myUserHandle())) { startActivity(intent, opts.toBundle()); } else { launcherApps.startMainActivity(intent.getComponent(), user, intent.getSourceBounds(), opts.toBundle()); } } else { if (user == null || user.equals(android.os.Process.myUserHandle())) { startActivity(intent); } else { launcherApps.startMainActivity(intent.getComponent(), user, intent.getSourceBounds(), null); } } return true; } catch (SecurityException e) { ... } return false; } ``` 这里会调用Activity.startActivity(intent, opts.toBundle()),这个方法熟悉吗?这就是我们经常用到的Activity.startActivity(Intent)的重载函数。而且由于设置了 ``` intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ``` 所以这个Activity会添加到一个新的Task栈中,而且,startActivity()调用的其实是startActivityForResult()这个方法。 ``` @Override public void startActivity(Intent intent, @Nullable Bundle options) { if (options != null) { startActivityForResult(intent, -1, options); } else { // Note we want to go through this call for compatibility with // applications that may have overridden the method. startActivityForResult(intent, -1); } } ``` 所以我们现在明确了,Launcher中开启一个App,其实和我们在Activity中直接startActivity()基本一样,都是调用了Activity.startActivityForResult()。 **Instrumentation是什么?和ActivityThread是什么关系?** 还记得前面说过的Instrumentation对象吗?每个Activity都持有Instrumentation对象的一个引用,但是整个进程只会存在一个Instrumentation对象。当startActivityForResult()调用之后,实际上还是调用了mInstrumentation.execStartActivity() ``` public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) { if (mParent == null) { Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options); if (ar != null) { mMainThread.sendActivityResult( mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData()); } ...ignore some code... } else { if (options != null) { //当现在的Activity有父Activity的时候会调用,但是在startActivityFromChild()内部实际还是调用的mInstrumentation.execStartActivity() mParent.startActivityFromChild(this, intent, requestCode, options); } else { mParent.startActivityFromChild(this, intent, requestCode); } } ...ignore some code... } ``` 下面是mInstrumentation.execStartActivity()的实现 ``` public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { IApplicationThread whoThread = (IApplicationThread) contextThread; ...ignore some code... try { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(); int result = ActivityManagerNative.getDefault() .startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null ? target.mEmbeddedID : null, requestCode, 0, null, options); checkStartActivityResult(result, intent); } catch (RemoteException e) { } return null; } ``` 所以当我们在程序中调用startActivity()的 时候,实际上调用的是Instrumentation的相关的方法。 Instrumentation意为“仪器”,我们先看一下这个类里面包含哪些方法吧 ![](http://i11.tietuku.com/15eb948d37d9d4b3.png) 我们可以看到,这个类里面的方法大多数和Application和Activity有关,是的,这个类就是完成对Application和Activity初始化和生命周期的工具类。比如说,我单独挑一个callActivityOnCreate()让你看看 ``` public void callActivityOnCreate(Activity activity, Bundle icicle) { prePerformCreate(activity); activity.performCreate(icicle); postPerformCreate(activity); } ``` 对activity.performCreate(icicle);这一行代码熟悉吗?这一行里面就调用了传说中的Activity的入口函数onCreate(),不信?接着往下看 Activity.performCreate() ``` final void performCreate(Bundle icicle) { onCreate(icicle); mActivityTransitionState.readState(icicle); performCreateCommon(); } ``` 没骗你吧,onCreate在这里调用了吧。但是有一件事情必须说清楚,那就是这个Instrumentation类这么重要,为啥我在开发的过程中,没有发现他的踪迹呢? 是的,Instrumentation这个类很重要,对Activity生命周期方法的调用根本就离不开他,他可以说是一个大管家,但是,这个大管家比较害羞,是一个女的,管内不管外,是老板娘~ 那么你可能要问了,老板是谁呀? 老板当然是大名鼎鼎的ActivityThread了! ActivityThread你都没听说过?那你肯定听说过传说中的UI线程吧?是的,这就是UI线程。我们前面说过,App和AMS是通过Binder传递信息的,那么ActivityThread就是专门与AMS的外交工作的。 AMS说:“ActivityThread,你给我暂停一个Activity!” ActivityThread就说:“没问题!”然后转身和Instrumentation说:“老婆,AMS让暂停一个Activity,我这里忙着呢,你快去帮我把这事办了把~” 于是,Instrumentation就去把事儿搞定了。 所以说,AMS是董事会,负责指挥和调度的,ActivityThread是老板,虽然说家里的事自己说了算,但是需要听从AMS的指挥,而Instrumentation则是老板娘,负责家里的大事小事,但是一般不抛头露面,听一家之主ActivityThread的安排。 **如何理解AMS和ActivityThread之间的Binder通信?** 前面我们说到,在调用startActivity()的时候,实际上调用的是 ``` mInstrumentation.execStartActivity() ``` 但是到这里还没完呢!里面又调用了下面的方法 ``` ActivityManagerNative.getDefault() .startActivity ``` ================================================ FILE: Part1/Android/Android关于oom的解决方案.md ================================================ #Android关于OOM的解决方案 ##OOM * 内存溢出(Out Of Memory) * 也就是说内存占有量超过了VM所分配的最大 ##出现OOM的原因 1. 加载对象过大 2. 相应资源过多,来不及释放 ##如何解决 1. 在内存引用上做些处理,常用的有软引用、强化引用、弱引用 2. 在内存中加载图片时直接在内存中作处理,如边界压缩 3. 动态回收内存 4. 优化Dalvik虚拟机的堆内存分配 5. 自定义堆内存大小 ================================================ FILE: Part1/Android/Android内存泄漏总结.md ================================================ #Android 内存泄漏总结 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了,简单粗俗的讲,就是该被释放的对象没有释放,一直被某个或某些实例所持有却不再被使用导致 GC 不能回收。最近自己阅读了大量相关的文档资料,打算做个 总结 沉淀下来跟大家一起分享和学习,也给自己一个警示,以后 coding 时怎么避免这些情况,提高应用的体验和质量。 我会从 java 内存泄漏的基础知识开始,并通过具体例子来说明 Android 引起内存泄漏的各种原因,以及如何利用工具来分析应用内存泄漏,最后再做总结。 ##Java 内存分配策略 Java 程序运行时的内存分配策略有三种,分别是静态分配,栈式分配,和堆式分配,对应的,三种存储策略使用的内存空间主要分别是静态存储区(也称方法区)、栈区和堆区。 * 静态存储区(方法区):主要存放静态数据、全局 static 数据和常量。这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在。 * 栈区 :当方法被执行时,方法体内的局部变量(其中包括基础数据类型、对象的引用)都在栈上创建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。因为栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 * 堆区 : 又称动态内存分配,通常就是指在程序运行时直接 new 出来的内存,也就是对象的实例。这部分内存在不使用时将会由 Java 垃圾回收器来负责回收。 ##栈与堆的区别: 在方法体内定义的(局部变量)一些基本类型的变量和对象的引用变量都是在方法的栈内存中分配的。当在一段方法块中定义一个变量时,Java 就会在栈中为该变量分配内存空间,当超过该变量的作用域后,该变量也就无效了,分配给它的内存空间也将被释放掉,该内存空间可以被重新使用。 堆内存用来存放所有由 new 创建的对象(包括该对象其中的所有成员变量)和数组。在堆中分配的内存,将由 Java 垃圾回收器来自动管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,这个特殊的变量就是我们上面说的引用变量。我们可以通过这个引用变量来访问堆中的对象或者数组。 举个例子: ``` public class Sample { int s1 = 0; Sample mSample1 = new Sample(); public void method() { int s2 = 1; Sample mSample2 = new Sample(); } } Sample mSample3 = new Sample(); ``` Sample 类的局部变量 s2 和引用变量 mSample2 都是存在于栈中,但 mSample2 指向的对象是存在于堆上的。 mSample3 指向的对象实体存放在堆上,包括这个对象的所有成员变量 s1 和 mSample1,而它自己存在于栈中。 结论: 局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储于堆中。—— 因为它们属于方法中的变量,生命周期随方法而结束。 成员变量全部存储与堆中(包括基本数据类型,引用和引用的对象实体)—— 因为它们属于类,类对象终究是要被new出来使用的。 了解了 Java 的内存分配之后,我们再来看看 Java 是怎么管理内存的。 ##Java是如何管理内存 Java的内存管理就是对象的分配和释放问题。在 Java 中,程序员需要通过关键字 new 为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间。另外,对象的释放是由 GC 决定和执行的。在 Java 中,内存的分配是由程序完成的,而内存的释放是由 GC 完成的,这种收支两条线的方法确实简化了程序员的工作。但同时,它也加重了JVM的工作。这也是 Java 程序运行速度较慢的原因之一。因为,GC 为了能够正确释放对象,GC 必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC 都需要进行监控。 监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。 为了更好理解 GC 的工作原理,我们可以将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外,每个线程对象可以作为一个图的起始顶点,例如大多程序从 main 进程开始执行,那么该图就是以 main 进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被 GC 回收。 以下,我们举一个例子说明如何用有向图表示内存管理。对于程序的每一个时刻,我们都有一个有向图表示JVM的内存分配情况。以下右图,就是左边程序运行到第6行的示意图。 ![](http://www.ibm.com/developerworks/cn/java/l-JavaMemoryLeak/1.gif) Java使用有向图的方式进行内存管理,可以消除引用循环的问题,例如有三个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的。这种方式的优点是管理内存的精度很高,但是效率较低。另外一种常用的内存管理技术是使用计数器,例如COM模型采用计数器方式管理构件,它与有向图相比,精度行低(很难处理循环引用的问题),但执行效率很高。 ##什么是Java中的内存泄露 在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。 在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,然后却不可达,由于C++中没有GC,这些内存将永远收不回来。在Java中,这些不可达的对象都由GC负责回收,因此程序员不需要考虑这部分的内存泄露。 通过分析,我们得知,对于C++,程序员需要自己管理边和顶点,而对于Java程序员只需要管理边就可以了(不需要管理顶点的释放)。通过这种方式,Java提高了编程的效率。 ![](http://www.ibm.com/developerworks/cn/java/l-JavaMemoryLeak/2.gif) 因此,通过以上分析,我们知道在Java中也有内存泄漏,但范围比C++要小一些。因为Java从语言上保证,任何对象都是可达的,所有的不可达对象都由GC管理。 对于程序员来说,GC基本是透明的,不可见的。虽然,我们只有几个函数可以访问GC,例如运行GC的函数System.gc(),但是根据Java语言规范定义, 该函数不保证JVM的垃圾收集器一定会执行。因为,不同的JVM实现者可能使用不同的算法管理GC。通常,GC的线程的优先级别较低。JVM调用GC的策略也有很多种,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但通常来说,我们不需要关心这些。除非在一些特定的场合,GC的执行影响应用程序的性能,例如对于基于Web的实时系统,如网络游戏等,用户不希望GC突然中断应用程序执行而进行垃圾回收,那么我们需要调整GC的参数,让GC能够通过平缓的方式释放内存,例如将垃圾回收分解为一系列的小步骤执行,Sun提供的HotSpot JVM就支持这一特性。 同样给出一个 Java 内存泄漏的典型例子, ``` Vector v = new Vector(10); for (int i = 1; i < 100; i++) { Object o = new Object(); v.add(o); o = null; } ``` 在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个 Vector 中,如果我们仅仅释放引用本身,那么 Vector 仍然引用该对象,所以这个对象对 GC 来说是不可回收的。因此,如果对象加入到Vector 后,还必须从 Vector 中删除,最简单的方法就是将 Vector 对象设置为 null。 **详细Java中的内存泄漏** 1.Java内存回收机制 不论哪种语言的内存分配方式,都需要返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址。Java中对象是采用new或者反射的方法创建的,这些对象的创建都是在堆(Heap)中分配的,所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的。GC为了能够正确释放对象,会监控每个对象的运行状况,对他们的申请、引用、被引用、赋值等状况进行监控,Java会使用有向图的方法进行管理内存,实时监控对象是否可以达到,如果不可到达,则就将其回收,这样也可以消除引用循环的问题。在Java语言中,判断一个内存空间是否符合垃圾收集标准有两个:一个是给对象赋予了空值null,以下再没有调用过,另一个是给对象赋予了新值,这样重新分配了内存空间。 2.Java内存泄漏引起的原因 内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory。j Java内存泄漏的根本原因是什么呢?长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景。具体主要有如下几大类: 1、静态集合类引起内存泄漏: 像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。 例如 ``` Static Vector v = new Vector(10); for (int i = 1; i<100; i++) { Object o = new Object(); v.add(o); o = null; } ``` 在这个例子中,循环申请Object 对象,并将所申请的对象放入一个Vector 中,如果仅仅释放引用本身(o=null),那么Vector 仍然引用该对象,所以这个对象对GC 来说是不可回收的。因此,如果对象加入到Vector 后,还必须从Vector 中删除,最简单的方法就是将Vector对象设置为null。 2、当集合里面的对象属性被修改后,再调用remove()方法时不起作用。 例如: ``` public static void main(String[] args) { Set set = new HashSet(); Person p1 = new Person("唐僧","pwd1",25); Person p2 = new Person("孙悟空","pwd2",26); Person p3 = new Person("猪八戒","pwd3",27); set.add(p1); set.add(p2); set.add(p3); System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:3 个元素! p3.setAge(2); //修改p3的年龄,此时p3元素对应的hashcode值发生改变 set.remove(p3); //此时remove不掉,造成内存泄漏 set.add(p3); //重新添加,居然添加成功 System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:4 个元素! for (Person person : set) { System.out.println(person); } } ``` 3、监听器 在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。 4、各种连接 比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。这种情况下一般都会在try里面去的连接,在finally里面释放连接。 5、内部类和外部模块的引用 内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。此外程序员还要小心外部模块不经意的引用,例如程序员A 负责A 模块,调用了B 模块的一个方法如: public void registerMsg(Object b); 这种调用就要非常小心了,传入了一个对象,很可能模块B就保持了对该对象的引用,这时候就需要注意模块B 是否提供相应的操作去除引用。 6、单例模式 不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被JVM正常回收,导致内存泄漏,考虑下面的例子: ``` class A{ public A(){ B.getInstance().setA(this); } .... } //B类采用单例模式 class B{ private A a; private static B instance=new B(); public B(){} public static B getInstance(){ return instance; } public void setA(A a){ this.a=a; } //getter... } ``` 显然B采用singleton模式,它持有一个A对象的引用,而这个A类的对象将不能被回收。想象下如果A是个比较复杂的对象或者集合类型会发生什么情况 ##Android中常见的内存泄漏汇总 --- ###集合类泄漏 集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。比如上面的典型例子就是其中一种情况,当然实际上我们在项目中肯定不会写这么 2B 的代码,但稍不注意还是很容易出现这种情况,比如我们都喜欢通过 HashMap 做一些缓存之类的事,这种情况就要多留一些心眼。 ###单例造成的内存泄漏 由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏。比如下面一个典型的例子, ``` public class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context; } public static AppManager getInstance(Context context) { if (instance == null) { instance = new AppManager(context); } return instance; } } ``` 这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要: 1、如果此时传入的是 Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以这将没有任何问题。 2、如果此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会被回收,这就造成泄漏了。 正确的方式应该改为下面这种方式: ``` public class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context.getApplicationContext();// 使用Application 的context } public static AppManager getInstance(Context context) { if (instance == null) { instance = new AppManager(context); } return instance; } } ``` 或者这样写,连 Context 都不用传进来了: ``` 在你的 Application 中添加一个静态方法,getContext() 返回 Application 的 context, ... context = getApplicationContext(); ... /** * 获取全局的context * @return 返回全局context对象 */ public static Context getContext(){ return context; } public class AppManager { private static AppManager instance; private Context context; private AppManager() { this.context = MyApplication.getContext();// 使用Application 的context } public static AppManager getInstance() { if (instance == null) { instance = new AppManager(); } return instance; } } ``` ###匿名内部类/非静态内部类和异步线程 非静态内部类创建静态实例造成的内存泄漏 有的时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,可能会出现这种写法: ``` public class MainActivity extends AppCompatActivity { private static TestResource mResource = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(mManager == null){ mManager = new TestResource(); } //... } class TestResource { //... } } ``` 这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确的做法为: 将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请按照上面推荐的使用Application 的 Context。当然,Application 的 context 不是万能的,所以也不能随便乱用,对于有些地方则必须使用 Activity 的 Context,对于Application,Service,Activity三者的Context的应用场景如下: ![](http://img.blog.csdn.net/20151123144226349?spm=5176.100239.blogcont.9.CtU1c4) 其中: NO1表示 Application 和 Service 可以启动一个 Activity,不过需要创建一个新的 task 任务队列。而对于 Dialog 而言,只有在 Activity 中才能创建 ###匿名内部类 android开发经常会继承实现Activity/Fragment/View,此时如果你使用了匿名类,并被异步线程持有了,那要小心了,如果没有任何措施这样一定会导致泄露 ``` public class MainActivity extends Activity { ... Runnable ref1 = new MyRunable(); Runnable ref2 = new Runnable() { @Override public void run() { } }; ... } ``` ref1和ref2的区别是,ref2使用了匿名内部类。我们来看看运行时这两个引用的内存: ![](http://img2.tbcdn.cn/L1/461/1/fb05ff6d2e68f309b94dd84352c81acfe0ae839e?spm=5176.100239.blogcont.10.CtU1c4) 可以看到,ref1没什么特别的。 但ref2这个匿名类的实现对象里面多了一个引用: this$0这个引用指向MainActivity.this,也就是说当前的MainActivity实例会被ref2持有,如果将这个引用再传入一个异步线程,此线程和此Acitivity生命周期不一致的时候,就造成了Activity的泄露。 ###Handler 造成的内存泄漏 Handler 的使用造成的内存泄漏问题应该说是最为常见了,很多时候我们为了避免 ANR 而不在主线程进行耗时操作,在处理网络任务或者封装一些请求回调等api都借助Handler来处理,但 Handler 不是万能的,对于 Handler 的使用代码编写一不规范即有可能造成内存泄漏。另外,我们知道 Handler、Message 和 MessageQueue 都是相互关联在一起的,万一 Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。 由于 Handler 属于 TLS(Thread Local Storage) 变量, 生命周期和 Activity 是不一致的。因此这种实现方式一般很难保证跟 View 或者 Activity 的生命周期保持一致,故很容易导致无法正确释放。 举个例子: ``` public class SampleActivity extends Activity { private final Handler mLeakyHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Post a message and delay its execution for 10 minutes. mLeakyHandler.postDelayed(new Runnable() { @Override public void run() { /* ... */ } }, 1000 * 60 * 10); // Go back to the previous Activity. finish(); } } ``` 在该 SampleActivity 中声明了一个延迟10分钟执行的消息 Message,mLeakyHandler 将其 push 进了消息队列 MessageQueue 里。当该 Activity 被 finish() 掉时,延迟执行任务的 Message 还会继续存在于主线程中,它持有该 Activity 的 Handler 引用,所以此时 finish() 掉的 Activity 就不会被回收了从而造成内存泄漏(因 Handler 为非静态内部类,它会持有外部类的引用,在这里就是指 SampleActivity)。 修复方法:在 Activity 中避免使用非静态内部类,比如上面我们将 Handler 声明为静态的,则其存活期跟 Activity 的生命周期就无关了。同时通过弱引用的方式引入 Activity,避免直接将 Activity 作为 context 传进去,见下面代码: ``` public class SampleActivity extends Activity { /** * Instances of static inner classes do not hold an implicit * reference to their outer class. */ private static class MyHandler extends Handler { private final WeakReference mActivity; public MyHandler(SampleActivity activity) { mActivity = new WeakReference(activity); } @Override public void handleMessage(Message msg) { SampleActivity activity = mActivity.get(); if (activity != null) { // ... } } } private final MyHandler mHandler = new MyHandler(this); /** * Instances of anonymous classes do not hold an implicit * reference to their outer class when they are "static". */ private static final Runnable sRunnable = new Runnable() { @Override public void run() { /* ... */ } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Post a message and delay its execution for 10 minutes. mHandler.postDelayed(sRunnable, 1000 * 60 * 10); // Go back to the previous Activity. finish(); } } ``` 综述,即推荐使用静态内部类 + WeakReference 这种方式。每次使用前注意判空。 前面提到了 WeakReference,所以这里就简单的说一下 Java 对象的几种引用类型。 Java对引用的分类有 Strong reference, SoftReference, WeakReference, PhatomReference 四种。 ![](https://gw.alicdn.com/tps/TB1U6TNLVXXXXchXFXXXXXXXXXX-644-546.jpg) 在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术。 软/弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列可以得知被回收的软/弱引用的对象列表,从而为缓冲器清除已失效的软/弱引用。 假设我们的应用会用到大量的默认图片,比如应用中有默认的头像,默认游戏图标等等,这些图片很多地方会用到。如果每次都去读取图片,由于读取文件需要硬件操作,速度较慢,会导致性能较低。所以我们考虑将图片缓存起来,需要的时候直接从内存中读取。但是,由于图片占用内存空间比较大,缓存很多图片需要很多的内存,就可能比较容易发生OutOfMemory异常。这时,我们可以考虑使用软/弱引用技术来避免这个问题发生。以下就是高速缓冲器的雏形: 首先定义一个HashMap,保存软引用对象。 ``` private Map > imageCache = new HashMap > (); ``` 再来定义一个方法,保存Bitmap的软引用到HashMap。 ![](https://gw.alicdn.com/tps/TB1oW_FLVXXXXXuaXXXXXXXXXXX-679-717.jpg) 使用软引用以后,在OutOfMemory异常发生之前,这些缓存的图片资源的内存空间可以被释放掉的,从而避免内存达到上限,避免Crash发生。 如果只是想避免OutOfMemory异常的发生,则可以使用软引用。如果对于应用的性能更在意,想尽快回收一些占用内存比较大的对象,则可以使用弱引用。 另外可以根据对象是否经常使用来判断选择软引用还是弱引用。如果该对象可能会经常使用的,就尽量用软引用。如果该对象不被使用的可能性更大些,就可以用弱引用。 ok,继续回到主题。前面所说的,创建一个静态Handler内部类,然后对 Handler 持有的对象使用弱引用,这样在回收时也可以回收 Handler 持有的对象,但是这样做虽然避免了 Activity 泄漏,不过 Looper 线程的消息队列中还是可能会有待处理的消息,所以我们在 Activity 的 Destroy 时或者 Stop 时应该移除消息队列 MessageQueue 中的消息。 下面几个方法都可以移除 Message: ``` public final void removeCallbacks(Runnable r); public final void removeCallbacks(Runnable r, Object token); public final void removeCallbacksAndMessages(Object token); public final void removeMessages(int what); public final void removeMessages(int what, Object object); ``` ###尽量避免使用 static 成员变量 如果成员变量被声明为 static,那我们都知道其生命周期将与整个app进程生命周期一样。 这会导致一系列问题,如果你的app进程设计上是长驻内存的,那即使app切到后台,这部分内存也不会被释放。按照现在手机app内存管理机制,占内存较大的后台进程将优先回收,yi'wei如果此app做过进程互保保活,那会造成app在后台频繁重启。当手机安装了你参与开发的app以后一夜时间手机被消耗空了电量、流量,你的app不得不被用户卸载或者静默。 这里修复的方法是: 不要在类初始时初始化静态成员。可以考虑lazy初始化。 架构设计上要思考是否真的有必要这样做,尽量避免。如果架构需要这么设计,那么此对象的生命周期你有责任管理起来。 ###避免 override finalize() 1、finalize 方法被执行的时间不确定,不能依赖与它来释放紧缺的资源。时间不确定的原因是: 虚拟机调用GC的时间不确定 Finalize daemon线程被调度到的时间不确定 2、finalize 方法只会被执行一次,即使对象被复活,如果已经执行过了 finalize 方法,再次被 GC 时也不会再执行了,原因是: 含有 finalize 方法的 object 是在 new 的时候由虚拟机生成了一个 finalize reference 在来引用到该Object的,而在 finalize 方法执行的时候,该 object 所对应的 finalize Reference 会被释放掉,即使在这个时候把该 object 复活(即用强引用引用住该 object ),再第二次被 GC 的时候由于没有了 finalize reference 与之对应,所以 finalize 方法不会再执行。 3、含有Finalize方法的object需要至少经过两轮GC才有可能被释放。 ###资源未关闭造成的内存泄漏 对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。 ###一些不良代码造成的内存压力 有些代码并不造成内存泄露,但是它们,或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存。 比如: Bitmap 没调用 recycle()方法,对于 Bitmap 对象在不使用时,我们应该先调用 recycle() 释放内存,然后才它设置为 null. 因为加载 Bitmap 对象的内存空间,一部分是 java 的,一部分 C 的(因为 Bitmap 分配的底层是通过 JNI 调用的 )。 而这个 recyle() 就是针对 C 部分的内存释放。 构造 Adapter 时,没有使用缓存的 convertView ,每次都在创建新的 converView。这里推荐使用 ViewHolder。 ##总结 对 Activity 等组件的引用应该控制在 Activity 的生命周期之内; 如果不能就考虑使用 getApplicationContext 或者 getApplication,以避免 Activity 被外部长生命周期的对象引用而泄露。 尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context ),即使要使用,也要考虑适时把外部成员变量置空;也可以在内部类中使用弱引用来引用外部类的变量。 对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏: 将内部类改为静态内部类 静态内部类中使用弱引用来引用外部类的成员变量 Handler 的持有的引用对象最好使用弱引用,资源释放时也可以清空 Handler 里面的消息。比如在 Activity onStop 或者 onDestroy 的时候,取消掉该 Handler 对象的 Message和 Runnable. 在 Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为 null,比如使用完Bitmap 后先调用 recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用 array.clear() ; array = null)等,最好遵循谁创建谁释放的原则。 正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。 保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。 ================================================ FILE: Part1/Android/Android几种进程.md ================================================ #Android几种进程 --- 1. 前台进程:即与用户正在交互的Activity或者Activity用到的Service等,如果系统内存不足时前台进程是最后被杀死的 2. 可见进程:可以是处于暂停状态(onPause)的Activity或者绑定在其上的Service,即被用户可见,但由于失去了焦点而不能与用户交互 3. 服务进程:其中运行着使用startService方法启动的Service,虽然不被用户可见,但是却是用户关心的,例如用户正在非音乐界面听的音乐或者正在非下载页面自己下载的文件等;当系统要空间运行前两者进程时才会被终止 4. 后台进程:其中运行着执行onStop方法而停止的程序,但是却不是用户当前关心的,例如后台挂着的QQ,这样的进程系统一旦没了有内存就首先被杀死 5. 空进程:不包含任何应用程序的程序组件的进程,这样的进程系统是一般不会让他存在的 如何避免后台进程被杀死: 1. 调用startForegound,让你的Service所在的线程成为前台进程 2. Service的onStartCommond返回START_STICKY或START_REDELIVER_INTENT 3. Service的onDestroy里面重新启动自己 ================================================ FILE: Part1/Android/Android图片中的三级缓存.md ================================================ #Android图片中的三级缓存 --- ##为什么要使用三级缓存 * 如今的 Android App 经常会需要网络交互,通过网络获取图片是再正常不过的事了 * 假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量。在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的应用,其用户数量级肯定要受到影响 * 特别是,当我们想要重复浏览一些图片时,如果每一次浏览都需要通过网络获取,流量的浪费可想而知 * 所以提出三级缓存策略,通过网络、本地、内存三级缓存图片,来减少不必要的网络交互,避免浪费流量 ##什么是三级缓存 * 网络加载,不优先加载,速度慢,浪费流量 * 本地缓存,次优先加载,速度快 * 内存缓存,优先加载,速度最快 ##三级缓存原理 * 首次加载 Android App 时,肯定要通过网络交互来获取图片,之后我们可以将图片保存至本地SD卡和内存中 * 之后运行 App 时,优先访问内存中的图片缓存,若内存中没有,则加载本地SD卡中的图片 * 总之,只在初次访问新内容时,才通过网络获取图片资源 参考链接 [http://www.jianshu.com/p/2cd59a79ed4a](http://www.jianshu.com/p/2cd59a79ed4a) ================================================ FILE: Part1/Android/Android基础知识.md ================================================ #Android: --- **五种布局: FrameLayout 、 LinearLayout 、 AbsoluteLayout 、 RelativeLayout 、 TableLayout 全都继承自ViewGroup,各自特点及绘制效率对比。** * FrameLayout(框架布局) 此布局是五种布局中最简单的布局,Android中并没有对child view的摆布进行控制,这个布局中所有的控件都会默认出现在视图的左上角,我们可以使用``android:layout_margin``,``android:layout_gravity``等属性去控制子控件相对布局的位置。 * LinearLayout(线性布局)    一行(或一列)只控制一个控件的线性布局,所以当有很多控件需要在一个界面中列出时,可以用LinearLayout布局。 此布局有一个需要格外注意的属性:``android:orientation=“horizontal|vertical``。 * 当`android:orientation="horizontal`时,*说明你希望将水平方向的布局交给**LinearLayout** *,其子元素的`android:layout_gravity="right|left"` 等控制水平方向的gravity值都是被忽略的,*此时**LinearLayout**中的子元素都是默认的按照水平从左向右来排*,我们可以用`android:layout_gravity="top|bottom"`等gravity值来控制垂直展示。 * 反之,可以知道 当`android:orientation="vertical`时,**LinearLayout**对其子元素展示上的的处理方式。 * AbsoluteLayout(绝对布局) 可以放置多个控件,并且可以自己定义控件的x,y位置 * RelativeLayout(相对布局) 这个布局也是相对自由的布局,Android 对该布局的child view的 水平layout& 垂直layout做了解析,由此我们可以FrameLayout的基础上使用标签或者Java代码对垂直方向 以及 水平方向 布局中的views进行任意的控制. * 相关属性: ```   android:layout_centerInParent="true|false"   android:layout_centerHorizontal="true|false"   android:layout_alignParentRight="true|false"    ``` * TableLayout(表格布局) 将子元素的位置分配到行或列中,一个TableLayout由许多的TableRow组成 --- **Activity生命周期。** * 启动Activity: onCreate()—>onStart()—>onResume(),Activity进入运行状态。 * Activity退居后台: 当前Activity转到新的Activity界面或按Home键回到主屏: onPause()—>onStop(),进入停滞状态。 * Activity返回前台: onRestart()—>**onStart()**—>onResume(),再次回到运行状态。 * Activity退居后台,且系统内存不足, 系统会杀死这个后台状态的Activity(此时这个Activity引用仍然处在任务栈中,只是这个时候引用指向的对象已经为null),若再次回到这个Activity,则会走onCreate()–>onStart()—>onResume()(将重新走一次Activity的初始化生命周期) * 锁屏:`onPause()->onStop()` * 解锁:`onStart()->onResume()` * 更多流程分支,请参照以下生命周期流程图 ![](http://img.blog.csdn.net/20130828141902812) --- **通过Acitivty的xml标签来改变任务栈的默认行为** * 使用``android:launchMode="standard|singleInstance|singleTask|singleTop"``来控制Acivity任务栈。 **任务栈**是一种后进先出的结构。位于栈顶的Activity处于焦点状态,当按下back按钮的时候,栈内的Activity会一个一个的出栈,并且调用其``onDestory()``方法。如果栈内没有Activity,那么系统就会回收这个栈,每个APP默认只有一个栈,以APP的包名来命名. * standard : 标准模式,每次启动Activity都会创建一个新的Activity实例,并且将其压入任务栈栈顶,而不管这个Activity是否已经存在。Activity的启动三回调(*onCreate()->onStart()->onResume()*)都会执行。 - singleTop : 栈顶复用模式.这种模式下,如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,所以它的启动三回调就不会执行,同时Activity的``onNewIntent()``方法会被回调.如果Activity已经存在但是不在栈顶,那么作用与*standard模式*一样. - singleTask: 栈内复用模式.创建这样的Activity的时候,系统会先确认它所需任务栈已经创建,否则先创建任务栈.然后放入Activity,如果栈中已经有一个Activity实例,那么这个Activity就会被调到栈顶,``onNewIntent()``,并且singleTask会清理在当前Activity上面的所有Activity.(clear top) - singleInstance : 加强版的singleTask模式,这种模式的Activity只能单独位于一个任务栈内,由于栈内复用的特性,后续请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了 Activity的堆栈管理以ActivityRecord为单位,所有的ActivityRecord都放在一个List里面.可以认为一个ActivityRecord就是一个Activity栈 --- **Activity缓存方法。** 有a、b两个Activity,当从a进入b之后一段时间,可能系统会把a回收,这时候按back,执行的不是a的onRestart而是onCreate方法,a被重新创建一次,这是a中的临时数据和状态可能就丢失了。 可以用Activity中的onSaveInstanceState()回调方法保存临时数据和状态,这个方法一定会在活动被回收之前调用。方法中有一个Bundle参数,putString()、putInt()等方法需要传入两个参数,一个键一个值。数据保存之后会在onCreate中恢复,onCreate也有一个Bundle类型的参数。 示例代码: ``` @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //这里,当Acivity第一次被创建的时候为空 //所以我们需要判断一下 if( savedInstanceState != null ){ savedInstanceState.getString("anAnt"); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString("anAnt","Android"); } ``` 一、onSaveInstanceState (Bundle outState) 当某个activity变得“容易”被系统销毁时,该activity的onSaveInstanceState就会被执行,除非该activity是被用户主动销毁的,例如当用户按BACK键的时候。 注意上面的双引号,何为“容易”?言下之意就是该activity还没有被销毁,而仅仅是一种可能性。这种可能性有哪些?通过重写一个activity的所有生命周期的onXXX方法,包括onSaveInstanceState和onRestoreInstanceState方法,我们可以清楚地知道当某个activity(假定为activity A)显示在当前task的最上层时,其onSaveInstanceState方法会在什么时候被执行,有这么几种情况: 1、当用户按下HOME键时。 这是显而易见的,系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activity A是否会被销毁,故系统会调用onSaveInstanceState,让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则 2、长按HOME键,选择运行其他的程序时。 3、按下电源按键(关闭屏幕显示)时。 4、从activity A中启动一个新的activity时。 5、屏幕方向切换时,例如从竖屏切换到横屏时。(如果不指定configchange属性) 在屏幕切换之前,系统会销毁activity A,在屏幕切换之后系统又会自动地创建activity A,所以onSaveInstanceState一定会被执行 总而言之,onSaveInstanceState的调用遵循一个重要原则,即当系统“未经你许可”时销毁了你的activity,则onSaveInstanceState会被系统调用,这是系统的责任,因为它必须要提供一个机会让你保存你的数据(当然你不保存那就随便你了)。另外,需要注意的几点: 1.布局中的每一个View默认实现了onSaveInstanceState()方法,这样的话,这个UI的任何改变都会自动地存储和在activity重新创建的时候自动地恢复。但是这种情况只有在你为这个UI提供了唯一的ID之后才起作用,如果没有提供ID,app将不会存储它的状态。 2.由于默认的onSaveInstanceState()方法的实现帮助UI存储它的状态,所以如果你需要覆盖这个方法去存储额外的状态信息,你应该在执行任何代码之前都调用父类的onSaveInstanceState()方法(super.onSaveInstanceState())。 既然有现成的可用,那么我们到底还要不要自己实现onSaveInstanceState()?这得看情况了,如果你自己的派生类中有变量影响到UI,或你程序的行为,当然就要把这个变量也保存了,那么就需要自己实现,否则就不需要。 3.由于onSaveInstanceState()方法调用的不确定性,你应该只使用这个方法去记录activity的瞬间状态(UI的状态)。不应该用这个方法去存储持久化数据。当用户离开这个activity的时候应该在onPause()方法中存储持久化数据(例如应该被存储到数据库中的数据)。 4.onSaveInstanceState()如果被调用,这个方法会在onStop()前被触发,但系统并不保证是否在onPause()之前或者之后触发。 二、onRestoreInstanceState (Bundle outState) 至于onRestoreInstanceState方法,需要注意的是,onSaveInstanceState方法和onRestoreInstanceState方法“不一定”是成对的被调用的,(本人注:我昨晚调试时就发现原来不一定成对被调用的!) onRestoreInstanceState被调用的前提是,activity A“确实”被系统销毁了,而如果仅仅是停留在有这种可能性的情况下,则该方法不会被调用,例如,当正在显示activity A的时候,用户按下HOME键回到主界面,然后用户紧接着又返回到activity A,这种情况下activity A一般不会因为内存的原因被系统销毁,故activity A的onRestoreInstanceState方法不会被执行 另外,onRestoreInstanceState的bundle参数也会传递到onCreate方法中,你也可以选择在onCreate方法中做数据还原。 还有onRestoreInstanceState在onstart之后执行。 至于这两个函数的使用,给出示范代码(留意自定义代码在调用super的前或后): ``` @Override public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putBoolean("MyBoolean", true); savedInstanceState.putDouble("myDouble", 1.9); savedInstanceState.putInt("MyInt", 1); savedInstanceState.putString("MyString", "Welcome back to Android"); // etc. super.onSaveInstanceState(savedInstanceState); } @Override public void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); boolean myBoolean = savedInstanceState.getBoolean("MyBoolean"); double myDouble = savedInstanceState.getDouble("myDouble"); int myInt = savedInstanceState.getInt("MyInt"); String myString = savedInstanceState.getString("MyString"); } ``` --- **Fragment的生命周期和activity如何的一个关系** 这我们引用本知识库里的一张图片: ![Mou icon](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/FlowchartDiagram.jpg?raw=true) **为什么在Service中创建子线程而不是Activity中** 这是因为Activity很难对Thread进行控制,当Activity被销毁之后,就没有任何其它的办法可以再重新获取到之前创建的子线程的实例。而且在一个Activity中创建的子线程,另一个Activity无法对其进行操作。但是Service就不同了,所有的Activity都可以与Service进行关联,然后可以很方便地操作其中的方法,即使Activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的Service中Binder的实例。因此,使用Service来处理后台任务,Activity就可以放心地finish,完全不需要担心无法对后台任务进行控制的情况。 **Intent的使用方法,可以传递哪些数据类型。** 通过查询Intent/Bundle的API文档,我们可以获知,Intent/Bundle支持传递基本类型的数据和基本类型的数组数据,以及String/CharSequence类型的数据和String/CharSequence类型的数组数据。而对于其它类型的数据貌似无能为力,其实不然,我们可以在Intent/Bundle的API中看到Intent/Bundle还可以传递Parcelable(包裹化,邮包)和Serializable(序列化)类型的数据,以及它们的数组/列表数据。 所以要让非基本类型和非String/CharSequence类型的数据通过Intent/Bundle来进行传输,我们就需要在数据类型中实现Parcelable接口或是Serializable接口。 [http://blog.csdn.net/kkk0526/article/details/7214247](http://blog.csdn.net/kkk0526/article/details/7214247) **Fragment生命周期** ![](http://7xntdm.com1.z0.glb.clouddn.com/fragment_lifecycle.png) 注意和Activity的相比的区别,按照执行顺序 * onAttach(),onDetach() * onCreateView(),onDestroyView() --- **Service的两种启动方法,有什么区别** 1.在Context中通过``public boolean bindService(Intent service,ServiceConnection conn,int flags)`` 方法来进行Service与Context的关联并启动,并且Service的生命周期依附于Context(**不求同时同分同秒生!但求同时同分同秒屎!!**)。 2.通过`` public ComponentName startService(Intent service)``方法去启动一个Service,此时Service的生命周期与启动它的Context无关。 3.要注意的是,whatever,**都需要在xml里注册你的Service**,就像这样: ``` ``` **广播(Broadcast Receiver)的两种动态注册和静态注册有什么区别。** * 静态注册:在AndroidManifest.xml文件中进行注册,当App退出后,Receiver仍然可以接收到广播并且进行相应的处理 * 动态注册:在代码中动态注册,当App退出后,也就没办法再接受广播了 --- **ContentProvider使用方法** [http://blog.csdn.net/juetion/article/details/17481039](http://blog.csdn.net/juetion/article/details/17481039) --- **目前能否保证service不被杀死** **Service设置成START_STICKY** * kill 后会被重启(等待5秒左右),重传Intent,保持与重启前一样 **提升service优先级** * 在AndroidManifest.xml文件中对于intent-filter可以通过``android:priority = "1000"``这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,**同时适用于广播**。 * 【结论】目前看来,priority这个属性貌似只适用于broadcast,对于Service来说可能无效 **提升service进程优先级** * Android中的进程是托管的,当系统进程空间紧张的时候,会依照优先级自动进行进程的回收 * 当service运行在低内存的环境时,将会kill掉一些存在的进程。因此进程的优先级将会很重要,可以在startForeground()使用startForeground()将service放到前台状态。这样在低内存时被kill的几率会低一些。 * 【结论】如果在极度极度低内存的压力下,该service还是会被kill掉,并且不一定会restart() **onDestroy方法里重启service** * service +broadcast 方式,就是当service走onDestory()的时候,发送一个自定义的广播,当收到广播的时候,重新启动service * 也可以直接在onDestroy()里startService * 【结论】当使用类似口口管家等第三方应用或是在setting里-应用-强制停止时,APP进程可能就直接被干掉了,onDestroy方法都进不来,所以还是无法保证 **监听系统广播判断Service状态** * 通过系统的一些广播,比如:手机重启、界面唤醒、应用状态改变等等监听并捕获到,然后判断我们的Service是否还存活,别忘记加权限 * 【结论】这也能算是一种措施,不过感觉监听多了会导致Service很混乱,带来诸多不便 **在JNI层,用C代码fork一个进程出来** * 这样产生的进程,会被系统认为是两个不同的进程.但是Android5.0之后可能不行 **root之后放到system/app变成系统级应用** **大招: 放一个像素在前台(手机QQ)** --- **动画有哪两类,各有什么特点?三种动画的区别** * tween 补间动画。通过指定View的初末状态和变化时间、方式,对View的内容完成一系列的图形变换来实现动画效果。 Alpha Scale Translate Rotate。 * frame 帧动画 AnimationDrawable 控制 animation-list xml布局 * PropertyAnimation 属性动画 --- **Android的数据存储形式。** * SQLite:SQLite是一个轻量级的数据库,支持基本的SQL语法,是常被采用的一种数据存储方式。 Android为此数据库提供了一个名为SQLiteDatabase的类,封装了一些操作数据库的api * SharedPreference: 除SQLite数据库外,另一种常用的数据存储方式,其本质就是一个xml文件,常用于存储较简单的参数设置。 * File: 即常说的文件(I/O)存储方法,常用语存储大数量的数据,但是缺点是更新数据将是一件困难的事情。 * ContentProvider: Android系统中能实现所有应用程序共享的一种数据存储方式,由于数据通常在各应用间的是互相私密的,所以此存储方式较少使用,但是其又是必不可少的一种存储方式。例如音频,视频,图片和通讯录,一般都可以采用此种方式进行存储。每个Content Provider都会对外提供一个公共的URI(包装成Uri对象),如果应用程序有数据需要共享时,就需要使用Content Provider为这些数据定义一个URI,然后其他的应用程序就通过Content Provider传入这个URI来对数据进行操作。 --- **Sqlite的基本操作。** [http://blog.csdn.net/zgljl2012/article/details/44769043](http://blog.csdn.net/zgljl2012/article/details/44769043) --- **如何判断应用被强杀** 在Application中定义一个static常量,赋值为-1,在欢迎界面改为0,如果被强杀,application重新初始化,在父类Activity判断该常量的值。 **应用被强杀如何解决** 如果在每一个Activity的onCreate里判断是否被强杀,冗余了,封装到Activity的父类中,如果被强杀,跳转回主界面,如果没有被强杀,执行Activity的初始化操作,给主界面传递intent参数,主界面会调用onNewIntent方法,在onNewIntent跳转到欢迎页面,重新来一遍流程。 **Json有什么优劣势。** **怎样退出终止App** **Asset目录与res目录的区别。** res 目录下面有很多文件,例如 drawable,mipmap,raw 等。res 下面除了 raw 文件不会被压缩外,其余文件都会被压缩。同时 res目录下的文件可以通过R 文件访问。Asset 也是用来存储资源,但是 asset 文件内容只能通过路径或者 AssetManager 读取。 [官方文档](https://developer.android.com/studio/projects/index.html) **Android怎么加速启动Activity。** 分两种情况,启动应用 和 普通Activity 启动应用 :Application 的构造方法,onCreate 方法中不要进行耗时操作,数据预读取(例如 init 数据) 放在异步中操作 启动普通的Activity:A 启动B 时不要在 A 的 onPause 中执行耗时操作。因为 B 的 onResume 方法必须等待 A 的 onPause 执行完成后才能运行 **Android内存优化方法:ListView优化,及时关闭资源,图片缓存等等。** **Android中弱引用与软引用的应用场景。** **Bitmap的四种属性,与每种属性队形的大小。** **View与View Group分类。自定义View过程:onMeasure()、onLayout()、onDraw()。** 如何自定义控件: 1. 自定义属性的声明和获取 * 分析需要的自定义属性 * 在res/values/attrs.xml定义声明 * 在layout文件中进行使用 * 在View的构造方法中进行获取 2. 测量onMeasure 3. 布局onLayout(ViewGroup) 4. 绘制onDraw 5. onTouchEvent 6. onInterceptTouchEvent(ViewGroup) 7. 状态的恢复与保存 **Android长连接,怎么处理心跳机制。** --- **View树绘制流程** --- **下拉刷新实现原理** --- **你用过什么框架,是否看过源码,是否知道底层原理。** Retrofit EventBus glide --- **Android5.0、6.0新特性。** Android5.0新特性: * MaterialDesign设计风格 * 支持多种设备 * 支持64位ART虚拟机 Android6.0新特性 * 大量漂亮流畅的动画 * 支持快速充电的切换 * 支持文件夹拖拽应用 * 相机新增专业模式 Android7.0新特性 * 分屏多任务 * 增强的Java8语言模式 * 夜间模式 --- **Context区别** * Activity和Service以及Application的Context是不一样的,Activity继承自ContextThemeWraper.其他的继承自ContextWrapper * 每一个Activity和Service以及Application的Context都是一个新的ContextImpl对象 * getApplication()用来获取Application实例的,但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的,但是如果在一些其它的场景,比如BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法,getApplicationContext()比getApplication()方法的作用域会更广一些,任何一个Context的实例,只要调用getApplicationContext()方法都可以拿到我们的Application对象。 * Activity在创建的时候会new一个ContextImpl对象并在attach方法中关联它,Application和Service也差不多。ContextWrapper的方法内部都是转调ContextImpl的方法 * 创建对话框传入Application的Context是不可以的 * 尽管Application、Activity、Service都有自己的ContextImpl,并且每个ContextImpl都有自己的mResources成员,但是由于它们的mResources成员都来自于唯一的ResourcesManager实例,所以它们看似不同的mResources其实都指向的是同一块内存 * Context的数量等于Activity的个数 + Service的个数 + 1,这个1为Application --- **IntentService的使用场景与特点。** >IntentService是Service的子类,是一个异步的,会自动停止的服务,很好解决了传统的Service中处理完耗时操作忘记停止并销毁Service的问题 优点: * 一方面不需要自己去new Thread * 另一方面不需要考虑在什么时候关闭该Service onStartCommand中回调了onStart,onStart中通过mServiceHandler发送消息到该handler的handleMessage中去。最后handleMessage中回调onHandleIntent(intent)。 --- **图片缓存** 查看每个应用程序最高可用内存: ``` int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); Log.d("TAG", "Max memory is " + maxMemory + "KB"); ``` --- **Gradle** 构建工具、Groovy语法、Java Jar包里面只有代码,aar里面不光有代码还包括代码还包括资源文件,比如 drawable 文件,xml 资源文件。对于一些不常变动的 Android Library,我们可以直接引用 aar,加快编译速度 --- **你是如何自学Android** 首先是看书和看视频敲代码,然后看大牛的博客,做一些项目,向github提交代码,觉得自己API掌握的不错之后,开始看进阶的书,以及看源码,看完源码学习到一些思想,开始自己造轮子,开始想代码的提升,比如设计模式,架构,重构等。 --- ================================================ FILE: Part1/Android/Android开机过程.md ================================================ # Android开机过程 * BootLoder引导,然后加载Linux内核. * 0号进程init启动.加载init.rc配置文件,配置文件有个命令启动了zygote进程 * zygote开始fork出SystemServer进程 * SystemServer加载各种JNI库,然后init1,init2方法,init2方法中开启了新线程ServerThread. * 在SystemServer中会创建一个socket客户端,后续AMS(ActivityManagerService)会通过此客户端和zygote通信 * ServerThread的run方法中开启了AMS,还孵化新进程ServiceManager,加载注册了一溜的服务,最后一句话进入loop 死循环 * run方法的SystemReady调用resumeTopActivityLocked打开锁屏界面 ================================================ FILE: Part1/Android/Android性能优化.md ================================================ #Android性能优化 --- ##合理管理内存 --- ###节制的使用Service 如果应用程序需要使用Service来执行后台任务的话,只有当任务正在执行的时候才应该让Service运行起来。当启动一个Service时,系统会倾向于将这个Service所依赖的进程进行保留,系统可以在LRUcache当中缓存的进程数量也会减少,导致切换程序的时候耗费更多性能。我们可以使用IntentService,当后台任务执行结束后会自动停止,避免了Service的内存泄漏。 ###当界面不可见时释放内存 当用户打开了另外一个程序,我们的程序界面已经不可见的时候,我们应当将所有和界面相关的资源进行释放。重写Activity的onTrimMemory()方法,然后在这个方法中监听TRIM_MEMORY_UI_HIDDEN这个级别,一旦触发说明用户离开了程序,此时就可以进行资源释放操作了。 ###当内存紧张时释放内存 onTrimMemory()方法还有很多种其他类型的回调,可以在手机内存降低的时候及时通知我们,我们应该根据回调中传入的级别来去决定如何释放应用程序的资源。 ###避免在Bitmap上浪费内存 读取一个Bitmap图片的时候,千万不要去加载不需要的分辨率。可以压缩图片等操作。 ###是有优化过的数据集合 Android提供了一系列优化过后的数据集合工具类,如SparseArray、SparseBooleanArray、LongSparseArray,使用这些API可以让我们的程序更加高效。HashMap工具类会相对比较低效,因为它需要为每一个键值对都提供一个对象入口,而SparseArray就避免掉了基本数据类型转换成对象数据类型的时间。 ###知晓内存的开支情况 * 使用枚举通常会比使用静态常量消耗两倍以上的内存,尽可能不使用枚举 * 任何一个Java类,包括匿名类、内部类,都要占用大概500字节的内存空间 * 任何一个类的实例要消耗12-16字节的内存开支,因此频繁创建实例也是会在一定程序上影响内存的 * 使用HashMap时,即使你只设置了一个基本数据类型的键,比如说int,但是也会按照对象的大小来分配内存,大概是32字节,而不是4字节,因此最好使用优化后的数据集合 ###谨慎使用抽象编程 在Android使用抽象编程会带来额外的内存开支,因为抽象的编程方法需要编写额外的代码,虽然这些代码根本执行不到,但是也要映射到内存中,不仅占用了更多的内存,在执行效率上也会有所降低。所以需要合理的使用抽象编程。 ###尽量避免使用依赖注入框架 使用依赖注入框架貌似看上去把findViewById()这一类的繁琐操作去掉了,但是这些框架为了要搜寻代码中的注解,通常都需要经历较长的初始化过程,并且将一些你用不到的对象也一并加载到内存中。这些用不到的对象会一直站用着内存空间,可能很久之后才会得到释放,所以可能多敲几行代码是更好的选择。 ###使用多个进程 谨慎使用,多数应用程序不该在多个进程中运行的,一旦使用不当,它甚至会增加额外的内存而不是帮我们节省内存。这个技巧比较适用于哪些需要在后台去完成一项独立的任务,和前台是完全可以区分开的场景。比如音乐播放,关闭软件,已经完全由Service来控制音乐播放了,系统仍然会将许多UI方面的内存进行保留。在这种场景下就非常适合使用两个进程,一个用于UI展示,另一个用于在后台持续的播放音乐。关于实现多进程,只需要在Manifast文件的应用程序组件声明一个android:process属性就可以了。进程名可以自定义,但是之前要加个冒号,表示该进程是一个当前应用程序的私有进程。 ##分析内存的使用情况 --- 系统不可能将所有的内存都分配给我们的应用程序,每个程序都会有可使用的内存上限,被称为堆大小。不同的手机堆大小不同,如下代码可以获得堆大小: ``` ActivityManager manager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); int heapSize = manager.getMemoryClass(); ``` 结果以MB为单位进行返回,我们开发时应用程序的内存不能超过这个限制,否则会出现OOM。 ###Android的GC操作 Android系统会在适当的时机触发GC操作,一旦进行GC操作,就会将一些不再使用的对象进行回收。GC操作会从一个叫做Roots的对象开始检查,所有它可以访问到的对象就说明还在使用当中,应该进行保留,而其他的对系那个就表示已经不再被使用了。 ###Android中内存泄漏 Android中的垃圾回收机制并不能防止内存泄漏的出现导致内存泄漏最主要的原因就是某些长存对象持有了一些其它应该被回收的对象的引用,导致垃圾回收器无法去回收掉这些对象,也就是出现内存泄漏了。比如说像Activity这样的系统组件,它又会包含很多的控件甚至是图片,如果它无法被垃圾回收器回收掉的话,那就算是比较严重的内存泄漏情况了。 举个例子,在MainActivity中定义一个内部类,实例化内部类对象,在内部类新建一个线程执行死循环,会导致内部类资源无法释放,MainActivity的控件和资源无法释放,导致OOM,可借助一系列工具,比如LeakCanary。 ##高性能编码优化 --- 都是一些微优化,在性能方面看不出有什么显著的提升的。使用合适的算法和数据结构是优化程序性能的最主要手段。 ###避免创建不必要的对象 不必要的对象我们应该避免创建: * 如果有需要拼接的字符串,那么可以优先考虑使用StringBuffer或者StringBuilder来进行拼接,而不是加号连接符,因为使用加号连接符会创建多余的对象,拼接的字符串越长,加号连接符的性能越低。 * 在没有特殊原因的情况下,尽量使用基本数据类型来代替封装数据类型,int比Integer要更加有效,其它数据类型也是一样。 * 当一个方法的返回值是String的时候,通常需要去判断一下这个String的作用是什么,如果明确知道调用方会将返回的String再进行拼接操作的话,可以考虑返回一个StringBuffer对象来代替,因为这样可以将一个对象的引用进行返回,而返回String的话就是创建了一个短生命周期的临时对象。 * 基本数据类型的数组也要优于对象数据类型的数组。另外两个平行的数组要比一个封装好的对象数组更加高效,举个例子,Foo[]和Bar[]这样的数组,使用起来要比Custom(Foo,Bar)[]这样的一个数组高效的多。 尽可能地少创建临时对象,越少的对象意味着越少的GC操作。 ###静态优于抽象 如果你并不需要访问一个对系那个中的某些字段,只是想调用它的某些方法来去完成一项通用的功能,那么可以将这个方法设置成静态方法,调用速度提升15%-20%,同时也不用为了调用这个方法去专门创建对象了,也不用担心调用这个方法后是否会改变对象的状态(静态方法无法访问非静态字段)。 ###对常量使用static final修饰符 ``` static int intVal = 42; static String strVal = "Hello, world!"; ``` 编译器会为上面的代码生成一个初始方法,称为方法,该方法会在定义类第一次被使用的时候调用。这个方法会将42的值赋值到intVal当中,从字符串常量表中提取一个引用赋值到strVal上。当赋值完成后,我们就可以通过字段搜寻的方式去访问具体的值了。 final进行优化: ``` static final int intVal = 42; static final String strVal = "Hello, world!"; ``` 这样,定义类就不需要方法了,因为所有的常量都会在dex文件的初始化器当中进行初始化。当我们调用intVal时可以直接指向42的值,而调用strVal会用一种相对轻量级的字符串常量方式,而不是字段搜寻的方式。 这种优化方式只对基本数据类型以及String类型的常量有效,对于其他数据类型的常量是无效的。 ###使用增强型for循环语法 ``` static class Counter { int mCount; } Counter[] mArray = ... public void zero() { int sum = 0; for (int i = 0; i < mArray.length; ++i) { sum += mArray[i].mCount; } } public void one() { int sum = 0; Counter[] localArray = mArray; int len = localArray.length; for (int i = 0; i < len; ++i) { sum += localArray[i].mCount; } } public void two() { int sum = 0; for (Counter a : mArray) { sum += a.mCount; } } ``` zero()最慢,每次都要计算mArray的长度,one()相对快得多,two()fangfa在没有JIT(Just In Time Compiler)的设备上是运行最快的,而在有JIT的设备上运行效率和one()方法不相上下,需要注意这种写法需要JDK1.5之后才支持。 Tips:ArrayList手写的循环比增强型for循环更快,其他的集合没有这种情况。因此默认情况下使用增强型for循环,而遍历ArrayList使用传统的循环方式。 ###多使用系统封装好的API 系统提供不了的Api完成不了我们需要的功能才应该自己去写,因为使用系统的Api很多时候比我们自己写的代码要快得多,它们的很多功能都是通过底层的汇编模式执行的。 举个例子,实现数组拷贝的功能,使用循环的方式来对数组中的每一个元素一一进行赋值当然可行,但是直接使用系统中提供的System.arraycopy()方法会让执行效率快9倍以上。 ###避免在内部调用Getters/Setters方法 面向对象中封装的思想是不要把类内部的字段暴露给外部,而是提供特定的方法来允许外部操作相应类的内部字段。但在Android中,字段搜寻比方法调用效率高得多,我们直接访问某个字段可能要比通过getters方法来去访问这个字段快3到7倍。但是编写代码还是要按照面向对象思维的,我们应该在能优化的地方进行优化,比如避免在内部调用getters/setters方法。 ##布局优化技巧 --- ###重用布局文件 **** 标签可以允许在一个布局当中引入另一个布局,那么比如说我们程序的所有界面都有一个公共的部分,这个时候最好的做法就是将这个公共的部分提取到一个独立的布局中,然后每个界面的布局文件当中来引用这个公共的布局。 Tips:如果我们要在标签中覆写layout属性,必须要将layout_width和layout_height这两个属性也进行覆写,否则覆写xiaoguo将不会生效。 **** 标签是作为标签的一种辅助扩展来使用的,它的主要作用是为了防止在引用布局文件时引用文件时产生多余的布局嵌套。布局嵌套越多,解析起来就越耗时,性能就越差。因此编写布局文件时应该让嵌套的层数越少越好。 举例:比如在LinearLayout里边使用一个布局。里边又有一个LinearLayout,那么其实就存在了多余的布局嵌套,使用merge可以解决这个问题。 ###仅在需要时才加载布局 某个布局当中的元素不是一起显示出来的,普通情况下只显示部分常用的元素,而那些不常用的元素只有在用户进行特定操作时才会显示出来。 举例:填信息时不是需要全部填的,有一个添加更多字段的选项,当用户需要添加其他信息的时候,才将另外的元素显示到界面上。用VISIBLE性能表现一般,可以用ViewStub。ViewStub也是View的一种,但是没有大小,没有绘制功能,也不参与布局,资源消耗非常低,可以认为完全不影响性能。 ``` ``` ``` public void onMoreClick() { ViewStub viewStub = (ViewStub) findViewById(R.id.view_stub); if (viewStub != null) { View inflatedView = viewStub.inflate(); editExtra1 = (EditText) inflatedView.findViewById(R.id.edit_extra1); editExtra2 = (EditText) inflatedView.findViewById(R.id.edit_extra2); editExtra3 = (EditText) inflatedView.findViewById(R.id.edit_extra3); } } ``` tips:ViewStub所加载的布局是不可以使用标签的,因此这有可能导致加载出来出来的布局存在着多余的嵌套结构。 ================================================ FILE: Part1/Android/Android系统机制.md ================================================ #Android系统机制 --- ###APP启动过程 1. Launcher线程捕获onclick的点击事件,调用Launcher.startActivitySafely,进一步调用Launcher.startActivity,最后调用父类Activity的startActivity。 2. Activity和ActivityManagerService交互,引入Instrumentation,将启动请求交给Instrumentation,调用Instrumentation.execStartActivity 3. ###Android内核解读-应用的安装过程 [http://blog.csdn.net/singwhatiwanna/article/details/19578947](http://blog.csdn.net/singwhatiwanna/article/details/19578947) apk的安装过程分为两步: 1. 将apk文件复制到程序目录下(/data/app/) 2. 为应用创建数据目录(/data/data/package name/)、提取dex文件到指定目录(/data/delvik-cache/)、修改系统包管理信息。 ###View的事件体系 ###Handler消息机制 ###AsyncTask AyncTask是一个抽象类。 需要重写的方法有四个: 1. onPreExecute() 这个方法会在后台任务开始之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等 2. doInBackGround(Params...) 在子线程中运行,不可更新UI,返回的是执行结果,第三个参数为Void不返回 3. onProgressUpdate(Progress...) 利用参数可以进行UI操作。 4. onPostExecute(Result) 返回的数据会作为参数传递到此方法中,可以利用返回的一些数据来进行一些UI操作。 ``` class DownloadTask extends AsyncTask { @Override protected void onPreExecute() { progressDialog.show(); } @Override protected Boolean doInBackground(Void... params) { try { while (true) { int downloadPercent = doDownload(); publishProgress(downloadPercent); if (downloadPercent >= 100) { break; } } } catch (Exception e) { return false; } return true; } @Override protected void onProgressUpdate(Integer... values) { progressDialog.setMessage("当前下载进度:" + values[0] + "%"); } @Override protected void onPostExecute(Boolean result) { progressDialog.dismiss(); if (result) { Toast.makeText(context, "下载成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show(); } } } ``` 启动这个任务: ``` new DownloadTask().execute(); ``` ###图片缓存机制 ================================================ FILE: Part1/Android/Art和Dalvik区别.md ================================================ #ART和Dalvik区别 --- Art上应用启动快,运行快,但是耗费更多存储空间,安装时间长,总的来说ART的功效就是"空间换时间"。 ART: Ahead of Time Dalvik: Just in Time 什么是Dalvik:Dalvik是Google公司自己设计用于Android平台的Java虚拟机。Dalvik虚拟机是Google等厂商合作开发的Android移动设备平台的核心组成部分之一,它可以支持已转换为.dex(即Dalvik Executable)格式的Java应用程序的运行,.dex格式是专为Dalvik应用设计的一种压缩格式,适合内存和处理器速度有限的系统。Dalvik经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik应用作为独立的Linux进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。 什么是ART:Android操作系统已经成熟,Google的Android团队开始将注意力转向一些底层组件,其中之一是负责应用程序运行的Dalvik运行时。Google开发者已经花了两年时间开发更快执行效率更高更省电的替代ART运行时。ART代表Android Runtime,其处理应用程序执行的方式完全不同于Dalvik,Dalvik是依靠一个Just-In-Time(JIT)编译器去解释字节码。开发者编译后的应用代码需要通过一个解释器在用户的设备上运行,这一机制并不高效,但让应用能更容易在不同硬件和架构上运行。ART则完全改变了这套做法,在应用安装的时候就预编译字节码到机器语言,这一机制叫Ahead-Of-Time(AOT)编译。在移除解释代码这一过程后,应用程序执行将更有效率,启动更快。 ART优点: 1. 系统性能的显著提升 2. 应用启动更快、运行更快、体验更流畅、触感反馈更及时 3. 更长的电池续航能力 4. 支持更低的硬件 ART缺点: 1. 更大的存储空间占用,可能会增加10%-20% 2. 更长的应用安装时间 ================================================ FILE: Part1/Android/Asynctask源码分析.md ================================================ #AsyncTask --- 首先从Android3.0开始,系统要求网络访问必须在子线程中进行,否则网络访问将会失败并抛出NetworkOnMainThreadException这个异常,这样做是为了避免主线程由于耗时操作所阻塞从而出现ANR现象。AsyncTask封装了线程池和Handler。AsyncTask有两个线程池:SerialExecutor和THREAD_POOL_EXECUTOR。前者是用于任务的排队,默认是串行的线程池:后者用于真正的执行任务。AsyncTask还有一个Handler,叫InternalHandler,用于将执行环境从线程池切换到主线程。AsyncTask内部就是通过InternalHandler来发送任务执行的进度以及执行结束等消息。 AsyncTask排队执行过程:系统先把参数Params封装为FutureTask对象,它相当于Runnable,接着FutureTask交给SerialExcutor的execute方法,它先把FutureTask插入到任务队列tasks中,如果这个时候没有正在活动的AsyncTask任务,那么就会执行下一个AsyncTask任务,同时当一个AsyncTask任务执行完毕之后,AsyncTask会继续执行其他任务直到所有任务都被执行为止。 --- 关于线程池,AsyncTask对应的线程池ThreadPoolExecutor都是进程范围内共享的,都是static的,所以是AsyncTask控制着进程范围内所有的子类实例。由于这个限制的存在,当使用默认线程池时,如果线程数超过线程池的最大容量,线程池就会爆掉(3.0默认串行执行,不会出现这个问题)。针对这种情况。可以尝试自定义线程池,配合AsyncTask使用。 ================================================ FILE: Part1/Android/Binder机制.md ================================================ #Binder机制 --- 首先Binder是Android系统进程间通信(IPC)方式之一。 Binder使用Client-Server通信方式。Binder框架定义了四个角色:Server,Client,ServiceManager以及Binder驱动。其中Server,Client,ServiceManager运行于用户空间,驱动运行于内核空间。Binder驱动程序提供设备文件/dev/binder与用户空间交互,Client、Server和Service Manager通过open和ioctl文件操作函数与Binder驱动程序进行通信。 Server创建了Binder实体,为其取一个字符形式,可读易记的名字,将这个Binder连同名字以数据包的形式通过Binder驱动发送给ServiceManager,通知ServiceManager注册一个名字为XX的Binder,它位于Server中。驱动为这个穿过进程边界的Binder创建位于内核中的实体结点以及ServiceManager对实体的引用,将名字以及新建的引用打包给ServiceManager。ServiceManager收数据包后,从中取出名字和引用填入一张查找表中。但是一个Server若向ServiceManager注册自己Binder就必须通过0这个引用和ServiceManager的Binder通信。Server向ServiceManager注册了Binder实体及其名字后,Client就可以通过名字获得该Binder的引用了。Clent也利用保留的0号引用向ServiceManager请求访问某个Binder:我申请名字叫XX的Binder的引用。ServiceManager收到这个连接请求,从请求数据包里获得Binder的名字,在查找表里找到该名字对应的条目,从条目中取出Binder引用,将该引用作为回复发送给发起请求的Client。 当然,不是所有的Binder都需要注册给ServiceManager广而告之的。Server端可以通过已经建立的Binder连接将创建的Binder实体传给Client,当然这条已经建立的Binder连接必须是通过实名Binder实现。由于这个Binder没有向ServiceManager注册名字,所以是匿名Binder。Client将会收到这个匿名Binder的引用,通过这个引用向位于Server中的实体发送请求。匿名Binder为通信双方建立一条私密通道,只要Server没有把匿名Binder发给别的进程,别的进程就无法通过穷举或猜测等任何方式获得该Binder的引用,向该Binder发送请求。 --- ###为什么Binder只进行了一次数据拷贝? Linux内核实际上没有从一个用户空间到另一个用户空间直接拷贝的函数,需要先用copy_from_user()拷贝到内核空间,再用copy_to_user()拷贝到另一个用户空间。为了实现用户空间到用户空间的拷贝,mmap()分配的内存除了映射进了接收方进程里,还映射进了内核空间。所以调用copy_from_user()将数据拷贝进内核空间也相当于拷贝进了接收方的用户空间,这就是Binder只需一次拷贝的‘秘密’。 最底层的是Android的ashmen(Anonymous shared memory)机制,它负责辅助实现内存的分配,以及跨进程所需要的内存共享。AIDL(android interface definition language)对Binder的使用进行了封装,可以让开发者方便的进行方法的远程调用,后面会详细介绍。Intent是最高一层的抽象,方便开发者进行常用的跨进程调用。 从英文字面上意思看,Binder具有粘结剂的意思那么它是把什么东西粘接在一起呢?在Android系统的Binder机制中,由一系统组件组成,分别是Client、Server、Service Manager和Binder驱动,其中Client、Server、Service Manager运行在用户空间,Binder驱动程序运行内核空间。Binder就是一种把这四个组件粘合在一起的粘连剂了,其中,核心组件便是Binder驱动程序了,ServiceManager提供了辅助管理的功能,Client和Server正是Binder驱动和ServiceManager提供的基础设施上,进行Client-Server之间的通信。 1. Client、Server和ServiceManager实现在用户空间中,Binder驱动实现在内核空间中 2. Binder驱动程序和ServiceManager在Android平台中已经实现,开发者只需要在用户空间实现自己的Client和Server 3. Binder驱动程序提供设备文件/dev/binder与用户空间交互,Client、Server和ServiceManager通过open和ioctl文件操作函数与Binder驱动程序进行通信 4. Client和Server之间的进程间通信通过Binder驱动程序间接实现 5. ServiceManager是一个守护进程,用来管理Server,并向Client提供查询Server接口的能力 服务器端:一个Binder服务器就是一个Binder类的对象。当创建一个Binder对象后,内部就会开启一个线程,这个线程用户接收binder驱动发送的消息,收到消息后,会执行相关的服务代码。 Binder驱动:当服务端成功创建一个Binder对象后,Binder驱动也会相应创建一个mRemote对象,该对象的类型也是Binder类,客户就可以借助这个mRemote对象来访问远程服务。 客户端:客户端要想访问Binder的远程服务,就必须获取远程服务的Binder对象在binder驱动层对应的binder驱动层对应的mRemote引用。当获取到mRemote对象的引用后,就可以调用相应Binde对象的服务了。 在这里我们可以看到,客户是通过Binder驱动来调用服务端的相关服务。首先,在服务端创建一个Binder对象,接着客户端通过获取Binder驱动中Binder对象的引用来调用服务端的服务。在Binder机制中正是借着Binder驱动将不同进程间的组件bind(粘连)在一起,实现通信。 mmap将一个文件或者其他对象映射进内存。文件被映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会凋零。munmap执行相反的操作,删除特定地址区域的对象映射。 当使用mmap映射文件到进程后,就可以直接操作这段虚拟地址进行文件的读写等操作,不必再调用read,write等系统调用。但需注意,直接对该段内存写时不会写入超过当前文件大小的内容。 使用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次内存数据:一次从输入文件到共享内存区,另一次从共享内存到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域,而是保持共享区域,直到通信完成为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除内存映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。 aidl主要就帮助了我们完成了包装数据和解包的过程,并调用了transact过程,而用来传递的数据包我们就称为parcel AIDL:xxx.aidl -> xxx.java ,注册service 1. 用aidl定义需要被调用方法接口 2. 实现这些方法 3. 调用这些方法 ================================================ FILE: Part1/Android/Bitmap的分析与使用.md ================================================ ## **Bitmap**的分析与使用 - Bitmap的创建 - 创建Bitmap的时候,Java不提供`new Bitmap()`的形式去创建,而是通过`BitmapFactory`中的静态方法去创建,如:`BitmapFactory.decodeStream(is);//通过InputStream去解析生成Bitmap`(这里就不贴`BitmapFactory`中创建`Bitmap`的方法了,大家可以自己去看它的源码),我们跟进`BitmapFactory`中创建`Bitmap`的源码,最终都可以追溯到这几个native函数 ``` private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage, Rect padding, Options opts); private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd, Rect padding, Options opts); private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts); private static native Bitmap nativeDecodeByteArray(byte[] data, int offset, int length, Options opts); ``` 而`Bitmap`又是Java对象,这个Java对象又是从native,也就是C/C++中产生的,所以,在Android中Bitmap的内存管理涉及到两部分,一部分是*native*,另一部分是*dalvik*,也就是我们常说的java堆(如果对java堆与栈不了解的同学可以戳),到这里基本就已经了解了创建Bitmap的一些内存中的特性(大家可以使用``adb shell dumpsys meminfo``去查看Bitmap实例化之后的内存使用情况)。 - Bitmap的使用 - 我们已经知道了`BitmapFactory`是如何通过各种资源创建`Bitmap`了,那么我们如何合理的使用它呢?以下是几个我们使用`Bitmap`需要关注的点 1. **Size** - 这里我们来算一下,在Android中,如果采用`Config.ARGB_8888`的参数去创建一个`Bitmap`,[这是Google推荐的配置色彩参数](https://developer.android.com/reference/android/graphics/Bitmap.Config.html),也是Android4.4及以上版本默认创建Bitmap的Config参数(``Bitmap.Config.inPreferredConfig``的默认值),那么每一个像素将会占用4byte,如果一张手机照片的尺寸为1280×720,那么我们可以很容易的计算出这张图片占用的内存大小为 1280x720x4 = 3686400(byte) = 3.5M,一张未经处理的照片就已经3.5M了! 显而易见,在开发当中,这是我们最需要关注的问题,否则分分钟OOM! - *那么,我们一般是如何处理Size这个重要的因素的呢?*,当然是调整`Bitmap`的大小到适合的程度啦!辛亏在`BitmapFactory`中,我们可以很方便的通过`BitmapFactory.Options`中的`options.inSampleSize`去设置`Bitmap`的压缩比,官方给出的说法是 > If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory....For example, inSampleSize == 4 returns an image that is 1/4 the width/height of the original, and 1/16 the number of pixels. Any value <= 1 is treated the same as 1. 很简洁明了啊!也就是说,只要按计算方法设置了这个参数,就可以完成我们Bitmap的Size调整了。那么,应该怎么调整姿势才比较舒服呢?下面先介绍其中一种通过``InputStream``的方式去创建``Bitmap``的方法,上一段从Gallery中获取照片并且将图片Size调整到合适手机尺寸的代码: ``` static final int PICK_PICS = 9; public void startGallery(){ Intent i = new Intent(); i.setAction(Intent.ACTION_PICK); i.setType("image/*"); startActivityForResult(i,PICK_PICS); } private int[] getScreenWithAndHeight(){ WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); DisplayMetrics dm = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(dm); return new int[]{dm.widthPixels,dm.heightPixels}; } /** * * @param actualWidth 图片实际的宽度,也就是options.outWidth * @param actualHeight 图片实际的高度,也就是options.outHeight * @param desiredWidth 你希望图片压缩成为的目的宽度 * @param desiredHeight 你希望图片压缩成为的目的高度 * @return */ private int findBestSampleSize(int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) { double wr = (double) actualWidth / desiredWidth; double hr = (double) actualHeight / desiredHeight; double ratio = Math.min(wr, hr); float n = 1.0f; //这里我们为什么要寻找 与ratio最接近的2的倍数呢? //原因就在于API中对于inSimpleSize的注释:最终的inSimpleSize应该为2的倍数,我们应该向上取与压缩比最接近的2的倍数。 while ((n * 2) <= ratio) { n *= 2; } return (int) n; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if(resultCode == RESULT_OK){ switch (requestCode){ case PICK_PICS: Uri uri = data.getData(); InputStream is = null; try { is = getContentResolver().openInputStream(uri); } catch (FileNotFoundException e) { e.printStackTrace(); } BitmapFactory.Options options = new BitmapFactory.Options(); //当这个参数为true的时候,意味着你可以在解析时候不申请内存的情况下去获取Bitmap的宽和高 //这是调整Bitmap Size一个很重要的参数设置 options.inJustDecodeBounds = true; BitmapFactory.decodeStream( is,null,options ); int realHeight = options.outHeight; int realWidth = options.outWidth; int screenWidth = getScreenWithAndHeight()[0]; int simpleSize = findBestSampleSize(realWidth,realHeight,screenWidth,300); options.inSampleSize = simpleSize; //当你希望得到Bitmap实例的时候,不要忘了将这个参数设置为false options.inJustDecodeBounds = false; try { is = getContentResolver().openInputStream(uri); } catch (FileNotFoundException e) { e.printStackTrace(); } Bitmap bitmap = BitmapFactory.decodeStream(is,null,options); iv.setImageBitmap(bitmap); try { is.close(); is = null; } catch (IOException e) { e.printStackTrace(); } break; } } super.onActivityResult(requestCode, resultCode, data); } ``` 我们来看看这段代码的功效: 压缩前:![压缩前](https://leanote.com/api/file/getImage?fileId=578d9ed8ab644135ea01684c) 压缩后:![压缩后](https://leanote.com/api/file/getImage?fileId=578d9f76ab644135ea016851) **对比条件为:1080P的魅族Note3拍摄的高清无码照片** 2. **Reuse** 上面介绍了``BitmapFactory``通过``InputStream``去创建`Bitmap`的这种方式,以及``BitmapFactory.Options.inSimpleSize`` 和 ``BitmapFactory.Options.inJustDecodeBounds``的使用方法,但将单个Bitmap加载到UI是简单的,但是如果我们需要一次性加载大量的图片,事情就会变得复杂起来。`Bitmap`是吃内存大户,我们不希望多次解析相同的`Bitmap`,也不希望可能不会用到的`Bitmap`一直存在于内存中,所以,这个场景下,`Bitmap`的重用变得异常的重要。 *在这里只介绍一种``BitmapFactory.Options.inBitmap``的重用方式,下一篇文章会介绍使用三级缓存来实现Bitmap的重用。* 根据官方文档[在Android 3.0 引进了BitmapFactory.Options.inBitmap](https://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inBitmap),如果这个值被设置了,decode方法会在加载内容的时候去重用已经存在的bitmap. 这意味着bitmap的内存是被重新利用的,这样可以提升性能, 并且减少了内存的分配与回收。然而,使用inBitmap有一些限制。特别是在Android 4.4 之前,只支持同等大小的位图。 我们看来看看这个参数最基本的运用方法。 ``` new BitmapFactory.Options options = new BitmapFactory.Options(); //inBitmap只有当inMutable为true的时候是可用的。 options.inMutable = true; Bitmap reusedBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.reused_btimap,options); options.inBitmap = reusedBitmap; ``` 这样,当你在下一次decodeBitmap的时候,将设置了`options.inMutable=true`以及`options.inBitmap`的`Options`传入,Android就会复用你的Bitmap了,具体实例: ``` @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(reuseBitmap()); } private LinearLayout reuseBitmap(){ LinearLayout linearLayout = new LinearLayout(this); linearLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); linearLayout.setOrientation(LinearLayout.VERTICAL); ImageView iv = new ImageView(this); iv.setLayoutParams(new ViewGroup.LayoutParams(500,300)); options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; //inBitmap只有当inMutable为true的时候是可用的。 options.inMutable = true; BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options); //压缩Bitmap到我们希望的尺寸 //确保不会OOM options.inSampleSize = findBestSampleSize(options.outWidth,options.outHeight,500,300); options.inJustDecodeBounds = false; Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options); options.inBitmap = bitmap; iv.setImageBitmap(bitmap); linearLayout.addView(iv); ImageView iv1 = new ImageView(this); iv1.setLayoutParams(new ViewGroup.LayoutParams(500,300)); iv1.setImageBitmap( BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options)); linearLayout.addView(iv1); ImageView iv2 = new ImageView(this); iv2.setLayoutParams(new ViewGroup.LayoutParams(500,300)); iv2.setImageBitmap( BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options)); linearLayout.addView(iv2); return linearLayout; } ``` 以上代码中,我们在解析了一次一张1080P分辨率的图片,并且设置在`options.inBitmap`中,然后分别decode了同一张图片,并且传入了相同的`options`。最终只占用一份第一次解析`Bitmap`的内存。 3. **Recycle** 一定要记得及时回收Bitmap,否则如上分析,你的native以及dalvik的内存都会被一直占用着,最终导致OOM ``` // 先判断是否已经回收 if(bitmap != null && !bitmap.isRecycled()){ // 回收并且置为null bitmap.recycle(); bitmap = null; } System.gc(); ``` - Enjoy Android :) 如果有误,轻喷,欢迎指正。 ================================================ FILE: Part1/Android/EventBus用法详解.md ================================================ #EventBus --- ###概述 EventBus是一款针对Android优化的发布/订阅(publish/subscribe)事件总线。主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息。简化了应用程序内各组件间、组件与后台线程间的通信。优点是开销小,代码更优雅。以及将发送者和接收者解耦。比如请求网络,等网络返回时通过Handler或Broadcast通知UI,两个Fragment之间需要通过Listener通信,这些需求都可以通过EventBus实现。 ###EventBus作为一个消息总线,有三个主要的元素: * Event:事件。可以是任意类型的对象 * Subscriber:事件订阅者,接收特定的事件。在EventBus中,使用约定来指定事件订阅者以简化使用。即所有事件订阅都都是以onEvent开头的函数,具体来说,函数的名字是onEvent,onEventMainThread,onEventBackgroundThread,onEventAsync这四个,这个和 ThreadMode(下面讲)有关。 * Publisher:事件发布者,用于通知 Subscriber 有事件发生。可以在任意线程任意位置发送事件,直接调用eventBus.post(Object) 方法,可以自己实例化 EventBus 对象,但一般使用默认的单例就好了:EventBus.getDefault(), 根据post函数参数的类型,会自动调用订阅相应类型事件的函数。 ###关于ThreadMode 前面说了,Subscriber的函数只能是那4个,因为每个事件订阅函数都是和一个ThreadMode相关联的,ThreadMode指定了会调用的函数。有以下四个ThreadMode: * PostThread:事件的处理在和事件的发送在相同的进程,所以事件处理时间不应太长,不然影响事件的发送线程,而这个线程可能是UI线程。对应的函数名是onEvent。 * MainThread: 事件的处理会在UI线程中执行。事件处理时间不能太长,这个不用说的,长了会ANR的,对应的函数名是onEventMainThread。 * BackgroundThread:事件的处理会在一个后台线程中执行,对应的函数名是onEventBackgroundThread,虽然名字是BackgroundThread,事件处理是在后台线程,但事件处理时间还是不应该太长,因为如果发送事件的线程是后台线程,会直接执行事件,如果当前线程是UI线程,事件会被加到一个队列中,由一个线程依次处理这些事件,如果某个事件处理时间太长,会阻塞后面的事件的派发或处理。 * Async:事件处理会在单独的线程中执行,主要用于在后台线程中执行耗时操作,每个事件会开启一个线程(有线程池),但最好限制线程的数目。 根据事件订阅都函数名称的不同,会使用不同的ThreadMode,比如果在后台线程加载了数据想在UI线程显示,订阅者只需把函数命名onEventMainThread。 对相应的函数名,进一步解释一下: **onEvent**:如果使用onEvent作为订阅函数,那么该事件在哪个线程发布出来的,onEvent就会在这个线程中运行,也就是说发布事件和接收事件线程在同一个线程。使用这个方法时,在onEvent方法中不能执行耗时操作,如果执行耗时操作容易导致事件分发延迟。 **onEventMainThread**:如果使用onEventMainThread作为订阅函数,那么不论事件是在哪个线程中发布出来的,onEventMainThread都会在UI线程中执行,接收事件就会在UI线程中运行,这个在Android中是非常有用的,因为在Android中只能在UI线程中跟新UI,所以在onEvnetMainThread方法中是不能执行耗时操作的。 **onEventBackground**:如果使用onEventBackgrond作为订阅函数,那么如果事件是在UI线程中发布出来的,那么onEventBackground就会在子线程中运行,如果事件本来就是子线程中发布出来的,那么onEventBackground函数直接在该子线程中执行。 **onEventAsync**:使用这个函数作为订阅函数,那么无论事件在哪个线程发布,都会创建新的子线程在执行onEventAsync。 ##基本用法 ###引入EventBus: ``` compile 'org.greenrobot:eventbus:3.0.0' ``` 定义事件: ``` public class MessageEvent { /* Additional fields if needed */ } ``` 注册事件接收者: ``` eventBus.register(this); ``` 发送事件: ``` eventBus.post(event) ``` 接收消息并处理: ``` // 3.0后不再要求事件以 onEvent 开头,而是采用注解的方式 @Subscribe(threadMode = ThreadMode.MAIN) public void receive(MessageEvent event){} ``` 注销事件接收: ``` eventBus.unregister(this); ``` 索引加速: ``` 3.0 后引入了索引加速(默认不开启)的功能,即通过 apt 编译插件的方式,在代码编译的时候对注解进行索引,避免了以往通过反射造成的性能损耗。 如何使用可以参考[官方文档](http://greenrobot.org/eventbus/documentation/subscriber-index/) ``` 最后,proguard 需要做一些额外处理: ``` #EventBus -keepclassmembers class ** { public void onEvent*(**); void onEvent*(**); } ``` ================================================ FILE: Part1/Android/Fragment.md ================================================ #Fragment ##为何产生 * 同时适配手机和平板、UI和逻辑的共享。 ##介绍 * Fragment也会被加入回退栈中。 * Fragment拥有自己的生命周期和接受、处理用户的事件 * 可以动态的添加、替换和移除某个Fragment ##生命周期 * 必须依存于Activity ![Mou icon](http://img.blog.csdn.net/20140719225005356?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbG1qNjIzNTY1Nzkx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) * Fragment依附于Activity的生命状态 * ![Mou icon](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/FlowchartDiagram.jpg?raw=true) 生命周期中那么多方法,懵逼了的话我们就一起来看一下每一个生命周期方法的含义吧。 ##Fragment生命周期方法含义: * `public void onAttach(Context context)` * onAttach方法会在Fragment于窗口关联后立刻调用。从该方法开始,就可以通过Fragment.getActivity方法获取与Fragment关联的窗口对象,但因为Fragment的控件未初始化,所以不能够操作控件。 * `public void onCreate(Bundle savedInstanceState)` * 在调用完onAttach执行完之后立刻调用onCreate方法,可以在Bundle对象中获取一些在Activity中传过来的数据。通常会在该方法中读取保存的状态,获取或初始化一些数据。在该方法中不要进行耗时操作,不然窗口不会显示。 * `public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState)` * 该方法是Fragment很重要的一个生命周期方法,因为会在该方法中创建在Fragment显示的View,其中inflater是用来装载布局文件的,container是``标签的父标签对应对象,savedInstanceState参数可以获取Fragment保存的状态,如果未保存那么就为null。 * `public void onViewCreated(View view,Bundle savedInstanceState)` * Android在创建完Fragment中的View对象之后,会立刻回调该方法。其种view参数就是onCreateView中返回的view,而bundle对象用于一般用途。 * `public void onActivityCreated(Bundle savedInstanceState)` * 在Activity的onCreate方法执行完之后,Android系统会立刻调用该方法,表示窗口已经初始化完成,从这一个时候开始,就可以在Fragment中使用getActivity().findViewById(Id);来操控Activity中的view了。 * `public void onStart()` * 这个没啥可讲的,但有一个细节需要知道,当系统调用该方法的时候,fragment已经显示在ui上,但还不能进行互动,因为onResume方法还没执行完。 * `public void onResume()` * 该方法为fragment从创建到显示Android系统调用的最后一个生命周期方法,调用完该方法时候,fragment就可以与用户互动了。 * `public void onPause()` * fragment由活跃状态变成非活跃状态执行的第一个回调方法,通常可以在这个方法中保存一些需要临时暂停的工作。如保存音乐播放进度,然后在onResume中恢复音乐播放进度。 * `public void onStop()` * 当onStop返回的时候,fragment将从屏幕上消失。 * `public void onDestoryView()` * 该方法的调用意味着在 `onCreateView` 中创建的视图都将被移除。 * `public void onDestroy()` * Android在Fragment不再使用时会调用该方法,要注意的是~这时Fragment还和Activity藕断丝连!并且可以获得Fragment对象,但无法对获得的Fragment进行任何操作(呵~呵呵~我已经不听你的了)。 * `public void onDetach()` * 为Fragment生命周期中的最后一个方法,当该方法执行完后,Fragment与Activity不再有关联(分手!我们分手!!(╯‵□′)╯︵┻━┻)。 ##Fragment比Activity多了几个额外的生命周期回调方法: * onAttach(Activity):当Fragment和Activity发生关联时使用 * onCreateView(LayoutInflater, ViewGroup, Bundle):创建该Fragment的视图 * onActivityCreate(Bundle):当Activity的onCreate方法返回时调用 * onDestoryView():与onCreateView相对应,当该Fragment的视图被移除时调用 * onDetach():与onAttach相对应,当Fragment与Activity关联被取消时调用 ###注意:除了onCreateView,其他的所有方法如果你重写了,必须调用父类对于该方法的实现 ##Fragment与Activity之间的交互 * Fragment与Activity之间的交互可以通过`Fragment.setArguments(Bundle args)`以及`Fragment.getArguments()`来实现。 ##Fragment状态的持久化。 由于Activity会经常性的发生配置变化,所以依附它的Fragment就有需要将其状态保存起来问题。下面有两个常用的方法去将Fragment的状态持久化。 * 方法一: * 可以通过`protected void onSaveInstanceState(Bundle outState)`,`protected void onRestoreInstanceState(Bundle savedInstanceState)` 状态保存和恢复的方法将状态持久化。 * 方法二(更方便,让Android自动帮我们保存Fragment状态): * 我们只需要将Fragment在Activity中作为一个变量整个保存,只要保存了Fragment,那么Fragment的状态就得到保存了,所以呢..... * `FragmentManager.putFragment(Bundle bundle, String key, Fragment fragment)` 是在Activity中保存Fragment的方法。 * `FragmentManager.getFragment(Bundle bundle, String key)` 是在Activity中获取所保存的Frament的方法。 * 很显然,key就传入Fragment的id,fragment就是你要保存状态的fragment,但,我们注意到上面的两个方法,第一个参数都是Bundle,这就意味着*FragmentManager*是通过Bundle去保存Fragment的。但是,这个方法仅仅能够保存Fragment中的控件状态,比如说EditText中用户已经输入的文字(*注意!在这里,控件需要设置一个id,否则Android将不会为我们保存控件的状态*),而Fragment中需要持久化的变量依然会丢失,但依然有解决办法,就是利用方法一! * 下面给出状态持久化的事例代码: ``` /** Activity中的代码 **/ FragmentB fragmentB; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fragment_activity); if( savedInstanceState != null ){ fragmentB = (FragmentB) getSupportFragmentManager().getFragment(savedInstanceState,"fragmentB"); } init(); } @Override protected void onSaveInstanceState(Bundle outState) { if( fragmentB != null ){ getSupportFragmentManager().putFragment(outState,"fragmentB",fragmentB); } super.onSaveInstanceState(outState); } /** Fragment中保存变量的代码 **/ @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { AppLog.e("onCreateView"); if ( null != savedInstanceState ){ String savedString = savedInstanceState.getString("string"); //得到保存下来的string } View root = inflater.inflate(R.layout.fragment_a,null); return root; } @Override public void onSaveInstanceState(Bundle outState) { outState.putString("string","anAngryAnt"); super.onSaveInstanceState(outState); } ``` ##静态的使用Fragment 1. 继承Fragment,重写onCreateView决定Fragment的布局 2. 在Activity中声明此Fragment,就和普通的View一样 ##Fragment常用的API * android.support.v4.app.Fragment 主要用于定义Fragment * android.support.v4.app.FragmentManager 主要用于在Activity中操作Fragment,可以使用FragmentManager.findFragmenById,FragmentManager.findFragmentByTag等方法去找到一个Fragment * android.support.v4.app.FragmentTransaction 保证一些列Fragment操作的原子性,熟悉事务这个词 * 主要的操作都是FragmentTransaction的方法 (一般我们为了向下兼容,都使用support.v4包里面的Fragment) getFragmentManager() // Fragment若使用的是support.v4包中的,那就使用getSupportFragmentManager代替 * 主要的操作都是FragmentTransaction的方法 ``` FragmentTransaction transaction = fm.benginTransatcion();//开启一个事务 transaction.add() //往Activity中添加一个Fragment transaction.remove() //从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈(回退栈后面会详细说),这个Fragment实例将会被销毁。 transaction.replace() //使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体~ transaction.hide() //隐藏当前的Fragment,仅仅是设为不可见,并不会销毁 transaction.show() //显示之前隐藏的Fragment detach() //当fragment被加入到回退栈的时候,该方法与*remove()*的作用是相同的, //反之,该方法只是将fragment从视图中移除, //之后仍然可以通过*attach()*方法重新使用fragment, //而调用了*remove()*方法之后, //不仅将Fragment从视图中移除,fragment还将不再可用。 attach() //重建view视图,附加到UI上并显示。 transatcion.commit() //提交一个事务 ``` ##管理Fragment回退栈 * 跟踪回退栈状态 * 我们通过实现*``OnBackStackChangedListener``*接口来实现回退栈状态跟踪,具体如下 ``` public class XXX implements FragmentManager.OnBackStackChangedListener /** 实现接口所要实现的方法 **/ @Override public void onBackStackChanged() { //do whatevery you want } /** 设置回退栈监听接口 **/ getSupportFragmentManager().addOnBackStackChangedListener(this); ``` * 管理回退栈 * ``FragmentTransaction.addToBackStack(String)`` *--将一个刚刚添加的Fragment加入到回退栈中* * ``getSupportFragmentManager().getBackStackEntryCount()`` *-获取回退栈中实体数量* * ``getSupportFragmentManager().popBackStack(String name, int flags)`` *-根据name立刻弹出栈顶的fragment* * ``getSupportFragmentManager().popBackStack(int id, int flags)`` *-根据id立刻弹出栈顶的fragment* ================================================ FILE: Part1/Android/Git操作.md ================================================ # Git 操作 ## git 命令 * 创建本地仓库 ``` git init ``` * 获取远程仓库 ``` git clone [url] 例:git clone https://github.com/you/yourpro.git ``` * 创建远程仓库 ``` // 添加一个新的 remote 远程仓库 git remote add [remote-name] [url] 例:git remote add origin https://github.com/you/yourpro.git origin:相当于该远程仓库的别名 // 列出所有 remote 的别名 git remote // 列出所有 remote 的 url git remote -v // 删除一个 renote git remote rm [name] // 重命名 remote git remote rename [old-name] [new-name] ``` * 从本地仓库中删除 ``` git rm file.txt // 从版本库中移除,删除文件 git rm file.txt -cached // 从版本库中移除,不删除原始文件 git rm -r xxx // 从版本库中删除指定文件夹 ``` * 从本地仓库中添加新的文件 ``` git add . // 添加所有文件 git add file.txt // 添加指定文件 ``` * 提交,把缓存内容提交到 HEAD 里 ``` git commit -m "注释" ``` * 撤销 ``` // 撤销最近的一个提交. git revert HEAD // 取消 commit + add git reset --mixed // 取消 commit git reset --soft // 取消 commit + add + local working git reset --hard ``` * 把本地提交 push 到远程服务器 ``` git push [remote-name] [loca-branch]:[remote-branch] 例:git push origin master:master ``` * 查看状态 ``` git status ``` * 从远程库中下载新的改动 ``` git fetch [remote-name]/[branch] ``` * 合并下载的改动到分支 ``` git merge [remote-name]/[branch] ``` * 从远程库中下载新的改动 ``` pull = fetch + merge git pull [remote-name] [branch] 例:git pull origin master ``` * 分支 ``` // 列出分支 git branch // 创建一个新的分支 git branch (branch-name) // 删除一个分支 git branch -d (branch-nam) // 删除 remote 的分支 git push (remote-name) :(remote-branch) ``` * 切换分支 ``` // 切换到一个分支 git checkout [branch-name] // 创建并切换到该分支 git checkout -b [branch-name] ``` ##与github建立ssh通信,让Git操作免去输入密码的繁琐。 * 首先呢,我们先建立ssh密匙。 > ssh key must begin with 'ssh-ed25519', 'ssh-rsa', 'ssh-dss', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', or 'ecdsa-sha2-nistp521'. -- from github 根据以上文段我们可以知道github所支持的ssh密匙类型,这里我们创建ssh-rsa密匙。 在command line 中输入以下指令:``ssh-keygen -t rsa``去创建一个ssh-rsa密匙。如果你并不需要为你的密匙创建密码和修改名字,那么就一路回车就OK,如果你需要,请您自行Google翻译,因为只是英文问题。 >$ ssh-keygen -t rsa Generating public/private rsa key pair. //您可以根据括号中的路径来判断你的.ssh文件放在了什么地方 Enter file in which to save the key (/c/Users/Liang Guan Quan/.ssh/id_rsa): * 到 https://github.com/settings/keys 这个地址中去添加一个新的SSH key,然后把你的xx.pub文件下的内容文本都复制到Key文本域中,然后就可以提交了。 * 添加完成之后 我们用``ssh git@github.com`` 命令来连通一下github,如果你在response里面看到了你github账号名,那么就说明配置成功了。 *let's enjoy github ;)* ## gitignore --- 在本地仓库根目录创建 .gitignore 文件。Win7 下不能直接创建,可以创建 ".gitignore." 文件,后面的标点自动被忽略; ``` /.idea // 过滤指定文件夹 /fd/* // 忽略根目录下的 /fd/ 目录的全部内容; *.iml // 过滤指定的所有文件 !.gitignore // 不忽略该文件 ``` ================================================ FILE: Part1/Android/Handler内存泄漏分析及解决.md ================================================ #Handler内存泄漏分析及解决 --- ###一、介绍 首先,请浏览下面这段handler代码: ``` public class SampleActivity extends Activity { private final Handler mLeakyHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } } } ``` 在使用handler时,这是一段很常见的代码。但是,它却会造成严重的内存泄漏问题。在实际编写中,我们往往会得到如下警告: ``` ⚠ In Android, Handler classes should be static or leaks might occur. ``` ###二、分析 1、 Android角度 当Android应用程序启动时,framework会为该应用程序的主线程创建一个Looper对象。这个Looper对象包含一个简单的消息队列Message Queue,并且能够循环的处理队列中的消息。这些消息包括大多数应用程序framework事件,例如Activity生命周期方法调用、button点击等,这些消息都会被添加到消息队列中并被逐个处理。 另外,主线程的Looper对象会伴随该应用程序的整个生命周期。 然后,当主线程里,实例化一个Handler对象后,它就会自动与主线程Looper的消息队列关联起来。所有发送到消息队列的消息Message都会拥有一个对Handler的引用,所以当Looper来处理消息时,会据此回调[Handler#handleMessage(Message)]方法来处理消息。 2、 Java角度 在java里,非静态内部类 和 匿名类 都会潜在的引用它们所属的外部类。但是,静态内部类却不会。 ###三、泄漏来源 请浏览下面一段代码: ``` public class SampleActivity extends Activity { private final Handler mLeakyHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Post a message and delay its execution for 10 minutes. mLeakyHandler.postDelayed(new Runnable() { @Override public void run() { /* ... */ } }, 1000 * 60 * 10); // Go back to the previous Activity. finish(); } } ``` 当activity结束(finish)时,里面的延时消息在得到处理前,会一直保存在主线程的消息队列里持续10分钟。而且,由上文可知,这条消息持有对handler的引用,而handler又持有对其外部类(在这里,即SampleActivity)的潜在引用。这条引用关系会一直保持直到消息得到处理,从而,这阻止了SampleActivity被垃圾回收器回收,同时造成应用程序的泄漏。 <<<<<<< HEAD 注意,上面代码中的Runnable类--非静态匿名类--同样持有对其外部类的引用。从而也导致泄漏。 ======= 注意,上面代码中的Runnable类--非静态匿名类--同样持有对其外部类的引用。从而也导致泄漏。 >>>>>>> c67abfcfd66909095068cb5f0c8632dc5547131b ###四、泄漏解决方案 首先,上面已经明确了内存泄漏来源: 只要有未处理的消息,那么消息会引用handler,非静态的handler又会引用外部类,即Activity,导致Activity无法被回收,造成泄漏; Runnable类属于非静态匿名类,同样会引用外部类。 为了解决遇到的问题,我们要明确一点:静态内部类不会持有对外部类的引用。所以,我们可以把handler类放在单独的类文件中,或者使用静态内部类便可以避免泄漏。 另外,如果想要在handler内部去调用所在的外部类Activity,那么可以在handler内部使用弱引用的方式指向所在Activity,这样统一不会导致内存泄漏。 对于匿名类Runnable,同样可以将其设置为静态类。因为静态的匿名类不会持有对外部类的引用。 ``` public class SampleActivity extends Activity { /** * Instances of static inner classes do not hold an implicit * reference to their outer class. */ private static class MyHandler extends Handler { private final WeakReference mActivity; public MyHandler(SampleActivity activity) { mActivity = new WeakReference(activity); } @Override public void handleMessage(Message msg) { SampleActivity activity = mActivity.get(); if (activity != null) { // ... } } } private final MyHandler mHandler = new MyHandler(this); /** * Instances of anonymous classes do not hold an implicit * reference to their outer class when they are "static". */ private static final Runnable sRunnable = new Runnable() { @Override public void run() { /* ... */ } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Post a message and delay its execution for 10 minutes. mHandler.postDelayed(sRunnable, 1000 * 60 * 10); // Go back to the previous Activity. finish(); } } ``` ###五、小结 <<<<<<< HEAD 虽然静态类与非静态类之间的区别并不大,但是对于Android开发者而言却是必须理解的。至少我们要清楚,如果一个内部类实例的生命周期比Activity更长,那么我们千万不要使用非静态的内部类。最好的做法是,使用静态内部类,然后在该类里使用弱引用来指向所在的Activity。 原文链接: [http://www.jianshu.com/p/cb9b4b71a820](http://www.jianshu.com/p/cb9b4b71a820) ======= 虽然静态类与非静态类之间的区别并不大,但是对于Android开发者而言却是必须理解的。至少我们要清楚,如果一个内部类实例的生命周期比Activity更长,那么我们千万不要使用非静态的内部类。最好的做法是,使用静态内部类,然后在该类里使用弱引用来指向所在的Activity。 原文链接: [http://www.jianshu.com/p/cb9b4b71a820](http://www.jianshu.com/p/cb9b4b71a820) >>>>>>> c67abfcfd66909095068cb5f0c8632dc5547131b ================================================ FILE: Part1/Android/Listview详解.md ================================================ #ListView详解 --- 直接继承自AbsListView,AbsListView继承自AdapterView,AdapterView又继承自ViewGroup。 Adpater在ListView和数据源之间起到了一个桥梁的作用 ###RecycleBin机制 RecycleBin机制是ListView能够实现成百上千条数据都不会OOM最重要的一个原因。RecycleBin是AbsListView的一个内部类。 * RecycleBin当中使用mActiveViews这个数组来存储View,调用这个方法后就会根据传入的参数来将ListView中的指定元素存储到mActiveViews中。 * mActiveViews当中所存储的View,一旦被获取了之后就会从mActiveViews当中移除,下次获取同样位置的时候将会返回null,所以mActiveViews不能被重复利用。 * addScrapView()用于将一个废弃的View进行缓存,该方法接收一个View参数,当有某个View确定要废弃掉的时候(比如滚动出了屏幕)就应该调用这个方法来对View进行缓存,RecycleBin当中使用mScrapV * iews和mCurrentScrap这两个List来存储废弃View。 * getScrapView 用于从废弃缓存中取出一个View,这些废弃缓存中的View是没有顺序可言的,因此getScrapView()方法中的算法也非常简单,就是直接从mCurrentScrap当中获取尾部的一个scrap view进行返回。 * 我们都知道Adapter当中可以重写一个getViewTypeCount()来表示ListView中有几种类型的数据项,而setViewTypeCount()方法的作用就是为每种类型的数据项都单独启用一个RecycleBin缓存机制。 View的流程分三步,onMeasure()用于测量View的大小,onLayout()用于确定View的布局,onDraw()用于将View绘制到界面上。 ================================================ FILE: Part1/Android/MVC,MVP,MVVM的区别.md ================================================ #MVC,MVP,MVVM的区别 --- #MVC 软件可以分为三部分 * 视图(View):用户界面 * 控制器(Controller):业务逻辑 * 模型(Model):数据保存 各部分之间的通信方式如下: 1. View传送指令到Controller 2. Controller完成业务逻辑后,要求Model改变状态 3. Model将新的数据发送到View,用户得到反馈 Tips:所有的通信都是单向的。 #互动模式 接受用户指令时,MVC可以分为两种方式。一种是通过View接受指令,传递给Controller。 另一种是直接通过Controller接受指令 #MVP MVP模式将Controller改名为Presenter,同时改变了通信方向。 1. 各部分之间的通信,都是双向的 2. View和Model不发生联系,都通过Presenter传递 3. View非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而Presenter非常厚,所有逻辑都部署在那里。 #MVVM MVVM模式将Presenter改名为ViewModel,基本上与MVP模式完全一致。 唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在ViewModel,反之亦然。 ================================================ FILE: Part1/Android/MVP.md ================================================ #MVP --- ###为什么需要MVP 1. 尽量简单 大部分的安卓应用只使用View-Model结构,程序员现在更多的是和复杂的View打交道而不是解决业务逻辑。当你在应用中只使用Model-View时,到最后,你会发现“所有的事物都被连接到一起”。复杂的任务被分成细小的任务,并且很容易解决。越小的东西,bug越少,越容易debug,更好测试。在MVP模式下的View层将会变得简单,所以即便是他请求数据的时候也不需要回调函数。View逻辑变成十分直接。 2. 后台任务 当你编写一个Actviity、Fragment、自定义View的时候,你会把所有的和后台任务相关的方法写在一个静态类或者外部类中。这样,你的Task不再和Activity联系在一起,这既不会导致内存泄露,也不依赖于Activity的重建。 ###优缺点 优点: 1. 降低耦合度,实现了Model和View真正的完全分离,可以修改View而不影响Modle 2. 模块职责划分明显,层次清晰 3. 隐藏数据 4. Presenter可以复用,一个Presenter可以用于多个View,而不需要更改Presenter的逻辑(当然是在View的改动不影响业务逻辑的前提下) 5. 利于测试驱动开发。以前的Android开发是难以进行单元测试的(虽然很多Android开发者都没有写过测试用例,但是随着项目变得越来越复杂,没有测试是很难保证软件质量的;而且近几年来Android上的测试框架已经有了长足的发展——开始写测试用例吧),在使用MVP的项目中Presenter对View是通过接口进行,在对Presenter进行不依赖UI环境的单元测试的时候。可以通过Mock一个View对象,这个对象只需要实现了View的接口即可。然后依赖注入到Presenter中,单元测试的时候就可以完整的测试Presenter应用逻辑的正确性。 6. View可以进行组件化。在MVP当中,View不依赖Model。这样就可以让View从特定的业务场景中脱离出来,可以说View可以做到对业务完全无知。它只需要提供一系列接口提供给上层操作。这样就可以做到高度可复用的View组件。 7. 代码灵活性 缺点: 1. Presenter中除了应用逻辑以外,还有大量的View->Model,Model->View的手动同步逻辑,造成Presenter比较笨重,维护起来会比较困难。 2. 由于对视图的渲染放在了Presenter中,所以视图和Presenter的交互会过于频繁。 3. 如果Presenter过多地渲染了视图,往往会使得它与特定的视图的联系过于紧密。一旦视图需要变更,那么Presenter也需要变更了。 4. 额外的代码复杂度及学习成本。 **在MVP模式里通常包含4个要素:** 1. View :负责绘制UI元素、与用户进行交互(在Android中体现为Activity); 2. View interface :需要View实现的接口,View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试; 3. Model :负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合); 4. Presenter :作为View与Model交互的中间纽带,处理与用户交互的负责逻辑。 ================================================ FILE: Part1/Android/Recyclerview和Listview的异同.md ================================================ #RecyclerView和ListView的异同 --- * ViewHolder是用来保存视图引用的类,无论是ListView亦或是RecyclerView。只不过在ListView中,ViewHolder需要自己来定义,且这只是一种推荐的使用方式,不使用当然也可以,这不是必须的。只不过不使用ViewHolder的话,ListView每次getView的时候都会调用findViewById(int),这将导致ListView性能展示迟缓。而在RecyclerView中使用RecyclerView.ViewHolder则变成了必须,尽管实现起来稍显复杂,但它却解决了ListView面临的上述不使用自定义ViewHolder时所面临的问题。 * 我们知道ListView只能在垂直方向上滚动,Android API没有提供ListView在水平方向上面滚动的支持。或许有多种方式实现水平滑动,但是请想念我,ListView并不是设计来做这件事情的。但是RecyclerView相较于ListView,在滚动上面的功能扩展了许多。它可以支持多种类型列表的展示要求,主要如下: 1. LinearLayoutManager,可以支持水平和竖直方向上滚动的列表。 2. StaggeredGridLayoutManager,可以支持交叉网格风格的列表,类似于瀑布流或者Pinterest。 3. GridLayoutManager,支持网格展示,可以水平或者竖直滚动,如展示图片的画廊。 * 列表动画是一个全新的、拥有无限可能的维度。起初的Android API中,删除或添加item时,item是无法产生动画效果的。后面随着Android的进化,Google的Chat Hasse推荐使用ViewPropertyAnimator属性动画来实现上述需求。 相比较于ListView,RecyclerView.ItemAnimator则被提供用于在RecyclerView添加、删除或移动item时处理动画效果。同时,如果你比较懒,不想自定义ItemAnimator,你还可以使用DefaultItemAnimator。 * ListView的Adapter中,getView是最重要的方法,它将视图跟position绑定起来,是所有神奇的事情发生的地方。同时我们也能够通过registerDataObserver在Adapter中注册一个观察者。RecyclerView也有这个特性,RecyclerView.AdapterDataObserver就是这个观察者。ListView有三个Adapter的默认实现,分别是ArrayAdapter、CursorAdapter和SimpleCursorAdapter。然而,RecyclerView的Adapter则拥有除了内置的内DB游标和ArrayList的支持之外的所有功能。RecyclerView.Adapter的实现的,我们必须采取措施将数据提供给Adapter,正如BaseAdapter对ListView所做的那样。 * 在ListView中如果我们想要在item之间添加间隔符,我们只需要在布局文件中对ListView添加如下属性即可: ``` android:divider="@android:color/transparent" android:dividerHeight="5dp" ``` * ListView通过AdapterView.OnItemClickListener接口来探测点击事件。而RecyclerView则通过RecyclerView.OnItemTouchListener接口来探测触摸事件。它虽然增加了实现的难度,但是却给予开发人员拦截触摸事件更多的控制权限。 * ListView可以设置选择模式,并添加MultiChoiceModeListener,如下所示: ``` listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); listView.setMultiChoiceModeListener(new MultiChoiceModeListener() { public boolean onCreateActionMode(ActionMode mode, Menu menu) { ... } public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { ... } public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { case R.id.menu_item_delete_crime: CrimeAdapter adapter = (CrimeAdapter)getListAdapter(); CrimeLab crimeLab = CrimeLab.get(getActivity()); for (int i = adapter.getCount() - 1; i >= 0; i--) { if (getListView().isItemChecked(i)) { crimeLab.deleteCrime(adapter.getItem(i)); } } mode.finish(); adapter.notifyDataSetChanged(); return true; default: return false; } public boolean onPrepareActionMode(ActionMode mode, Menu menu) { ... } public void onDestroyActionMode(ActionMode mode) { ... } }); ``` 而RecyclerView则没有此功能。 [http://www.cnblogs.com/littlepanpc/p/4497290.html](http://www.cnblogs.com/littlepanpc/p/4497290.html) ================================================ FILE: Part1/Android/SurfaceView.md ================================================ ##为什么要使用SurfaceView来实现动画? ###因为View的绘图存在以下缺陷: 1. View缺乏双缓冲机制 2. 当程序需要更新View上的图像时,程序必须重绘View上显示的整张图片 3. 新线程无法直接更新View组件 ##SurfaceView的绘图机制 * 一般会与SurfaceView结合使用 * 调用SurfaceView的getHolder()方法即可获得SurfaceView关联的SurfaceHolder ##SurfaceHolder提供了如下方法来获取Canvas对象 1. Canvas lockCanvas():锁定整个SurfaceView对象,获取该Surface上的Canvas 2. Canvas lockCanvas(Rect dirty):锁定SurfaceView上Rect划分的区域,获取该Surface上的Canvas 3. unlockCanvasAndPost(canvas):释放绘图、提交所绘制的图形,需要注意,当调用SurfaceHolder上的unlockCanvasAndPost方法之后,该方法之前所绘制的图形还处于缓冲之中,下一次lockCanvas()方法锁定的区域可能会“遮挡”它 ``` public class SurfaceViewTest extends Activity { // SurfaceHolder负责维护SurfaceView上绘制的内容 private SurfaceHolder holder; private Paint paint; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); paint = new Paint(); SurfaceView surface = (SurfaceView) findViewById(R.id.show); // 初始化SurfaceHolder对象 holder = surface.getHolder(); holder.addCallback(new Callback() { @Override public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { } @Override public void surfaceCreated(SurfaceHolder holder) { // 锁定整个SurfaceView Canvas canvas = holder.lockCanvas(); // 绘制背景 Bitmap back = BitmapFactory.decodeResource( SurfaceViewTest.this.getResources() , R.drawable.sun); // 绘制背景 canvas.drawBitmap(back, 0, 0, null); // 绘制完成,释放画布,提交修改 holder.unlockCanvasAndPost(canvas); // 重新锁一次,"持久化"上次所绘制的内容 holder.lockCanvas(new Rect(0, 0, 0, 0)); holder.unlockCanvasAndPost(canvas); } @Override public void surfaceDestroyed(SurfaceHolder holder) { } }); // 为surface的触摸事件绑定监听器 surface.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View source, MotionEvent event) { // 只处理按下事件 if (event.getAction() == MotionEvent.ACTION_DOWN) { int cx = (int) event.getX(); int cy = (int) event.getY(); // 锁定SurfaceView的局部区域,只更新局部内容 Canvas canvas = holder.lockCanvas(new Rect(cx - 50, cy - 50, cx + 50, cy + 50)); // 保存canvas的当前状态 canvas.save(); // 旋转画布 canvas.rotate(30, cx, cy); paint.setColor(Color.RED); // 绘制红色方块 canvas.drawRect(cx - 40, cy - 40, cx, cy, paint); // 恢复Canvas之前的保存状态 canvas.restore(); paint.setColor(Color.GREEN); // 绘制绿色方块 canvas.drawRect(cx, cy, cx + 40, cy + 40, paint); // 绘制完成,释放画布,提交修改 holder.unlockCanvasAndPost(canvas); } return false; } }); } } ``` 上面的程序为SurfaceHolder添加了一个CallBack实例,该Callback中定义了如下三个方法: * void surfaceChanged(SurfaceHolder holder, int format, int width, int height):当一个surface的格式或大小发生改变时回调该方法。 * void surfaceCreated(SurfaceHolder holder):当surface被创建时回调该方法 * void surfaceDestroyed(SurfaceHolder holder):当surface将要被销毁时回调该方法 ================================================ FILE: Part1/Android/Zygote和System进程的启动过程.md ================================================ #Zygote和System进程的启动过程 --- ##init脚本的启动 --- ``` +------------+ +-------+ +-----------+ |Linux Kernel+--> |init.rc+-> |app_process| +------------+ +-------+ +-----------+ create and public server socket ``` linux内核加载完成后,运行init.rc脚本 ``` service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server socket zygote stream 666 ``` * /system/bin/app_process Zygote服务启动的进程名 * --start-system-server 表明Zygote启动完成之后,要启动System进程。 * socket zygote stream 666 在Zygote启动时,创建一个权限为666的socket。此socket用来请求Zygote创建新进程。socket的fd保存在名称为“ANDROID_SOCKET_zygote”的环境变量中。 ##Zygote进程的启动过程 --- ``` create rumtime +-----------+ +----------+ |app_process+----------> |ZygoteInit| +-----------+ +-----+----+ | | | registerZygoteSocket() | +------+ startSystemServer() | |System| <-------+ | +------+ fork | runSelectLoopMode() | v ``` **app_process进程** --- /system/bin/app_process 启动时创建了一个AppRuntime对象。通过AppRuntime对象的start方法,通过JNI调用创建了一个虚拟机实例,然后运行com.android.internal.os.ZygoteInit类的静态main方法,传递true(boolean startSystemServer)参数。 **ZygoteInit类** --- ZygoteInit类的main方法运行时,会通过registerZygoteSocket方法创建一个供ActivityManagerService使用的server socket。然后通过调用startSystemServer方法来启动System进程。最后通过runSelectLoopMode来等待AMS的新建进程请求。 1. 在registerZygoteSocket方法中,通过名为ANDROID_SOCKET_zygote的环境获取到zygote启动时创建的socket的fd,然后以此来创建server socket。 2. 在startSystemServer方法中,通过Zygote.forkSystemServer方法创建了一个子进程,并将其用户和用户组的ID设置为1000。 3. 在runSelectLoopMode方法中,会将之前建立的server socket保存起来。然后进入一个无限循环,在其中通过selectReadable方法,监听socket是否有数据可读。有数据则说明接收到了一个请求。 selectReadable方法会返回一个整数值index。如果index为0,则说明这个是AMS发过来的连接请求。这时会与AMS建立一个新的socket连接,并包装成ZygoteConnection对象保存起来。如果index大于0,则说明这是AMS发过来的一个创建新进程的请求。此时会取出之前保存的ZygoteConnection对象,调用其中的runOnce方法创建新进程。调用完成后将connection删除。 这就是Zygote处理一次AMS请求的过程。 ##System进程的启动 --- ``` + | | v fork() +--------------+ |System Process| +------+-------+ | | RuntimeInit.zygoteInit() commonInit, zygoteInitNative | init1() SurfaceFlinger, SensorServic... | | | init2() +------------+ +-------> |ServerThread| | +----+-------+ | | | | AMS, PMS, WMS... | | | | | | v v ``` System进程是在ZygoteInit的handleSystemServerProcess中开始启动的。 1. 首先,因为System进程是直接fork Zygote进程的,所以要先通过closeServerSocket方法关掉server socket。 2. 调用RuntimeInit.zygoteInit方法进一步启动System进程。在zygoteInit中,通过commonInit方法设置时区和键盘布局等通用信息,然后通过zygoteInitNative方法启动了一个Binder线程池。最后通过invokeStaticMain方法调用SystemServer类的静态Main方法。 3. SystemServer类的main通过JNI调用cpp实现的init1方法。在init1方法中,会启动各种以C++开发的系统服务(例如SurfaceFlinger和SensorService)。然后回调ServerServer类的init2方法来启动以Java开发的系统服务。 4. 在init2方法中,首先会新建名为"android.server.ServerThread"的ServerThread线程,并调用其start方法。然后在该线程中启动各种Service(例如AMS,PMS,WMS等)。启动的方式是调用对应Service类的静态main方法。 5. 首先,AMS会被创建,但未注册到ServerManager中。然后PMS被创建,AMS这时候才注册到ServerManager中。然后到ContentService、WMS等。 注册到ServerManager中时会制定Service的名字,其后其他进程可以通过这个名字来获取到Binder Proxy对象,以访问Service提供的服务。 6. 执行到这里,System就将系统的关键服务启动起来了,这时候其他进程便可利用这些Service提供的基础服务了。 7. 最后会调用ActivityManagerService的systemReady方法,在该方法里会启动系统界面以及Home程序。 ##Android进程启动 --- ``` +----------------------+ +-------+ +----------+ +----------------+ +-----------+ |ActivityManagerService| |Process| |ZygoteInit| |ZygoteConnection| |RuntimeInit| +--------------+-------+ +---+---+ +-----+----+ +-----------+----+ +------+----+ | | | | | | | | | | startProcessLocked() | | | | +---------------> | | | | | | start() | | | | | "android.app.ActivityThread" | | | +-----------------> | | | | | | | | | | | | | | | |openZygoteSocketIfNeeded() | | | +------+ | | | | | | | | | | | <----+ | | | | | | | | | |sZygoteWriter.write(arg) | | | +------+ | | | | | | | | | | | | | | | | | <----+ | | | | | | | | | +--------------> | | | | | | | | | | |runSelectLoopMode() | | | | +-----------------+ | | | | | | | | | | | <---------------+ | | | | | acceptCommandPeer() | | | | | | | | | | | | | | runOnce() | | | | +------------------> | | | | | |forkAndSpecialize() | | | +-------------+ | | | | | | | | | | | <-----------+ | | | | | handleChildProc() | | | | | | | | | | | | | | | | | | | zygoteInit() | | | | +-------------> | | | | | | | | | | |in^okeStaticMain() | | | | +----------------> | | | | |("android.app.ActivityThread") | | | | | | | | | | + + + + + ``` * AMS向Zygote发起请求(通过之前保存的socket),携带各种参数,包括“android.app.ActivityThread”。 * Zygote进程fork自己,然后在新Zygote进程中调用RuntimeInit.zygoteInit方法进行一系列的初始化(commonInit、Binder线程池初始化等)。 * 新Zygote进程中调用ActivityThread的main函数,并启动消息循环。 ================================================ FILE: Part1/Android/事件分发机制.md ================================================ # 事件分发机制 --- * 对于一个根ViewGroup来说,发生点击事件首先调用dispatchTouchEvent * 如果这个ViewGroup的onIterceptTouchEvent返回true就表示它要拦截当前事件,接着这个ViewGroup的onTouchEvent就会被调用.如果onIterceptTouchEvent返回false,那么就会继续向下调用子View的dispatchTouchEvent方法 * 当一个View需要处理事件的时候,如果它没有设置onTouchListener,那么直接调用onTouchEvent.如果设置了Listenter 那么就要看Listener的onTouch方法返回值.为true就不调,为false就调onTouchEvent * View的默认实现会在onTouchEvent里面把touch事件解析成Click之类的事件 * 点击事件传递顺序 Activity -> Window -> View * 一旦一个元素拦截了某事件,那么一个事件序列里面后续的Move,Down事件都会交给它处理.并且它的onInterceptTouchEvent不会再调用 * View的onTouchEvent默认都会消耗事件,除非它的clickable和longClickable都是false(不可点击),但是enable属性不会影响 ================================================ FILE: Part1/Android/开源框架源码分析.md ================================================ #框架源码分析 --- ###Retrofit --- ###EventBus --- ###Glide --- ================================================ FILE: Part1/Android/插件化技术学习.md ================================================ ###Android动态加载dex技术初探 [http://blog.csdn.net/u013478336/article/details/50734108](http://blog.csdn.net/u013478336/article/details/50734108) Android使用Dalvik虚拟机加载可执行程序,所以不能直接加载基于class的jar,而是需要将class转化为dex字节码。 Android支持动态加载的两种方式是:DexClassLoader和PathClassLoader,DexClassLoader可加载jar/apk/dex,且支持从SD卡加载;PathClassLoader据说只能加载已经安装在Android系统内APK文件。 ###Android插件化基础 Android简单来说就是如下操作: * 开发者将插件代码封装成Jar或者APK * 宿主下载或者从本地加载Jar或者APK到宿主中 * 将宿主调用插件中的算法或者Android特定的Class(如Activity) ###插件化开发—动态加载技术加载已安装和未安装的apk [http://blog.csdn.net/u010687392/article/details/47121729?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io](http://blog.csdn.net/u010687392/article/details/47121729?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io) 为什么引入动态加载技术? * 一个应用程序dex文件的方法数最大不能超过65536个 * 可以让应用程序实现插件化、插拔式结构,对后期维护有益 什么是动态加载技术 动态加载技术就是使用类加载器加载相应的apk、dex、jar(必须含有dex文件),再通过反射获得该apk、dex、jar内部的资源(class、图片、color等等)进而供宿主app使用。 关于动态加载使用的类加载器 * PathClassLoader - 只能加载已经安装的apk,即/data/app目录下的apk。 * DexClassLoader - 能加载手机中未安装的apk、jar、dex,只要能在找到对应的路径。 #插件化技术学习 --- 原因: 各大厂商都碰到了AndroidNative平台的瓶颈: 1. 从技术上讲,业务逻辑的复杂代码急剧膨胀,各大厂商陆续触到65535方法数的天花板;同时,对模块热更新提出了更高的要求。 2. 在业务层面上,功能模块的解耦以及维护团队的分离也是大势所趋。 插件化技术主要解决两个问题: 1. 代码加载 2. 资源加载 ###代码加载 类的加载可以使用Java的ClassLoader机制,还需要组件生命周期管理。 ###资源加载 用AssetManager的隐藏方法addAssetPath。 ##Android插件化原理解析——Hook机制之动态代理 使用代理机制进行API Hook进而达到方法增强。 静态代理 动态代理:可以简单理解为JVM可以在运行时帮我们动态生成一系列的代理类。 ###代理Hook 如果我们自己创建代理对象,然后把原始对象替换为我们的代理对象,就可以在这个代理对象中为所欲为了;修改参数,替换返回值,称之为Hook。 整个Hook过程简要总结如下: 1. 寻找Hook点,原则是静态变量或者单例对象,尽量Hook public的对象和方法,非public不保证每个版本都一样,需要适配。 2. 选择合适的代理方式,如果是接口可以用动态代理;如果是类可以手动写代理也可以使用cglib。 3. 偷梁换柱-用代理对象替换原始对象 ##Android插件化原理解析——Hook机制之Binder Hook ================================================ FILE: Part1/Android/查漏补缺.md ================================================ #查漏补缺 --- 请分析一张400*500尺寸的PNG图片加载到程序中占用内存中的大小 [http://m.blog.csdn.net/article/details?id=7856519](http://m.blog.csdn.net/article/details?id=7856519) ================================================ FILE: Part1/Android/热修复技术.md ================================================ #热修复技术 --- APP提早发出去的包,如果出现客户端的问题,实在是干着急,覆水难收。因此线上修复方案迫在眉睫。 ###概述 基于Xposed中的思想,通过修改c层的Method实例描述,来实现更改与之对应的java方法的行为,从而达到修复的目的。 ###Xposed 诞生于XDA论坛,类似一个应用平台,不同的是其提供诸多系统级的应用。可实现许多神奇的功能。Xposed需要以越狱为前提,像是iOS中的cydia。 Xposed可以修改任何程序的任何java方法(需root),github上提供了XposedInstaller,是一个android app。提供很多framework层,应用层级的程序。开发者可以为其开发一些系统或应用方面的插件,自定义android系统,它甚至可以做动态权限管理(XposedMods)。 ###Android系统启动与应用启动 Zygote进程是Android手机系统启动后,常驻的一个名为‘受精卵’的进程。 * zygote的启动实现脚本在/init.rc文件中 * 启动过程中执行的二进制文件在/system/bin/app_process 任何应用程序启动时,会从zygote进程fork出一个新的进程。并装载一些必要的class,invoke一些初始化方法。这其中包括像: * ActivityThread * ServiceThread * ApplicationPackageManager 等应用启动中必要的类,触发必要的方法,比如:handleBindApplication,将此进程与对应的应用绑定的初始化方法;同时,会将zygote进程中的dalvik虚拟机实例复制一份,因此每个应用程序进程都有自己的dalvik虚拟机实例;会将已有Java运行时加载到进程中;会注册一些android核心类的jni方法到虚拟机中,支撑从c到java的启动过程。 ###Xposed做了手脚 Xposed在这个过程改写了app_process(源码在Xposed : a modified app_process binary),替换/system/bin/app_process这个二进制文件。然后做了两个事: 1. 通过Xposed的hook技术,在上述过程中,对上面提到的那些加载的类的方法hook。 2. 加载XposedBridge.jar 这时hook必要的方法是为了方便开发者为它开发插件,加载XposedBridge.jar是为动态hook提供了基础。在这个时候加载它意味着,所有的程序在启动时,都可以加载这个jar(因为上面提到的fork过程)。结合hook技术,从而达到了控制所有程序的所有方法。 为获得/system/bin/目录的读写权限,因而需要以root为前提。 ###Xposed的hook思想 那么Xposed是怎么hook java方法的呢?要从XposedBridge看起,重点在 XposedBridge.hookmethod(原方法的Member对象,含有新方法的XC_MethodHook对象);,这里会调到 ``` private native synchronized static void hookMethodNative(Member method, Class declaringClass, int slot, Object additionalInfo); ``` 这个native的方法,通过这个方法,可以让所hook的方法,转向native层的一个c方法。如何做到? ``` When a transmit from java to native occurs, dvm sets up a native stack. In dvmCallJNIMethod(), dvmPlatformInvoke is used to call the native method(signature in Method.insns). ``` 在jni这个中间世界里,类型数据由jni表来沟通java和c的世界;方法由c++指针结合DVM*系(如dvmSlotToMethod,dvmDecodeIndirectRef等方法)的api方法,操作虚拟机,从而实现java方法与c方法的世界。 那么hook的过程是这样:首先通过dexclassload来load所要hook的方法,分析类后,进c层,见代码XposedBridge_hookMethodNative方法,拿到要hook的Method类,然后通过dvmslotTomethod方法获取Method*指针, ``` Method* method = dvmSlotToMethod(declaredClass, slot); ``` declaredClass就是所hook方法所在的类,对应的jobject。slot是Method类中,描述此java对象在vm中的索引;那么通过这个方法,我们就获取了c层的Method指针,通过 ``` SET_METHOD_FLAG(method, ACC_NATIVE); ``` 将该方法标记为一个native方法,然后通过 ``` method->nativeFunc = &hookedMethodCallback; ``` 定向c层方法到hookedMethodCallback,这样当被hook的java方法执行时,就会调到c层的hookedMethodCallback方法。 通过meth->nativeFunc重定向MethodCallBridge到hookedMethodCallback这个方法上,控制这个c++指针是无视java的private的。 另外,在method结构体中有 ``` method->insns = (const u2*) hookInfo; ``` 用insns指向替换成为的方法,以便hookedMethodCallback可以获取真正期望执行的java方法。 现在所有被hook的方法,都指向了hookedMethodCallbackc方法中,然后在此方法中实现调用替换成为的java方法。 ###从Xposed提炼精髓 回顾Xposed,以root为必要条件,在app_process加载XposedBidge.jar,从而实现有hook所有应用的所有方法的能力;而后续动态hook应用内的方法,其实只是load了从zypote进程复制出来的运行时的这个XposedBidge.jar,然后hook而已。因此,若在一个应用范围内的hook,root不是必须的,只是单纯的加载hook的实现方法,即可修改本应用的方法。 业界内也不乏通过「修改BaseDexClassLoader中的pathList,来动态加载dex」方式实现热修复。后者纯java实现,但需要hack类的优化流程,将打CLASS_ISPREVERIFIED标签的类,去除此标签,以解决类与类引用不在一个dex中的异常问题。这会放弃dex optimize对启动运行速度的优化。原则上,这对于方法数没有大到需要multidex的应用,损失更明显。而前者不触犯原有的优化流程,只点杀需要hook的方法,更为纯粹、有效。 ================================================ FILE: Part1/Android/线程通信基础流程分析.md ================================================ > 老司机们都知道,Android的线程间通信就靠Handler、Looper、Message、MessageQueue这四个麻瓜兄弟了,那么,他们是怎么运作的呢?下面做一个基于主要源代码的大学生水平的分析。 [原文链接](http://anangryant.leanote.com/post/Handler%E3%80%81Looper%E3%80%81Message%E3%80%81MessageQueue%E5%88%86%E6%9E%90) ##Looper(先分析这个是因为能够引出四者的关系) 在Looper中,维持一个`Thread`对象以及`MessageQueue`,通过Looper的构造函数我们可以知道: ``` private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed);//传入的参数代表这个Queue是否能够被退出 mThread = Thread.currentThread(); } ``` `Looper`在构造函数里干了两件事情: 1. 将线程对象指向了创建`Looper`的线程 2. 创建了一个新的`MessageQueue` 分析完构造函数之后,接下来我们主要分析两个方法: 1. `looper.loop()` 2. `looper.prepare()` ###looper.loop()(在当前线程启动一个Message loop机制,此段代码将直接分析出Looper、Handler、Message、MessageQueue的关系) ``` public static void loop() { final Looper me = myLooper();//获得当前线程绑定的Looper if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue;//获得与Looper绑定的MessageQueue // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); //进入死循环,不断地去取对象,分发对象到Handler中消费 for (;;) { Message msg = queue.next(); // 不断的取下一个Message对象,在这里可能会造成堵塞。 if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } //在这里,开始分发Message了 //至于这个target是神马?什么时候被赋值的? //我们一会分析Handler的时候就会讲到 msg.target.dispatchMessage(msg); if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } //当分发完Message之后,当然要标记将该Message标记为 *正在使用* 啦 msg.recycleUnchecked(); } } ``` *分析了上面的源代码,我们可以意识到,最重要的方法是:* 1. `queue.next()` 2. `msg.target.dispatchMessage(msg)` 3. `msg.recycleUnchecked()` 其实Looper中最重要的部分都是由`Message`、`MessageQueue`组成的有木有!这段最重要的代码中涉及到了四个对象,他们与彼此的关系如下: 1. `MessageQueue`:装食物的容器 2. `Message`:被装的食物 3. `Handler`(msg.target实际上就是`Handler`):食物的消费者 4. `Looper`:负责分发食物的人 ###looper.prepare()(在当前线程关联一个Looper对象) ``` private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } //在当前线程绑定一个Looper sThreadLocal.set(new Looper(quitAllowed)); } ``` 以上代码只做了两件事情: 1. 判断当前线程有木有`Looper`,如果有则抛出异常(在这里我们就可以知道,Android规定一个线程只能够拥有一个与自己关联的`Looper`)。 2. 如果没有的话,那么就设置一个新的`Looper`到当前线程。 -------------- ##Handler 由于我们使用Handler的通常性的第一步是: ``` Handler handler = new Handler(){ //你们有没有很好奇这个方法是在哪里被回调的? //我也是!所以接下来会分析到哟! @Override public void handleMessage(Message msg) { //Handler your Message } }; ``` 所以我们先来分析`Handler`的构造方法 ``` //空参数的构造方法与之对应,这里只给出主要的代码,具体大家可以到源码中查看 public Handler(Callback callback, boolean async) { //打印内存泄露提醒log .... //获取与创建Handler线程绑定的Looper mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } //获取与Looper绑定的MessageQueue //因为一个Looper就只有一个MessageQueue,也就是与当前线程绑定的MessageQueue mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } ``` *带上问题:* 1. `Looper.loop()`死循环中的`msg.target`是什么时候被赋值的? 2. `handler.handleMessage(msg)`在什么时候被回调的? ###A1:`Looper.loop()`死循环中的`msg.target`是什么时候被赋值的? 要分析这个问题,很自然的我们想到从发送消息开始,无论是`handler.sendMessage(msg)`还是`handler.sendEmptyMessage(what)`,我们最终都可以追溯到以下方法 ``` public boolean sendMessageAtTime(Message msg, long uptimeMillis) { //引用Handler中的MessageQueue //这个MessageQueue就是创建Looper时被创建的MessageQueue MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } //将新来的Message加入到MessageQueue中 return enqueueMessage(queue, msg, uptimeMillis); } ``` 我们接下来分析`enqueueMessage(queue, msg, uptimeMillis)`: ``` private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { //显而易见,大写加粗的赋值啊! **msg.target = this;** if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); } ``` ###A2:`handler.handleMessage(msg)`在什么时候被回调的? 通过以上的分析,我们很明确的知道`Message`中的`target`是在什么时候被赋值的了,我们先来分析在`Looper.loop()`中出现过的过的`dispatchMessage(msg)`方法 ``` public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } //看到这个大写加粗的方法调用没! **handleMessage(msg);** } } ``` 加上以上分析,我们将之前分析结果串起来,就可以知道了某些东西: `Looper.loop()`不断地获取`MessageQueue`中的`Message`,然后调用与`Message`绑定的`Handler`对象的`dispatchMessage`方法,最后,我们看到了`handleMessage`就在`dispatchMessage`方法里被调用的。 ------------------ 通过以上的分析,我们可以很清晰的知道Handler、Looper、Message、MessageQueue这四者的关系以及如何合作的了。 #总结: 当我们调用`handler.sendMessage(msg)`方法发送一个`Message`时,实际上这个`Message`是发送到**与当前线程绑定**的一个`MessageQueue`中,然后**与当前线程绑定**的`Looper`将会不断的从`MessageQueue`中取出新的`Message`,调用`msg.target.dispathMessage(msg)`方法将消息分发到与`Message`绑定的`handler.handleMessage()`方法中。 一个`Thread`对应多个`Handler` 一个`Thread`对应一个`Looper`和`MessageQueue`,`Handler`与`Thread`共享`Looper`和`MessageQueue`。 `Message`只是消息的载体,将会被发送到**与线程绑定的唯一的**`MessageQueue`中,并且被**与线程绑定的唯一的**`Looper`分发,被与其自身绑定的`Handler`消费。 ------ - Enjoy Android :) 如果有误,轻喷,欢迎指正。 ================================================ FILE: Part1/Android/自定义控件.md ================================================ #自定义控件 --- 自定义View的步骤: - 自定义View的属性 - 在View的构造方法中获得我们自定义View的步骤 - [3.重写onMeasure](不必须) - 重写onDraw ================================================ FILE: Part1/DesignPattern/Builder模式.md ================================================ #Builder模式 --- ##模式介绍 --- ###模式的定义 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 ###模式的使用场景 1. 相同的方法,不同的执行顺序,产生不同的事件结果时; 2. 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时; 3. 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式非常合适; ###Android源码中的模式实现 在Android源码中,我们最常用到的Builder模式就是AlertDialog.Builder, 使用该Builder来构建复杂的AlertDialog对象。简单示例如下 : ``` //显示基本的AlertDialog private void showDialog(Context context) { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setIcon(R.drawable.icon); builder.setTitle("Title"); builder.setMessage("Message"); builder.setPositiveButton("Button1", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { setTitle("点击了对话框上的Button1"); } }); builder.setNeutralButton("Button2", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { setTitle("点击了对话框上的Button2"); } }); builder.setNegativeButton("Button3", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { setTitle("点击了对话框上的Button3"); } }); builder.create().show(); // 构建AlertDialog, 并且显示 } ``` ##优点与缺点 --- ###优点 * 良好的封装性, 使用建造者模式可以使客户端不必知道产品内部组成的细节; * 建造者独立,容易扩展; * 在对象创建过程中会使用到系统中的一些其它对象,这些对象在产品对象的创建过程中不易得到。 ###缺点 * 会产生多余的Builder对象以及Director对象,消耗内存; * 对象的构建过程暴露。 ================================================ FILE: Part1/DesignPattern/代理模式.md ================================================ #代理模式 --- ##模式介绍 代理模式是对象的结构模式。代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。 ##模式的使用场景 就是一个人或者机构代表另一个人或者机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。 ##角色介绍 * 抽象对象角色:声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象。 * 目标对象角色:定义了代理对象所代表的目标对象。 * 代理对象角色:代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或之后,执行某个操作,而不是单纯地将调用传递给目标对象。 ##优点与缺点 --- 优点 给对象增加了本地化的扩展性,增加了存取操作控制 缺点 会产生多余的代理类 ================================================ FILE: Part1/DesignPattern/单例模式.md ================================================ #单例模式 --- ###定义 >保证一个类仅有一个实例,并提供一个访问它的全局访问点。 >Singleton:负责创建Singleton类自己的唯一实例,并提供一个getInstance的方法,让外部来访问这个类的唯一实例。 * 饿汉式: ``` private static Singleton uniqueInstance = new Singleton(); ``` * 懒汉式 ``` private static Singleton uniqueInstance = null; ``` ###功能 单例模式是用来保证这个类在运行期间只会被创建一个类实例,另外,单例模式还提供了一个全局唯一访问这个类实例的访问点,就是getInstance方法。 ###范围 Java里面实现的单例是一个虚拟机的范围。因为装载类的功能是虚拟机的,所以一个虚拟机在通过自己的ClassLoader装载饿汉式实现单例类的时候就会创建一个类的实例。 懒汉式单例有延迟加载和缓存的思想 ###优缺点 * 懒汉式是典型的时间换空间 * 饿汉式是典型的空间换时间 --- * 不加同步的懒汉式是线程不安全的。比如,有两个线程,一个是线程A,一个是线程B,它们同时调用getInstance方法,就可能导致并发问题。 * 饿汉式是线程安全的,因为虚拟机保证只会装载一次,在装载类的时候是不会发生并发的。 --- 如何实现懒汉式的线程安全? 加上synchronized即可 ``` public static synchronized Singleton getInstance(){} ``` 但这样会降低整个访问的速度,而且每次都要判断。可以用双重检查加锁。 双重加锁机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例。这是第二重检查。 双重加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。 ``` /** * 双重检查加锁的单例模式 * @author dream * */ public class Singleton { /** * 对保存实例的变量添加volitile的修饰 */ private volatile static Singleton instance = null; private Singleton(){ } public static Singleton getInstance(){ //先检查实例是否存在,如果不存在才进入下面的同步块 if(instance == null){ //同步块,线程安全的创建实例 synchronized (Singleton.class) { //再次检查实例是否存在,如果不存在才真正的创建实例 instance = new Singleton(); } } return instance; } } ``` ###一种更好的单例实现方式 ``` public class Singleton { /** * 类级的内部类,也就是静态类的成员式内部类,该内部类的实例与外部类的实例 * 没有绑定关系,而且只有被调用时才会装载,从而实现了延迟加载 * @author dream * */ private static class SingletonHolder{ /** * 静态初始化器,由JVM来保证线程安全 */ private static final Singleton instance = new Singleton(); } /** * 私有化构造方法 */ private Singleton(){ } public static Singleton getInstance(){ return SingletonHolder.instance; } } ``` 根据《高效Java第二版》中的说法,单元素的枚举类型已经成为实现Singleton的最佳方法。 ``` package example6; /** * 使用枚举来实现单例模式的示例 * @author dream * */ public class Singleton { /** * 定义一个枚举的元素,它就代表了Singleton的一个实例 */ uniqueInstance; /** * 示意方法,单例可以有自己的操作 */ public void singletonOperation(){ //功能树立 } } ``` --- ###本质 控制实例数量 ###何时选用单例模式 当需要控制一个类的实例只能有一个,而且客户只能从一个全局访问点访问它时,可以选用单例模式,这些功能恰好是单例模式要解决的问题。 ================================================ FILE: Part1/DesignPattern/原型模式.md ================================================ #原型模式 --- ##模式介绍 ###模式的定义 用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。 ### 模式的使用场景 1. 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗; 2. 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式; 3. 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。 ##Android源码中的模式实现 Intent中使用了原型模式 ``` Uri uri = Uri.parse("smsto:0800000123"); Intent shareIntent = new Intent(Intent.ACTION_SENDTO, uri); shareIntent.putExtra("sms_body", "The SMS text"); Intent intent = (Intent)shareIntent.clone() ; startActivity(intent); ``` ##优点与缺点 ###优点 原型模式是在内存二进制流的拷贝,要比直接 new 一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。 ###缺点 这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的,在实际开发当中应该注意这个潜在的问题。优点就是减少了约束,缺点也是减少了约束,需要大家在实际应用时考虑。 ================================================ FILE: Part1/DesignPattern/外观模式.md ================================================ #外观模式 --- ###定义 为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 ###外观模式的目的 不是给子系统添加新的功能接口,而是为了让外部减少与子系统内多个模块的交互,松散耦合,从而让外部能够更简单的使用子系统。 ###优缺点 1. 优点 * 松散耦合 * 简单易用 * 更好的划分访问的层次 2. 缺点 * 过多的或者是不太合理的Facade也容易让人迷惑。到底是调用Facade好还是直接调用模块好。 ###本质 封装交互,简化调用 ###何时选用外观模式 * 如果你希望为复杂的子系统提供一个简单接口的时候,可以考虑使用外观模式。使用外观对象对实现大部分客户需要的功能,从而简化客户的使用。 * 如果想要让客户程序和抽象类的实现部分松散耦合,可以考虑使用外观模式,使用外观对象来将这个子系统与它的客户分离开来,从而提高子系统的独立性和可移植性。 * 如果构建多层结构的系统,可以考虑使用外观模式,使用外观对象作为每层的入口,这样就可以简化层间调用,也可以松散层次之间的依赖关系。 ================================================ FILE: Part1/DesignPattern/常见的面向对象设计原则.md ================================================ #常见的面向对象设计原则 1. 单一职责原则 SRP 一个类应该仅有一个引起它变化的原因。 2. 开放关闭原则 OCP 一个类应该对外扩展开放,对修改关闭。 3. 里氏替换原则 LSP 子类型能够替换掉它们的父类型。 4. 依赖倒置原则 DIP 要依赖于抽象,不要依赖于具体类,要做到依赖倒置,应该做到: * 高层模块不应该依赖底层模块,二者都应该依赖于抽象。 * 抽象不应该依赖于具体实现,具体实现应该依赖于抽象。 5. 接口隔离原则 ISP 不应该强迫客户依赖于他们不用的方法。 6. 最少知识原则 LKP 只和你的朋友谈话。 7. 其他原则 * 面向接口编程 * 优先使用组合,而非继承 * 一个类需要的数据应该隐藏在类的内部 * 类之间应该零耦合,或者只有传导耦合,换句话说,类之间要么没关系,要么只使用另一个类的接口提供的操作 * 在水平方向上尽可能统一地分布系统功能 ================================================ FILE: Part1/DesignPattern/策略模式.md ================================================ #策略模式 --- ##模式的定义 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。 注:针对同一类型操作,将复杂多样的处理方式分别开来,有选择的实现各自特有的操作。 ##模式的使用场景 * 针对同一类型问题的多种处理方式,仅仅是具体行为有差别时。 * 需要安全的封装多种同一类型的操作时。 * 出现同一抽象多个子类,而又需要使用if-else 或者 switch-case来选择时。 ##Android源码中的模式实现 策略模式主要用来分离算法,根据相同的行为抽象来做不同的具体策略实现。 ##优缺点 ###优点: * 结构清晰明了、使用简单直观。 * 耦合度相对而言较低,扩展方便。 * 操作封装也更为彻底,数据更为安全。 ###缺点: * 随着策略的增加,子类也会变得繁多。 ================================================ FILE: Part1/DesignPattern/简单工厂.md ================================================ #简单工厂 --- ###接口 接口是一种特殊的抽象类,跟一般的抽象类相比,接口里的所有方法都是抽象方法,接口里的所有属性都是常量。也就是说接口里面只有方法定义没有任何方法实现。 接口的思想是"封装隔离" ###简单工厂 示例代码: [https://github.com/GeniusVJR/DesignMode_Java/tree/master/SimpleFactory](https://github.com/GeniusVJR/DesignMode_Java/tree/master/SimpleFactory) 客户端在调用的时候,不但知道了接口,同时还知道了具体的实现。接口的思想是"封装隔离",而实现类Impl应该是被接口Api封装并同客户端隔离开来的,客户端不应该知道具体的实现类是Impl。 ###简单工厂的功能 不仅可以利用简单工厂来创建接口,也可以用简单工厂来创造抽象类,甚至是一个具体的实例。 ###静态工厂 没有创建工厂实例的必要,把简单工厂实现成一个工具类,直接使用静态方法。 ###万能工厂 一个简单哪工厂可以包含很多用来构造东西的方法,这些方法可以创建不同的接口、抽象类或者是类实例。 ###简单工厂的优缺点 1. 优点 * 帮助封装 * 解耦 2. 缺点 * 可能增加客户端的复杂度 * 不方便扩展子工厂 ##思考 简单工厂的本质是选择实现。 ================================================ FILE: Part1/DesignPattern/观察者模式.md ================================================ #观察者模式 --- 首先在Android中,我们往ListView添加数据后,都会调用Adapter的notifyDataChanged()方法,其中使用了观察者模式。 当ListView的数据发生变化时,调用Adapter的notifyDataSetChanged函数,这个函数又会调用DataSetObservable的notifyChanged函数,这个函数会调用所有观察者(AdapterDataSetObserver)的onChanged方法,在onChanged函数中又会调用ListView重新布局的函数使得ListView刷新界面。 Android中应用程序发送广播的过程: - 通过sendBroadcast把一个广播通过Binder发送给ActivityManagerService,ActivityManagerService根据这个广播的Action类型找到相应的广播接收器,然后把这个广播放进自己的消息队列中,就完成第一阶段对这个广播的异步分发。 - ActivityManagerService在消息循环中处理这个广播,并通过Binder机制把这个广播分发给注册的ReceiverDispatcher,ReceiverDispatcher把这个广播放进MainActivity所在线程的消息队列中,就完成第二阶段对这个广播的异步分发: - ReceiverDispatcher的内部类Args在MainActivity所在的线程消息循环中处理这个广播,最终是将这个广播分发给所注册的BroadcastReceiver实例的onReceive函数进行处理: ================================================ FILE: Part1/DesignPattern/责任链模式.md ================================================ #责任链模式 --- ##模式介绍 ###模式的定义 一个请求沿着一条“链”传递,直到该“链”上的某个处理者处理它为止。 ###模式的使用场景 一个请求可以被多个处理者处理或处理者未明确指定时。 ================================================ FILE: Part1/DesignPattern/适配器模式.md ================================================ #适配器模式 --- 定义: >将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 功能: >进行转换匹配,目的是复用已有的功能,而不是来实现新的接口。在适配器里实现功能,这种适配器称为智能适配器。 优点: * 更好的复用性 * 更好的扩展性 缺点: * 过多的使用适配器,会让系统非常零乱,不容易整体进行把握。 本质: 转换匹配,复用功能。 何时选用适配器模式: * 如果你想要使用一个已经存在的类,但是它的接口不符合你的需求,这种情况可以使用适配器模式,来把已有的实现转换成你需要的接口。 * 如果你想创建一个可以复用的类,这个类可能和一些不兼容的类一起工作,这种情况可以使用适配器模式,到时候需要什么就适配什么。 * 如果你想使用一些已经存在的子类,但是不可能对每一个子类都进行适配,这种情况可以选用对象适配器,直接适配这些子类的父类就可以了。 ================================================ FILE: Part2/JVM/JVM.md ================================================ #JVM --- **内存模型以及分区,需要详细到每个区放什么。** [http://blog.csdn.net/ns_code/article/details/17565503](http://blog.csdn.net/ns_code/article/details/17565503) JVM所管理的内存分为以下几个运行时数据区:程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区。 ![](http://img.blog.csdn.net/20131226151744250) 程序计数器(Program Counter Register) 一块较小的内存空间,它是当前线程所执行的字节码的行号指示器,字节码解释器工作时通过改变该计数器的值来选择下一条需要执行的字节码指令,分支、跳转、循环等基础功能都要依赖它来实现。每条线程都有一个独立的的程序计数器,各线程间的计数器互不影响,因此该区域是线程私有的。 当线程在执行一个Java方法时,该计数器记录的是正在执行的虚拟机字节码指令的地址,当线程在执行的是Native方法(调用本地操作系统方法)时,该计数器的值为空。另外,该内存区域是唯一一个在Java虚拟机规范中么有规定任何OOM(内存溢出:OutOfMemoryError)情况的区域。 Java虚拟机栈(Java Virtual Machine Stacks) 该区域也是线程私有的,它的生命周期也与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧,栈它是用于支持续虚拟机进行方法调用和方法执行的数据结构。对于执行引擎来讲,活动线程中,只有栈顶的栈帧是有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法,执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。栈帧用于存储局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。在编译程序代码时,栈帧中需要多大的局部变量表、多深的操作数栈都已经完全确定了,并且写入了方法表的Code属性之中。因此,一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。 本地方法栈(Native Method Stacks) 该区域与虚拟机栈所发挥的作用非常相似,只是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为使用到的本地操作系统(Native)方法服务。 Java堆(Java Heap) Java Heap是Java虚拟机所管理的内存中最大的一块,它是所有线程共享的一块内存区域。几乎所有的对象实例和数组都在这类分配内存。Java Heap是垃圾收集器管理的主要区域,因此很多时候也被称为“GC堆”。 根据Java虚拟机规范的规定,Java堆可以处在物理上不连续的内存空间中,只要逻辑上是连续的即可。如果在堆中没有内存可分配时,并且堆也无法扩展时,将会抛出OutOfMemoryError异常。 方法区(Method Area) 方法区也是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区域又被称为“永久代”,但这仅仅对于Sun HotSpot来讲,JRockit和IBM J9虚拟机中并不存在永久代的概念。Java虚拟机规范把方法区描述为Java堆的一个逻辑部分,而且它和Java Heap一样不需要连续的内存,可以选择固定大小或可扩展,另外,虚拟机规范允许该区域可以选择不实现垃圾回收。相对而言,垃圾收集行为在这个区域比较少出现。该区域的内存回收目标主要针是对废弃常量的和无用类的回收。运行时常量池是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Class文件常量池),用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。运行时常量池相对于Class文件常量池的另一个重要特征是具备动态性,Java语言并不要求常量一定只能在编译期产生,也就是并非预置入Class文件中的常量池的内容才能进入方法区的运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的是String类的intern()方法。 根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。 **内存泄漏和内存溢出的差别** 内存泄露是指分配出去的内存没有被回收回来,由于失去了对该内存区域的控制,因而造成了资源的浪费。Java中一般不会产生内存泄露,因为有垃圾回收器自动回收垃圾,但这也不绝对,当我们new了对象,并保存了其引用,但是后面一直没用它,而垃圾回收器又不会去回收它,这边会造成内存泄露, 内存溢出是指程序所需要的内存超出了系统所能分配的内存(包括动态扩展)的上限。 **类型擦除** [http://blog.csdn.net/ns_code/article/details/18011009](http://blog.csdn.net/ns_code/article/details/18011009) Java语言在JDK1.5之后引入的泛型实际上只在程序源码中存在,在编译后的字节码文件中,就已经被替换为了原来的原生类型,并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说,`ArrayList`和`ArrayList`就是同一个类。所以泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型被称为伪泛型。 下面是一段简单的Java泛型代码: ``` Map map = new HashMap(); map.put(1,"No.1"); map.put(2,"No.2"); System.out.println(map.get(1)); System.out.println(map.get(2)); ```` 将这段Java代码编译成Class文件,然后再用字节码反编译工具进行反编译后,将会发现泛型都变回了原生类型,如下面的代码所示: ``` Map map = new HashMap(); map.put(1,"No.1"); map.put(2,"No.2"); System.out.println((String)map.get(1)); System.out.println((String)map.get(2)); ``` 为了更详细地说明类型擦除,再看如下代码: ``` import java.util.List; public class FanxingTest{ public void method(List list){ System.out.println("List String"); } public void method(List list){ System.out.println("List Int"); } } ``` 当我用Javac编译器编译这段代码时,报出了如下错误: ``` FanxingTest.java:3: 名称冲突:method(java.util.List) 和 method (java.util.List) 具有相同疑符 public void method(List list){ ^ FanxingTest.java:6: 名称冲突:method(java.util.List) 和 metho d(java.util.List) 具有相同疑符 public void method(List list){ ^ ``` 2 错误 这是因为泛型List和List编译后都被擦除了,变成了一样的原生类型List,擦除动作导致这两个方法的特征签名变得一模一样,在Class类文件结构一文中讲过,Class文件中不能存在特征签名相同的方法。 把以上代码修改如下: ``` import java.util.List; public class FanxingTest{ public int method(List list){ System.out.println("List String"); return 1; } public boolean method(List list){ System.out.println("List Int"); return true; } } ``` 发现这时编译可以通过了(注意:Java语言中true和1没有关联,二者属于不同的类型,不能相互转换,不存在C语言中整数值非零即真的情况)。两个不同类型的返回值的加入,使得方法的重载成功了。这是为什么呢? 我们知道,Java代码中的方法特征签名只包括了方法名称、参数顺序和参数类型,并不包括方法的返回值,因此方法的返回值并不参与重载方法的选择,这样看来为重载方法加入返回值貌似是多余的。对于重载方法的选择来说,这确实是多余的,但我们现在要解决的问题是让上述代码能通过编译,让两个重载方法能够合理地共存于同一个Class文件之中,这就要看字节码的方法特征签名,它不仅包括了Java代码中方法特征签名中所包含的那些信息,还包括方法返回值及受查异常表。为两个重载方法加入不同的返回值后,因为有了不同的字节码特征签名,它们便可以共存于一个Class文件之中。 **堆里面的分区:Eden,survival from to,老年代,各自的特点。** **对象创建方法,对象的内存分配,对象的访问定位。** 对内存分配情况分析最常见的示例便是对象实例化: ``` Object obj = new Object(); ``` 这段代码的执行会涉及java栈、Java堆、方法区三个最重要的内存区域。假设该语句出现在方法体中,及时对JVM虚拟机不了解的Java使用这,应该也知道obj会作为引用类型(reference)的数据保存在Java栈的本地变量表中,而会在Java堆中保存该引用的实例化对象,但可能并不知道,Java堆中还必须包含能查找到此对象类型数据的地址信息(如对象类型、父类、实现的接口、方法等),这些类型数据则保存在方法区中。 另外,由于reference类型在Java虚拟机规范里面只规定了一个指向对象的引用,并没有定义这个引用应该通过哪种方式去定位,以及访问到Java堆中的对象的具体位置,因此不同虚拟机实现的对象访问方式会有所不同,主流的访问方式有两种:使用句柄池和直接使用指针。 **GC的两种判定方法:引用计数与引用链。** 引用计数方式最基本的形态就是让每个被管理的对象与一个引用计数器关联在一起,该计数器记录着该对象当前被引用的次数,每当创建一个新的引用指向该对象时其计数器就加1,每当指向该对象的引用失效时计数器就减1。当该计数器的值降到0就认为对象死亡。 Java的内存回收机制可以形象地理解为在堆空间中引入了重力场,已经加载的类的静态变量和处于活动线程的堆栈空间的变量是这个空间的牵引对象。这里牵引对象是指按照Java语言规范,即便没有其它对象保持对它的引用也不能够被回收的对象,即Java内存空间中的本原对象。当然类可能被去加载,活动线程的堆栈也是不断变化的,牵引对象的集合也是不断变化的。对于堆空间中的任何一个对象,如果存在一条或者多条从某个或者某几个牵引对象到该对象的引用链,则就是可达对象,可以形象地理解为从牵引对象伸出的引用链将其拉住,避免掉到回收池中。 **GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别用在什么地方,如果让你优化收集方法,有什么思路?** 标记清除算法是最基础的收集算法,其他收集算法都是基于这种思想。标记清除算法分为“标记”和“清除”两个阶段:首先标记出需要回收的对象,标记完成之后统一清除对象。它的主要缺点:①.标记和清除过程效率不高 。②.标记清除之后会产生大量不连续的内存碎片。 标记整理,标记操作和“标记-清除”算法一致,后续操作不只是直接清理对象,而是在清理无用对象完成后让所有存活的对象都向一端移动,并更新引用其对象的指针。主要缺点:在标记-清除的基础上还需进行对象的移动,成本相对较高,好处则是不会产生内存碎片。 复制算法,它将可用内存容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完之后,就将还存活的对象复制到另外一块上面,然后在把已使用过的内存空间一次理掉。这样使得每次都是对其中的一块进行内存回收,不会产生碎片等情况,只要移动堆订的指针,按顺序分配内存即可,实现简单,运行高效。主要缺点:内存缩小为原来的一半。 **Minor GC与Full GC分别在什么时候发生?** Minor GC:通常是指对新生代的回收。指发生在新生代的垃圾收集动作,因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快 Major GC:通常是指对年老代的回收。 Full GC:Major GC除并发gc外均需对整个堆进行扫描和回收。指发生在老年代的 GC,出现了 Major GC,经常会伴随至少一次的 Minor GC(但非绝对的,在 ParallelScavenge 收集器的收集策略里就有直接进行 Major GC 的策略选择过程) 。MajorGC 的速度一般会比 Minor GC 慢 10倍以上。 **几种常用的内存调试工具:jmap、jstack、jconsole。** jmap(linux下特有,也是很常用的一个命令)观察运行中的jvm物理内存的占用情况。 参数如下: -heap:打印jvm heap的情况 -histo:打印jvm heap的直方图。其输出信息包括类名,对象数量,对象占用大小。 -histo:live :同上,但是只答应存活对象的情况 -permstat:打印permanent generation heap情况 jstack(linux下特有)可以观察到jvm中当前所有线程的运行情况和线程当前状态 jconsole一个图形化界面,可以观察到java进程的gc,class,内存等信息 jstat最后要重点介绍下这个命令。这是jdk命令中比较重要,也是相当实用的一个命令,可以观察到classloader,compiler,gc相关信息 具体参数如下: -class:统计class loader行为信息 -compile:统计编译行为信息 -gc:统计jdk gc时heap信息 -gccapacity:统计不同的generations(不知道怎么翻译好,包括新生区,老年区,permanent区)相应的heap容量情况 -gccause:统计gc的情况,(同-gcutil)和引起gc的事件 -gcnew:统计gc时,新生代的情况 -gcnewcapacity:统计gc时,新生代heap容量 -gcold:统计gc时,老年区的情况 -gcoldcapacity:统计gc时,老年区heap容量 -gcpermcapacity:统计gc时,permanent区heap容量 -gcutil:统计gc时,heap情况 -printcompilation:不知道干什么的,一直没用过。 **类加载的五个过程:加载、验证、准备、解析、初始化。** 类加载过程 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括加载、验证、准备、解析、初始化、使用、卸载。 其中类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。 这里简要说明下Java中的绑定:绑定指的是把一个方法的调用与方法所在的类(方法主体)关联起来,对java来说,绑定分为静态绑定和动态绑定: * 静态绑定:即前期绑定。在程序执行前方法已经被绑定,此时由编译器或其它连接程序实现。针对java,简单的可以理解为程序编译期的绑定。java当中的方法只有final,static,private和构造方法是前期绑定的。 * 动态绑定:即晚期绑定,也叫运行时绑定。在运行时根据具体对象的类型进行绑定。在java中,几乎所有的方法都是后期绑定的。 “加载”(Loading)阶段是“类加载”(Class Loading)过程的第一个阶段,在此阶段,虚拟机需要完成以下三件事情: 1. 通过一个类的全限定名来获取定义此类的二进制字节流。 2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 3. 在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。 验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中。 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。 类初始化是类加载过程的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。 **双亲委派模型:Bootstrap ClassLoader、Extension ClassLoader、ApplicationClassLoader。** 1. 启动类加载器,负责将存放在\lib目录中的,或者被-Xbootclasspath参数所指定的路径中,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即时放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被java程序直接引用。 2. 扩展类加载器:负责加载\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用该类加载器。 3. 应用程序类加载器:负责加载用户路径上所指定的类库,开发者可以直接使用这个类加载器,也是默认的类加载器。 三种加载器的关系:启动类加载器->扩展类加载器->应用程序类加载器->自定义类加载器。 这种关系即为类加载器的双亲委派模型。其要求除启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不以继承关系实现,而是用组合的方式来复用父类的代码。 双亲委派模型的工作过程:如果一个类加载器接收到了类加载的请求,它首先把这个请求委托给他的父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它在搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。 好处:java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar中,无论哪个类加载器要加载这个类,最终都会委派给启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果用户自己写了一个名为java.lang.Object的类,并放在程序的Classpath中,那系统中将会出现多个不同的Object类,java类型体系中最基础的行为也无法保证,应用程序也会变得一片混乱。 实现:在java.lang.ClassLoader的loadClass()方法中,先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载失败,则抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。 **分派:静态分派与动态分派。** 静态分派与重载有关,虚拟机在重载时是通过参数的静态类型,而不是运行时的实际类型作为判定依据的;静态类型在编译期是可知的; 动态分派与重写(Override)相关,invokevirtual(调用实例方法)指令执行的第一步就是在运行期确定接收者的实际类型,根据实际类型进行方法调用; **GC收集器有哪些?CMS收集器与G1收集器的特点。** **自动内存管理机制,GC算法,运行时数据区结构,可达性分析工作原理,如何分配对象内存** **反射机制,双亲委派机制,类加载器的种类** **Jvm内存模型,先行发生原则,violate关键字作用** ================================================ FILE: Part2/JVM/JVM类加载机制.md ================================================ #虚拟机类加载机制 --- **虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被Java虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。** 类从被加载到虚拟内存中开始,到卸载内存为止,它的整个生命周期包括了:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)七个阶段。其中,验证,准备和解析三个部分统称为连接(Linking)。 ###类加载的过程 类加载的全过程,加载,验证,准备,解析和初始化这五个阶段。 --- ####加载 在加载阶段,虚拟机需要完成以下三件事情: * 通过一个类的全限定名来获取定义此类的二进制字节流 * 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构 * 在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口 ####验证 这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。不同的虚拟机对类验证的实现可能有所不同,但大致上都会完成下面四个阶段的检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证。 **文件格式验证** 第一阶段要验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。 **元数据验证** 第二阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。 **字节码验证** 第三阶段时整个验证过程中最复杂的一个阶段,主要工作是数据流和控制流的分析。在第二阶段对元数据信息中的数据类型做完校验后,这阶段将对类的方法体进行校验分析。这阶段的任务是保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。 **符号引用验证** 最后一个阶段的校验发生在虚拟机将符号引用直接转化为直接引用的时候,这个转化动作将在连接的第三个阶段-解析阶段产生。符号引用验证可以看作是对类自身以外(常量池中的各种符号引用)的信息进行匹配性的校验。 ####准备 准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区进行分配。 ####解析 解析阶段是虚拟机将常量池的符号引用转换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行。 * 类或接口的解析 * 字段解析 * 类方法解析 * 接口方法解析 ####初始化 前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由Java虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码(或者说是字节码)。在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者说初始化阶段是执行类构造器()方法的过程。 ###类加载器 --- ####类与类加载器 虚拟机设计团队把类加载阶段中的"通过一个类的全限定名来获取描述此类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让程序自己决定如何去获取所需的类。实现这个动作的代码模块被称为"类加载器"。 ####双亲委派模型 站在Java虚拟机的角度讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另外一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全部继承自抽象类java.lang.ClassLoader。从Java开发人员的角度来看,类加载器还可以分得更细致一些,绝大部分Java程序都会使用到以下三种系统提供的类加载器: * 启动类加载器 * 扩展类加载器 * 应用程序类加载器 ================================================ FILE: Part2/JVM/Java内存区域与内存溢出.md ================================================ ##内存区域 Java虚拟机在执行Java程序的过程中会把他所管理的内存划分为若干个不同的数据区域。Java虚拟机规范将JVM所管理的内存分为以下几个运行时数据区:程序计数器,Java虚拟机栈,本地方法栈,Java堆,方法区。下面详细阐述各数据区所存储的数据类型。 ![这里写图片描述](http://img.blog.csdn.net/20160401142029373) **程序计数器(Program Counter Register)** 一块较小的内存空间,它是当前线程所执行的子节码的行号指示器,字节码解释器工作时通过改变该计数器的值来选择下一条需要执行的子节码指令,分支、跳转、循环等基础功能都要依赖它来实现。每条线程都有一个独立的程序计数器,各线程间的计数器互不影响,因此该区域是线程私有的。 当线程在执行一个Java方法时,该计数器纪录的是正在执行的虚拟机字节吗指令的地址,当线程在执行的是Native方法(调用本地操作系统方法)时,该计数器的值为空。另外,该内存区域是唯一一个在Java虚拟机规范中没有任何OOM(内存溢出:OutOfMemoryError)情况的区域。 **Java虚拟机栈(Java Virtual Machine Stacks)** 该区域也是线程私有的,它的生命周期也与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会创建一个帧栈,栈它是用于支持虚拟机进行方法调用和方法执行的数据结构。对于执行引擎来讲,活动线程中,只有栈顶的栈帧是有效的,称为当前栈,这个栈帧所关联的方法称为当前方法,执行引擎所运行的所有字节码都只针对当前的栈帧进行操作。栈帧用于存储局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。在编译程序代码时,栈帧中需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。 在Java虚拟机规范中,对这个区域规定了两种异常情况: 1. 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。 2. 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemory异常。 这两种情况存在着一些互相重叠的部分:当栈空间无法继续分配时,到底是内存太小,还是已使用的栈空间太大,其本质只是对同一件事情的两种描述而已。其本质上只是对一件事情的两种描述而已。在单线程的操作中,无论是由于栈帧太大,还是虚拟机栈空间太小,当栈空间无法分配时,虚拟机抛出的都是StackOverflowError异常,而不会得到OutOfMemoryError异常。而在多线程环境下,则会抛出OutOfMemory异常。 下面详细说明栈帧中所存放的各部分信息的作用和数据结构。 局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量,其中存放的数据的类型是编译期可知的各种基本数据类型、对象引用(reference)和returnAddress类型(它指向了一条字节码指令的地址)。局部变量表所需的内存空间在编译期间完成分配,即在Java程序被编译成Class文件时,就确定了所需分配的最大局部变量表的容量。当进入一个方法时,这个方法需要在栈中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。 下面详细说明栈帧中所存放的各部分信息的作用和数据结构。 1、局部变量表 局部变量表的容量以变量槽(Slot)为最小单位。在虚拟机规范中并没有明确指明一个Slot应占用的内存空间大小(允许其随着处理器、操作系统或虚拟机的不同而发生变化),一个Slot可以存放一个32位以内的数据类型:boolean、byte、char、short、int、float、reference和returnAddresss。reference是对象的引用类型,returnAddress是为字节指令服务的,它执行了一条字节码指令的地址。对于64位的数据类型(long和double),虚拟机会以高位在前的方式为其分配两个连续的Slot空间。 虚拟机通过索引定位的方式使用局部变量表,索引值的范围是从0开始到局部变量表最大的Slot数量,对于32位数据类型的变量,索引n代表第n个Slot,对于64位的,索引n代表第n和第n+1两个Slot。 在方法执行时,虚拟机是使用局部变量表来完成参数值到参数变量列表的传递过程的,如果是实例方法(非static),则局部变量表中的第0位索引的Slot默认是用于传递方法所属对象实例的引用,在方法中可以通过关键字“this”来访问这个隐含的参数。其余参数则按照参数表的顺序来排列,占用从1开始的局部变量Slot,参数表分配完毕后,再根据方法体内部定义的变量顺序和作用域分配其余的Slot。 局部变量表中的Slot是可重用的,方法体中定义的变量,作用域并不一定会覆盖整个方法体,如果当前字节码PC计数器的值已经超过了某个变量的作用域,那么这个变量对应的Slot就可以交给其他变量使用。这样的设计不仅仅是为了节省空间,在某些情况下Slot的复用会直接影响到系统的而垃圾收集行为。 2、操作数栈 操作数栈又常被称为操作栈,操作数栈的最大深度也是在编译的时候就确定了。32位数据类型所占的栈容量为1,64为数据类型所占的栈容量为2。当一个方法开始执行时,它的操作栈是空的,在方法的执行过程中,会有各种字节码指令(比如:加操作、赋值元算等)向操作栈中写入和提取内容,也就是入栈和出栈操作。 Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。因此我们也称Java虚拟机是基于栈的,这点不同于Android虚拟机,Android虚拟机是基于寄存器的。 基于栈的指令集最主要的优点是可移植性强,主要的缺点是执行速度相对会慢些;而由于寄存器由硬件直接提供,所以基于寄存器指令集最主要的优点是执行速度快,主要的缺点是可移植性差。 3、动态连接 每个栈帧都包含一个指向运行时常量池(在方法区中,后面介绍)中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。Class文件的常量池中存在有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用,一部分会在类加载阶段或第一次使用的时候转化为直接引用(如final、static域等),称为静态解析,另一部分将在每一次的运行期间转化为直接引用,这部分称为动态连接。 4、方法返回地址 当一个方法被执行后,有两种方式退出该方法:执行引擎遇到了任意一个方法返回的字节码指令或遇到了异常,并且该异常没有在方法体内得到处理。无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行。方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者的PC计数器的值就可以作为返回地址,栈帧中很可能保存了这个计数器值,而方法异常退出时,返回地址是要通过异常处理器来确定的,栈帧中一般不会保存这部分信息。 方法退出的过程实际上等同于把当前栈帧出站,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,如果有返回值,则把它压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令。 **本地方法栈(Native Method Stacks)** 该区域与虚拟机栈所发挥的作用非常相似,只是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为使用到的本地操作系统(Native)方法服务。 **Java堆(Java Heap)** Java Heap是Java虚拟机所管理的内存中的最大的一块,它是所有线程共享的一块内存区域。几乎所有的对象实例和数组都在这类分配内存。Java Heap是垃圾收集器管理的主要区域,因此很多时候也被称为"GC堆"。 根据Java虚拟机的规定,Java堆可以处在物理上不连续的内存空间中,只要逻辑上是连续的即可。如果在堆中没有内存可分配时,并且堆也无法扩展时,将会抛出OutOfMemory。 **方法区(Method Area)** 方法区也是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区域又被称为"永久代"。但着这仅仅对于Sun HotSpot来讲,JRocket和IBMJ9虚拟机中并不存在永久代的概念。Java虚拟机规范把方法区描述为Java堆的一个逻辑部分,而且它和Java Heap一样不需要连续的内存,可以选择固定大小或可扩展,另外,虚拟机规范允许该区域可以选择不实现垃圾回收。相对而言,垃圾收集行为在这个区域比较少出现。该区域的内存回收目标主要针是对废弃常量的和无用类的回收。运行时常量池是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Class文件常量池),用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。运行时常量池相对于Class文件常量池的另一个重要特征是具备动态性,Java语言并不要求常量一定只能在编译期产生,也就是并非预置入Class文件中的常量池的内容才能进入方法区的运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的是String类的intern()方法。 根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。 直接内存(Direct Memory) 直接内存并不是虚拟机运行内存时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,它直接从操作系统中分配内存,因此不受Java堆的大小的限制,但是会受到本机总内存的大小及处理器寻址空间的限制,因此它也可能导致OutOfMemoryError异常出现。在Java1.4中新引入了NIO机制,它是一种基于通道与缓冲区的新I/O方式,可以直接从操作系统中分配直接内存,可以直接从操作系统中分配直接内存,即在堆外分配内存,这样能在一些场景中提高性能,因为避免了在Java堆和Native堆中来回复制数据。 内存溢出 下面给出个内存区域内存溢出的简单测试方法 ![这里写图片描述](http://img.blog.csdn.net/20160401173849014) 这里有一点要重点说明,在多线程情况下,给每个线程的栈分配的内存越大,反而越容易产生内存产生内存溢出一场。操作系统为每个进程分配的内存是有限制的,虚拟机提供了参数来控制Java堆和方法区这两部分内存的最大值,忽略掉程序计数器消耗的内存(很小),以及进程本身消耗的内存,剩下的内存便给了虚拟机栈和本地方法栈,每个线程分配到的栈容量越大,可以建立的线程数量自然就越少。因此,如果是建立过多的线程导致的内存溢出,在不能减少线程数的情况下,就只能通过减少最大堆和每个线程的栈容量来换取更多的线程。 另外,由于Java堆内也可能发生内存泄露(Memory Leak),这里简要说明一下内存泄露和内存溢出的区别: 内存泄漏是指分配出去的内存没有被回收回来,由于失去了对该内存区域的控制,因而造成了资源的浪费。Java中一般不会产生内存泄漏,因为有垃圾回收器自动回收垃圾,但这也不绝对,当我们new了对象,并保存了其引用,但是后面一直没用它,而垃圾回收器又不会去回收它,这就会造成内存泄漏。 内存溢出是指程序所需要的内存超过了系统所能分配的内存(包括动态扩展)的上限。 对象实例化分析 对内存分配情况分析最常见的示例便是对象实例化: ``` Object obj = new Object(); ``` 这段代码的执行会涉及java栈、Java堆、方法区三个最重要的内存区域。假设该语句出现在方法体中,及时对JVM虚拟机不了解的Java使用这,应该也知道obj会作为引用类型(reference)的数据保存在Java栈的本地变量表中,而会在Java堆中保存该引用的实例化对象,但可能并不知道,Java堆中还必须包含能查找到此对象类型数据的地址信息(如对象类型、父类、实现的接口、方法等),这些类型数据则保存在方法区中。 另外,由于reference类型在Java虚拟机规范里面只规定了一个指向对象的引用,并没有定义这个引用应该通过哪种方式去定位,以及访问到Java堆中的对象的具体位置,因此不同虚拟机实现的对象访问方式会有所不同,主流的访问方式有两种:使用句柄池和直接使用指针。 通过句柄池访问的方式如下: ![这里写图片描述](http://img.blog.csdn.net/20160401175131207) 通过直接指针访问的方式如下: ![这里写图片描述](http://img.blog.csdn.net/20160401175203926) 这两种对象的访问方式各有优势,使用句柄访问方式的最大好处就是reference中存放的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。使用直接指针访问方式的最大好处是速度快,它节省了一次指针定位的时间开销。目前Java默认使用的HotSpot虚拟机采用的便是是第二种方式进行对象访问的。 ================================================ FILE: Part2/JVM/垃圾回收算法.md ================================================ #垃圾回收算法 --- 1. 引用计数法:缺点是无法处理循环引用问题 2. 标记-清除法:标记所有从根结点开始的可达对象,缺点是会造成内存空间不连续,不连续的内存空间的工作效率低于连续的内存空间,不容易分配内存 3. 复制算法:将内存空间分成两块,每次将正在使用的内存中存活对象复制到未使用的内存块中,之后清除正在使用的内存块。算法效率高,但是代价是系统内存折半。适用于新生代(存活对象少,垃圾对象多) 4. 标记-压缩算法:标记-清除的改进,清除未标记的对象时还将所有的存活对象压缩到内存的一端,之后,清理边界所有空间既避免碎片产生,又不需要两块同样大小的内存快,性价比高。适用于老年代。 5. 分代 ================================================ FILE: Part2/JavaConcurrent/Java并发基础知识.md ================================================ #Java并发 --- (Executor框架和多线程基础) **Thread与Runable如何实现多线程** Java 5以前实现多线程有两种实现方法:一种是继承Thread类;另一种是实现Runnable接口。两种方式都要通过重写run()方法来定义线程的行为,推荐使用后者,因为Java中的继承是单继承,一个类有一个父类,如果继承了Thread类就无法再继承其他类了,显然使用Runnable接口更为灵活。 实现Runnable接口相比继承Thread类有如下优势: 1. 可以避免由于Java的单继承特性而带来的局限 2. 增强程序的健壮性,代码能够被多个程序共享,代码与数据是独立的 3. 适合多个相同程序代码的线程区处理同一资源的情况 补充:Java 5以后创建线程还有第三种方式:实现Callable接口,该接口中的call方法可以在线程执行结束时产生一个返回值,代码如下所示: ``` class MyTask implements Callable { private int upperBounds; public MyTask(int upperBounds) { this.upperBounds = upperBounds; } @Override public Integer call() throws Exception { int sum = 0; for(int i = 1; i <= upperBounds; i++) { sum += i; } return sum; } } public class Test { public static void main(String[] args) throws Exception { List> list = new ArrayList<>(); ExecutorService service = Executors.newFixedThreadPool(10); for(int i = 0; i < 10; i++) { list.add(service.submit(new MyTask((int) (Math.random() * 100)))); } int sum = 0; for(Future future : list) { while(!future.isDone()) ; sum += future.get(); } System.out.println(sum); } } ``` **线程同步的方法有什么;锁,synchronized块,信号量等** **锁的等级:方法锁、对象锁、类锁** **生产者消费者模式的几种实现,阻塞队列实现,sync关键字实现,lock实现,reentrantLock等** **ThreadLocal的设计理念与作用,ThreadPool用法与优势(这里在Android SDK原生的AsyncTask底层也有使用)** **线程池的底层实现和工作原理(建议写一个雏形简版源码实现)** **几个重要的线程api,interrupt,wait,sleep,stop等等** **写出生产者消费者模式。** **ThreadPool用法与优势。** **Concurrent包里的其他东西:ArrayBlockingQueue、CountDownLatch等等。** **wait()和sleep()的区别。** sleep()方法是线程类(Thread)的静态方法,导致此线程暂停执行指定时间,将执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复(线程回到就绪(ready)状态),因为调用sleep 不会释放对象锁。wait()是Object 类的方法,对此对象调用wait()方法导致本线程放弃对象锁(线程暂停执行),进入等待此对象的等待锁定池,只有针对此对象发出notify 方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入就绪状态。 ###3、IO(IO,NIO,目前okio已经被集成Android包) **IO框架主要用到什么设计模式** JDK的I/O包中就主要使用到了两种设计模式:Adatper模式和Decorator模式。 **NIO包有哪些结构?分别起到的作用?** **NIO针对什么情景会比IO有更好的优化?** **OKIO底层实现** ================================================ FILE: Part2/JavaConcurrent/NIO.md ================================================ #NIO --- Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java1.4开始),Java NIO提供了与标准IO不同的IO工作方式。 ###Java NIO: Channels and Buffers(通道和缓冲区) 标准的俄IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入通道也类似。 ###Java NIO: Non-blocking IO(非阻塞IO) Java NIO可以让你非阻塞的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。 ###Java NIO: Selectors(选择器) Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。 NIO由以下核心部分组成: * Channels * Buffers * Selectors **Channel和Buffer** 基本上,所有的IO和NIO都从一个Channel开始。Channel有点像流。数据可以从Channel读到Buffer中,也可以从Buffer写到Channel中 ![](http://ifeve.com/wp-content/uploads/2013/06/overview-channels-buffers1.png) Channel的实现 * FileChannel * DatagramChannel * SocketChannel * ServerSocketChannel 这些通道涵盖了UDP和TCP网络IO,以及文件IO。 以下是Java NIO里关键的Buffer实现 * ByteBuffer * CharBuffer * DoubleBuffer * FloatBuffer * IntBuffer * LongBuffer * ShortBuffer 这些Buffer覆盖了你能通过IO发送的基本数据类型:byte,short,int,long,float,double和char Java NIO还有个MappedByteBuffer,用于表示内存映射文件。 **Selextor** Selector允许单线程处理多个Channel。如果你的应用打开了多个连接(通道),但每一个连接的流量都很低,使用Selector就会很方便。 例如,在一个聊天服务器中 这是在一个单线程中使用一个Selector处理3个Channel的图示: ![](http://ifeve.com/wp-content/uploads/2013/06/overview-selectors.png) 要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接送等。 ##Channel --- Java NIO的通道类似流,但又有些不同: * 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。 * 通道可以异步的读写。 * 通道的数据总是要先读到一个Buffer,或者总要从一个Buffer中写入。 正如上面所说,从通道读取数据到缓冲区,从缓冲区写入数据到通道。 **Channel的实现** * FileChannel 从文件中读取数据 * DataChannel 能通过UDP读写网络中的数据 * SocketChannel 能通过TCP读写网络中的数据 * ServerSocketChannel 可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel 基本的Channel示例 下面是一个使用FileChannel读取数据到Buffer中的示例 ``` RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw"); FileChannel inChannel = aFile.getChannel(); ByteBuffer buf = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buf); while (bytesRead != -1) { System.out.println("Read " + bytesRead); buf.flip(); while(buf.hasRemaining()){ System.out.print((char) buf.get()); } buf.clear(); bytesRead = inChannel.read(buf); } aFile.close(); ``` 注意 buf.flip() 的调用,首先读取数据到Buffer,然后反转Buffer,接着再从Buffer中读取数据。 ##Buffer Java NIO中的Buffer用于和NIO通道进行交互。如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的。 缓冲区本质是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问这块内存。 **Buffer的基本用法** 使用Buffer读写数据一般遵循以下四个步骤: 1. 写入数据到Buffer 2. 调用flip()方法 3. 从Buffer中读取数据 4. 调用clear()方法或者compact()方法 ================================================ FILE: Part2/JavaConcurrent/Synchronized.md ================================================ #synchronized --- 在并发编程中,多线程同时并发访问的资源叫做临界资源,当多个线程同时访问对象并要求操作相同资源时,分割了原子操作就有可能出现数据的不一致或数据不完整的情况,为避免这种情况的发生,我们会采取同步机制,以确保在某一时刻,方法内只允许有一个线程。 采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。 这里就使用同步机制获取互斥锁的情况,进行几点说明: 1. 如果同一个方法内同时有两个或更多线程,则每个线程有自己的局部变量拷贝。 2. 类的每个实例都有自己的对象级别锁。当一个线程访问实例对象中的synchronized同步代码块或同步方法时,该线程便获取了该实例的对象级别锁,其他线程这时如果要访问synchronized同步代码块或同步方法,便需要阻塞等待,直到前面的线程从同步代码块或方法中退出,释放掉了该对象级别锁。 3. 访问同一个类的不同实例对象中的同步代码块,不存在阻塞等待获取对象锁的问题,因为它们获取的是各自实例的对象级别锁,相互之间没有影响。 4. 持有一个对象级别锁不会阻止该线程被交换出来,也不会阻塞其他线程访问同一示例对象中的非synchronized代码。当一个线程A持有一个对象级别锁(即进入了synchronized修饰的代码块或方法中)时,线程也有可能被交换出去,此时线程B有可能获取执行该对象中代码的时间,但它只能执行非同步代码(没有用synchronized修饰),当执行到同步代码时,便会被阻塞,此时可能线程规划器又让A线程运行,A线程继续持有对象级别锁,当A线程退出同步代码时(即释放了对象级别锁),如果B线程此时再运行,便会获得该对象级别锁,从而执行synchronized中的代码。 5. 持有对象级别锁的线程会让其他线程阻塞在所有的synchronized代码外。例如,在一个类中有三个synchronized方法a,b,c,当线程A正在执行一个实例对象M中的方法a时,它便获得了该对象级别锁,那么其他的线程在执行同一实例对象(即对象M)中的代码时,便会在所有的synchronized方法处阻塞,即在方法a,b,c处都要被阻塞,等线程A释放掉对象级别锁时,其他的线程才可以去执行方法a,b或者c中的代码,从而获得该对象级别锁。 6. 使用synchronized(obj)同步语句块,可以获取指定对象上的对象级别锁。obj为对象的引用,如果获取了obj对象上的对象级别锁,在并发访问obj对象时时,便会在其synchronized代码处阻塞等待,直到获取到该obj对象的对象级别锁。当obj为this时,便是获取当前对象的对象级别锁。 7. 类级别锁被特定类的所有示例共享,它用于控制对static成员变量以及static方法的并发访问。具体用法与对象级别锁相似。 8. 互斥是实现同步的一种手段,临界区、互斥量和信号量都是主要的互斥实现方式。synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令。根据虚拟机规范的要求,在执行monitorenter指令时,首先要尝试获取对象的锁,如果获得了锁,把锁的计数器加1,相应地,在执行monitorexit指令时会将锁计数器减1,当计数器为0时,锁便被释放了。由于synchronized同步块对同一个线程是可重入的,因此一个线程可以多次获得同一个对象的互斥锁,同样,要释放相应次数的该互斥锁,才能最终释放掉该锁。 ##内存可见性 加锁(synchronized同步)的功能不仅仅局限于互斥行为,同时还存在另外一个重要的方面:内存可见性。我们不仅希望防止某个线程正在使用对象状态而另一个线程在同时修改该状态,而且还希望确保当一个线程修改了对象状态后,其他线程能够看到该变化。而线程的同步恰恰也能够实现这一点。 内置锁可以用于确保某个线程以一种可预测的方式来查看另一个线程的执行结果。为了确保所有的线程都能看到共享变量的最新值,可以在所有执行读操作或写操作的线程上加上同一把锁。下图示例了同步的可见性保证。 ![](http://img.blog.csdn.net/20131212211029125) 当线程A执行某个同步代码块时,线程B随后进入由同一个锁保护的同步代码块,这种情况下可以保证,当锁被释放前,A看到的所有变量值(锁释放前,A看到的变量包括y和x)在B获得同一个锁后同样可以由B看到。换句话说,当线程B执行由锁保护的同步代码块时,可以看到线程A之前在同一个锁保护的同步代码块中的所有操作结果。如果在线程A unlock M之后,线程B才进入lock M,那么线程B都可以看到线程A unlock M之前的操作,可以得到i=1,j=1。如果在线程B unlock M之后,线程A才进入lock M,那么线程B就不一定能看到线程A中的操作,因此j的值就不一定是1。 现在考虑如下代码: ``` public class MutableInteger { private int value; public int get(){ return value; } public void set(int value){ this.value = value; } } ``` 以上代码中,get和set方法都在没有同步的情况下访问value。如果value被多个线程共享,假如某个线程调用了set,那么另一个正在调用get的线程可能会看到更新后的value值,也可能看不到。 通过对set和get方法进行同步,可以使MutableInteger成为一个线程安全的类,如下: ``` public class SynchronizedInteger { private int value; public synchronized int get(){ return value; } public synchronized void set(int value){ this.value = value; } } ``` 对set和get方法进行了同步,加上了同一把对象锁,这样get方法可以看到set方法中value值的变化,从而每次通过get方法取得的value的值都是最新的value值。 ================================================ FILE: Part2/JavaConcurrent/Thread和Runnable实现多线程的区别.md ================================================ #Thread和Runnable实现多线程的区别 --- Java中实现多线程有两种方法:继承Thread、实现Runnable接口,在程序开发中只要是多线程,肯定永远以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下优势: 1. 可以避免由于Java的单继承特性而带来的局限 2. 增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的 3. 适合多个相同程序的线程区处理同一资源的情况 首先通过Thread类实现 ``` class MyThread extends Thread{ private int ticket = 5; public void run(){ for (int i=0;i<10;i++) { if(ticket > 0){ System.out.println("ticket = " + ticket--); } } } } public class ThreadDemo{ public static void main(String[] args){ new MyThread().start(); new MyThread().start(); new MyThread().start(); } } ``` 运行结果: ``` ticket = 5 ticket = 4 ticket = 5 ticket = 5 ticket = 4 ticket = 3 ticket = 2 ticket = 1 ticket = 4 ticket = 3 ticket = 3 ticket = 2 ticket = 1 ticket = 2 ticket = 1 ``` 每个线程单独卖了5张票,即独立的完成了买票的任务,但实际应用中,比如火车站售票,需要多个线程去共同完成任务,在本例中,即多个线程共同买5张票。 通过实现Runnable借口实现的多线程程序 ``` class MyThread implements Runnable{ private int ticket = 5; public void run(){ for (int i=0;i<10;i++) { if(ticket > 0){ System.out.println("ticket = " + ticket--); } } } } public class RunnableDemo{ public static void main(String[] args){ MyThread my = new MyThread(); new Thread(my).start(); new Thread(my).start(); new Thread(my).start(); } } ``` 运行结果 ``` ticket = 5 ticket = 2 ticket = 1 ticket = 3 ticket = 4 ``` * 在第二种方法(Runnable)中,ticket输出的顺序并不是54321,这是因为线程执行的时机难以预测。ticket并不是原子操作。 * 在第一种方法中,我们new了3个Thread对象,即三个线程分别执行三个对象中的代码,因此便是三个线程去独立地完成卖票的任务;而在第二种方法中,我们同样也new了3个Thread对象,但只有一个Runnable对象,3个Thread对象共享这个Runnable对象中的代码,因此,便会出现3个线程共同完成卖票任务的结果。如果我们new出3个Runnable对象,作为参数分别传入3个Thread对象中,那么3个线程便会独立执行各自Runnable对象中的代码,即3个线程各自卖5张票。 * 在第二种方法中,由于3个Thread对象共同执行一个Runnable对象中的代码,因此可能会造成线程的不安全,比如可能ticket会输出-1(如果我们System.out....语句前加上线程休眠操作,该情况将很有可能出现),这种情况的出现是由于,一个线程在判断ticket为1>0后,还没有来得及减1,另一个线程已经将ticket减1,变为了0,那么接下来之前的线程再将ticket减1,便得到了-1。这就需要加入同步操作(即互斥锁),确保同一时刻只有一个线程在执行每次for循环中的操作。而在第一种方法中,并不需要加入同步操作,因为每个线程执行自己Thread对象中的代码,不存在多个线程共同执行同一个方法的情况。 ================================================ FILE: Part2/JavaConcurrent/thread与runable如何实现多线程.md ================================================ ##Thread与Runable如何实现多线程? Java实现多线程有两种途径:继承Thread类或者实现Runnable接口。Runnable是接口 Runnable是接口,建议用接口的方式生成线程,因为接口可以实现多继承,况且Runnable只有一个run方法,很适合继承。 在使用Thread方法的时候只需继承Thread,并且new一个实例出来,调用start()方法既可以启动一个线程 [ java多线程 Thread 和Runnable](http://blog.csdn.net/luyysea/article/details/7995351) ================================================ FILE: Part2/JavaConcurrent/volatile变量修饰符.md ================================================ #volatile变量修饰符 --- ##volatile用处说明 在JDK1.2之前,Java的类型模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而随着JVM的成熟和优化,现在在多线程环境下volatile关键字的使用变的非常重要。 在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另一个线程还在继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。 要解决这个问题,就需要把变量声明为volatile,这就指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。一般来说,多任务环境下,各任务间共享的变量都应该加volatile修饰符。 volatile修饰de成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。 Java语言规范中指出:为了获得最佳速度,允许线程保存成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才将私有拷贝与共享内存中的原始值进行比较。 这样当多个线程同时与某个对象交互时,就必须注意到要让线程及时的得到共享成员变量的变化。而volatile关键字就是提示JVM:对于这个成员变量,不能保存它的私有拷贝,而应直接与共享成员变量交互。 volatile是一种稍弱的同步机制,在访问volatile变量时不会执行加锁操作,也就不会执行线程阻塞,因此volatilei变量是一种比synchronized关键字更轻量级的同步机制。 使用建议:在两个或者更多的线程需要访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,或者为常量时,没必要使用volatile。 由于使用volatile屏蔽掉了JVM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。 下面给出一段代码,通过其运行结果来说明使用关键字volatile产生的差异,但实际上遇到了意料之外的问题: ``` ``` ================================================ FILE: Part2/JavaConcurrent/使用wait notify notifyall实现线程间通信.md ================================================ #使用wait/notify/notifyAll实现线程间通信 --- 在Java中,可以通过配合调用Object对象的wait()方法和notify()方法或notifyAll()方法来实现线程间的通信。在线程中调用wait()方法,将阻塞等待其他线程的通知(其他线程调用notify()方法或notifyAll()方法),在线程中调用notify()方法或notifyAll()方法,将通知其他线程从wait()方法处返回。 Object是所有类的超类,它有5个方法组成了等待/通知机制的核心:notify()、notifyAll()、wait()、wait(long)和wait(long,int)。在Java中,所有的类都从Object继承而来,因此,所有的类都拥有这些共有方法可供使用。而且,由于他们都被声明为final,因此在子类中不能覆写任何一个方法。 这里详细说明一下各个方法在使用中需要注意的几点: 1、wait() ``` public final void wait() throws InterruptedException,IllegalMonitorStateException ``` 该方法用来将当前线程置入休眠状态,直到接到通知或被中断为止。在调用wait()之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。进入wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时,没有持有适当的锁,则抛出IllegalMonitorStateException,它是RuntimeException的一个子类,因此,不需要try-catch结构。 2、notify() ``` public final native void notify() throws IllegalMonitorStateException ``` 该方法也要在同步方法或同步块中调用,即在调用前,线程也必须要获得该对象的对象级别锁,的如果调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。 该方法用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个wait()状态的线程来发出通知,并使它等待获取该对象的对象锁(notify后,当前线程不会马上释放该对象锁,wait所在的线程并不能马上获取该对象锁,要等到程序退出synchronized代码块后,当前线程才会释放锁,wait所在的线程也才可以获取该对象锁),但不惊动其他同样在等待被该对象notify的线程们。当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll。这里需要注意:它们等待的是被notify或notifyAll,而不是锁。这与下面的notifyAll()方法执行后的情况不同。 3、notifyAll() ``` public final native void notifyAll() throws IllegalMonitorStateException ``` 该方法与notify()方法的工作方式相同,重要的一点差异是: notifyAll使所有原来在该对象上wait的线程统统退出wait的状态(即全部被唤醒,不再等待notify或notifyAll,但由于此时还没有获取到该对象锁,因此还不能继续往下执行),变成等待获取该对象上的锁,一旦该对象锁被释放(notifyAll线程退出调用了notifyAll的synchronized代码块的时候),他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出synchronized代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。 4、wait(long)和wait(long,int) 显然,这两个方法是设置等待超时时间的,后者在超值时间上加上ns,精度也难以达到,因此,该方法很少使用。对于前者,如果在等待线程接到通知或被中断之前,已经超过了指定的毫秒数,则它通过竞争重新获得锁,并从wait(long)返回。另外,需要知道,如果设置了超时时间,当wait()返回时,我们不能确定它是因为接到了通知还是因为超时而返回的,因为wait()方法不会返回任何相关的信息。但一般可以通过设置标志位来判断,在notify之前改变标志位的值,在wait()方法后读取该标志位的值来判断,当然为了保证notify不被遗漏,我们还需要另外一个标志位来循环判断是否调用wait()方法。 深入理解: * 如果线程调用了对象的wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。 * 当有线程调用了对象的notifyAll()方法(唤醒所有wait线程)或notify()方法(只随机唤醒一个wait线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。 * 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了synchronized代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。 ================================================ FILE: Part2/JavaConcurrent/可重入内置锁.md ================================================ #可重入内置锁 --- 每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁。线程在进入同步代码块之前会自动获取锁,并且在退出同步代码块时会自动释放锁。获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。 当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞。然而,由于内置锁是可重入的,因此如果摸个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功。“重入”意味着获取锁的操作的粒度是“线程”,而不是调用。重入的一种实现方法是,为每个锁关联一个获取计数值和一个所有者线程。当计数值为0时,这个锁就被认为是没有被任何线程所持有,当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将获取计数值置为1,如果同一个线程再次获取这个锁,计数值将递增,而当线程退出同步代码块时,计数器会相应地递减。当计数值为0时,这个锁将被释放。 重入进一步提升了加锁行为的封装性,因此简化了面向对象并发代码的开发。分析如下程序: ``` public class Father { public synchronized void doSomething(){ ...... } } public class Child extends Father { public synchronized void doSomething(){ ...... super.doSomething(); } } ``` 子类覆写了父类的同步方法,然后调用父类中的方法,此时如果没有可重入的锁,那么这段代码件产生死锁。 由于Fither和Child中的doSomething方法都是synchronized方法,因此每个doSomething方法在执行前都会获取Child对象实例上的锁。如果内置锁不是可重入的,那么在调用super.doSomething时将无法获得该Child对象上的互斥锁,因为这个锁已经被持有,从而线程会永远阻塞下去,一直在等待一个永远也无法获取的锁。重入则避免了这种死锁情况的发生。 同一个线程在调用本类中其他synchronized方法/块或父类中的synchronized方法/块时,都不会阻碍该线程地执行,因为互斥锁时可重入的。 ================================================ FILE: Part2/JavaConcurrent/多线程环境中安全使用集合API.md ================================================ #多线程环境中安全使用集合API --- 在集合API中,最初设计的Vector和Hashtable是多线程安全的。例如:对于Vector来说,用来添加和删除元素的方法是同步的。如果只有一个线程与Vector的实例交互,那么,要求获取和释放对象锁便是一种浪费,另外在不必要的时候如果滥用同步化,也有可能会带来死锁。因此,对于更改集合内容的方法,没有一个是同步化的。集合本质上是非多线程安全的,当多个线程与集合交互时,为了使它多线程安全,必须采取额外的措施。 在Collections类 中有多个静态方法,它们可以获取通过同步方法封装非同步集合而得到的集合: * public static Collection synchronizedCollention(Collection c) * public static List synchronizedList(list l) * public static Map synchronizedMap(Map m) * public static Set synchronizedSet(Set s) * public static SortedMap synchronizedSortedMap(SortedMap sm) * public static SortedSet synchronizedSortedSet(SortedSet ss) 这些方法基本上返回具有同步集合方法版本的新类。比如,为了创建多线程安全且由ArrayList支持的List,可以使用如下代码: ``` List list = Collection.synchronizedList(new ArrayList()); ``` 注意,ArrayList实例马上封装起来,不存在对未同步化ArrayList的直接引用(即直接封装匿名实例)。这是一种最安全的途径。如果另一个线程要直接引用ArrayList实例,它可以执行非同步修改。 下面给出一段多线程中安全遍历集合元素的示例。我们使用Iterator逐个扫描List中的元素,在多线程环境中,当遍历当前集合中的元素时,一般希望阻止其他线程添加或删除元素。安全遍历的实现方法如下: ``` import java.util.*; public class SafeCollectionIteration extends Object { public static void main(String[] args) { //为了安全起见,仅使用同步列表的一个引用,这样可以确保控制了所有访问 //集合必须同步化,这里是一个List List wordList = Collections.synchronizedList(new ArrayList()); //wordList中的add方法是同步方法,会获取wordList实例的对象锁 wordList.add("Iterators"); wordList.add("require"); wordList.add("special"); wordList.add("handling"); //获取wordList实例的对象锁, //迭代时,阻塞其他线程调用add或remove等方法修改元素 synchronized ( wordList ) { Iterator iter = wordList.iterator(); while ( iter.hasNext() ) { String s = (String) iter.next(); System.out.println("found string: " + s + ", length=" + s.length()); } } } } ``` 这里需要注意的是:在Java语言中,大部分的线程安全类都是相对线程安全的,它能保证对这个对象单独的操作时线程安全的,我们在调用的时候不需要额外的保障措施,但是对于一些特定的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。例如Vector、HashTable、Collections的synchronizedXxxx()方法包装的集合等。 ================================================ FILE: Part2/JavaConcurrent/守护线程与阻塞线程.md ================================================ #守护线程与阻塞线程的四种情况 --- Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 用户线程即运行在前台的线程,而守护线程是运行在后台的线程。 守护线程作用是为其他前台线程的运行提供便利服务,而且仅在普通、非守护线程仍然运行时才需要,比如垃圾回收线程就是一个守护线程。当VM检测仅剩一个守护线程,而用户线程都已经退出运行时,VM就会退出,因为如果没有了守护者,也就没有继续运行程序的必要了。如果有非守护线程仍然活着,VM就不会退出。 守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。用户可以用Thread的setDaemon(true)方法设置当前线程为守护线程。 虽然守护线程可能非常有用,但必须小心确保其它所有非守护线程消亡时,不会由于它的终止而产生任何危害。因为你不可能知道在所有的用户线程退出运行前,守护线程是否已经完成了预期的服务任务。一旦所有的用户线程退出了,虚拟机也就退出运行了。因此,不要再守护线程中执行业务逻辑操作(比如对数据的读写等)。 还有几点: 1. setDaemon(true)必须在调用线程的start()方法之前设置,否则会跑出IllegalThreadStateException异常。 2. 在守护线程中产生的新线程也是守护线程 3. 不要认为所有的应用都可以分配给守护线程来进行服务,比如读写操作或者计算逻辑。 ##线程阻塞 线程可以阻塞于四种状态: 1. 当线程执行Thread.sleep()时,它一直阻塞到指定的毫秒时间之后,或者阻塞被另一个线程打断 2. 当线程碰到一条wait()语句时,它会一直阻塞到接到通知(notify())、被中断或经过了指定毫秒 时间为止(若指定了超时值的话) 3. 线程阻塞与不同的I/O的方式有多种。常见的一种方式是InputStream的read()方法,该方法一直阻塞到从流中读取一个字节的数据为止,它可以无限阻塞,因此不能指定超时时间 4. 线程也可以阻塞等待获取某个对象锁的排它性访问权限(即等待获得synchronized语句必须的锁时阻塞) 并非所有的阻塞状态都是可中断的,以上阻塞状态的前两种可以被中断,后两种不会对中断做出反应。 ================================================ FILE: Part2/JavaConcurrent/实现内存可见的两种方法比较:加锁和volatile变量.md ================================================ #并发编程中实现内存可见的两种方法比较:加锁和volatile变量 --- 1. volatile变量是一种稍弱的同步机制在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比synchronized关键字更轻量级的同步机制。 2. 从内存可见性的角度看,写入volatile变量相当于退出同步代码块,而读取volatile变量相当于进入同步代码块。 3. 在代码中如果过度依赖volatile变量来控制状态的可见性,通常会比使用锁的代码更脆弱,也更难以理解。仅当volatile变量能简化代码的实现以及对同步策略的验证时,才应该使用它。一般来说,用同步机制会更安全些。 4. 加锁机制(即同步机制)既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性,原因是声明为volatile的简单变量如果当前值与该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:“count++”、“count = count+1”。 当且仅当满足以下所有条件时,才应该使用volatile变量: 1. 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。 2. 该变量没有包含在具有其他变量的不变式中。 总结:在需要同步的时候,第一选择应该是synchronized关键字,这是最安全的方式,尝试其他任何方式都是有风险的。尤其在、jdK1.5之后,对synchronized同步机制做了很多优化,如:自适应的自旋锁、锁粗化、锁消除、轻量级锁等,使得它的性能明显有了很大的提升。 ================================================ FILE: Part2/JavaConcurrent/死锁.md ================================================ #死锁 --- 当线程需要同时持有多个锁时,有可能产生死锁。考虑如下情形: 线程A当前持有互斥所锁lock1,线程B当前持有互斥锁lock2。接下来,当线程A仍然持有lock1时,它试图获取lock2,因为线程B正持有lock2,因此线程A会阻塞等待线程B对lock2的释放。如果此时线程B在持有lock2的时候,也在试图获取lock1,因为线程A正持有lock1,因此线程B会阻塞等待A对lock1的释放。二者都在等待对方所持有锁的释放,而二者却又都没释放自己所持有的锁,这时二者便会一直阻塞下去。这种情形称为死锁。 下面给出一个两个线程间产生死锁的示例,如下: ``` public class Deadlock { private String objID; public Deadlock(String id) { objID = id; } public synchronized void checkOther(Deadlock other) { print("entering checkOther()"); try { Thread.sleep(2000); } catch ( InterruptedException x ) { } print("in checkOther() - about to " + "invoke 'other.action()'"); //调用other对象的action方法,由于该方法是同步方法,因此会试图获取other对象的对象锁 other.action(); print("leaving checkOther()"); } public synchronized void action() { print("entering action()"); try { Thread.sleep(500); } catch ( InterruptedException x ) { } print("leaving action()"); } public void print(String msg) { threadPrint("objID=" + objID + " - " + msg); } public static void threadPrint(String msg) { String threadName = Thread.currentThread().getName(); System.out.println(threadName + ": " + msg); } public static void main(String[] args) { final Deadlock obj1 = new Deadlock("obj1"); final Deadlock obj2 = new Deadlock("obj2"); Runnable runA = new Runnable() { public void run() { obj1.checkOther(obj2); } }; Thread threadA = new Thread(runA, "threadA"); threadA.start(); try { Thread.sleep(200); } catch ( InterruptedException x ) { } Runnable runB = new Runnable() { public void run() { obj2.checkOther(obj1); } }; Thread threadB = new Thread(runB, "threadB"); threadB.start(); try { Thread.sleep(5000); } catch ( InterruptedException x ) { } threadPrint("finished sleeping"); threadPrint("about to interrupt() threadA"); threadA.interrupt(); try { Thread.sleep(1000); } catch ( InterruptedException x ) { } threadPrint("about to interrupt() threadB"); threadB.interrupt(); try { Thread.sleep(1000); } catch ( InterruptedException x ) { } threadPrint("did that break the deadlock?"); } } ``` 运行结果: ``` threadA: objID=obj1 - entering checkOther() threadB: objID=obj2 - entering checkOther() threadA: objID=obj1 - in checkOther() - about to invoke 'other.action()' threadB: objID=obj2 - in checkOther() - about to invoke 'other.action()' main: finished sleeping main: about to interrupt() threadA main: about to interrupt() threadB main: did that break the deadlock? ``` 从结果中可以看出,在执行到other.action()时,由于两个线程都在试图获取对方的锁,但对方都没有释放自己的锁,因而便产生了死锁,在主线程中试图中断两个线程,但都无果。 大部分代码并不容易产生死锁,死锁可能在代码中隐藏相当长的时间,等待不常见的条件地发生,但即使是很小的概率,一旦发生,便可能造成毁灭性的破坏。避免死锁是一件困难的事,遵循以下原则有助于规避死锁: 1. 只在必要的最短时间内持有锁,考虑使用同步语句块代替整个同步方法; 2. 尽量编写不在同一时刻需要持有多个锁的代码,如果不可避免,则确保线程持有第二个锁的时间尽量短暂; 3. 创建和使用一个大锁来代替若干小锁,并把这个锁用于互斥,而不是用作单个对象的对象级别锁; ================================================ FILE: Part2/JavaConcurrent/生产者和消费者问题.md ================================================ #生产者和消费者问题 ``` package 生产者消费者; public class ProducerConsumerTest { public static void main(String[] args) { PublicResource resource = new PublicResource(); new Thread(new ProducerThread(resource)).start(); new Thread(new ConsumerThread(resource)).start(); new Thread(new ProducerThread(resource)).start(); new Thread(new ConsumerThread(resource)).start(); new Thread(new ProducerThread(resource)).start(); new Thread(new ConsumerThread(resource)).start(); } } ``` ``` package 生产者消费者; /** * 生产者线程,负责生产公共资源 * @author dream * */ public class ProducerThread implements Runnable{ private PublicResource resource; public ProducerThread(PublicResource resource) { this.resource = resource; } @Override public void run() { while (true) { try { Thread.sleep((long) (Math.random() * 1000)); } catch (InterruptedException e) { e.printStackTrace(); } resource.increase(); } } } ``` ``` package 生产者消费者; /** * 消费者线程,负责消费公共资源 * @author dream * */ public class ConsumerThread implements Runnable{ private PublicResource resource; public ConsumerThread(PublicResource resource) { this.resource = resource; } @Override public void run() { while (true) { try { Thread.sleep((long) (Math.random() * 1000)); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } resource.decrease(); } } } ``` ``` package 生产者消费者; /** * 公共资源类 * @author dream * */ public class PublicResource { private int number = 0; private int size = 10; /** * 增加公共资源 */ public synchronized void increase() { while (number >= size) { try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } number++; System.out.println("生产了1个,总共有" + number); notifyAll(); } /** * 减少公共资源 */ public synchronized void decrease() { while (number <= 0) { try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } number--; System.out.println("消费了1个,总共有" + number); notifyAll(); } } ``` ================================================ FILE: Part2/JavaConcurrent/线程中断.md ================================================ #线程中断 --- ##使用interrupt()中断线程 当一个线程运行时,另一个线程可以调用对应的Thread对象的interrupt()方法来中断它,该方法只是在目标线程中设置一个标志,表示它已经被中断,并立即返回。这里需要注意的是,如果只是单纯的调用interrupt()方法,线程并没有实际被中断,会继续往下执行。 演示休眠线程的中断 ``` public class SleepInterrupt extends Object implements Runnable{ @Override public void run() { try { System.out.println("in run() - about to sleep for 20 seconds"); Thread.sleep(20000); System.out.println("in run() - woke up"); } catch (InterruptedException e) { System.out.println("in run() - interrupted while sleeping"); //处理完中断异常后,返回到run()方法入口 //如果没有return,线程不会实际被中断,它会继续打印下面的信息 return; } System.out.println("in run() - leaving normally"); } public static void main(String[] args) { SleepInterrupt si = new SleepInterrupt(); Thread t = new Thread(si); t.start(); //住线程休眠2秒,从而确保刚才启动的线程有机会执行一段时间 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("in main() - interrupting other thread"); //中断线程t t.interrupt(); System.out.println("in main() - leaving"); } } ``` 运行结果如下: ``` in run() - about to sleep for 20 seconds in main() - interrupting other thread in main() - leaving in run() - interrupted while sleeping ``` 主线程启动新线程后,自身休眠2秒钟,允许新线程获得运行时间。新线程打印信息“about to sleep for 20 seconds”后,继而休眠20秒钟,大约2秒钟后,main线程通知新线程中断,那么新线程的20秒的休眠将被打断,从而抛出InterruptException异常,执行跳转到catch块,打印出“interrupted while sleeping”信息,并立即从run()方法返回,然后消亡,而不会打印出catch块后面的“leaving normally”信息。 请注意:由于不确定的线程规划,上图运行结果的后两行可能顺序相反,这取决于主线程和新线程哪个先消亡。但前两行信息的顺序必定如上图所示。 另外,如果将catch块中的return语句注释掉,则线程在抛出异常后,会继续往下执行,而不会被中断,从而会打印出”leaving normally“信息。 ##待决中断 --- 在上面的例子中,sleep()方法的实现检查到休眠线程被中断,它会相当友好地终止线程,并抛出InterruptedException异常。另外一种情况,如果线程在调用sleep()方法前被中断,那么该中断称为待决中断,它会在刚调用sleep()方法时,立即抛出InterruptedException异常。 ``` public class PendingInterrupt extends Object{ public static void main(String[] args) { //如果输入了参数,则在main线程中中断当前线程(即main线程) if(args.length > 0){ Thread.currentThread().interrupt(); } //获取当前时间 long startTime = System.currentTimeMillis(); try { Thread.sleep(2000); System.out.println("was NOT interrupted"); } catch (InterruptedException e) { System.out.println("was interrupted"); } //计算中间代码执行的时间 System.out.println("elapsedTime=" + (System.currentTimeMillis() - startTime)); } } ``` 如果PendingInterrupt不带任何命令行参数,那么线程不会被中断,最终输出的时间差距应该在2000附近(具体时间由系统决定,不精确),如果PendingInterrupt带有命令行参数,则调用中断当前线程的代码,但main线程仍然运行,最终输出的时间差距应该远小于2000,因为线程尚未休眠,便被中断,因此,一旦调用sleep()方法,会立即打印出catch块中的信息。执行结果如下: ``` was NOT interrupted elapsedTime=2001 ``` 这种模式下,main线程中断它自身。除了将中断标志(它是Thread的内部标志)设置为true外,没有其他任何影响。线程被中断了,但main线程仍然运行,main线程继续监视实时时钟,并进入try块,一旦调用sleep()方法,它就会注意到待决中断的存在,并抛出InterruptException。于是执行跳转到catch块,并打印出线程被中断的信息。最后,计算并打印出时间差。 ##使用isInterrupted()方法判断中断状态 --- 可以在Thread对象上调用isInterrupted()方法来检查任何线程的中断状态。这里需要注意:线程一旦被中断,isInterrupted()方法便会返回true,而一旦sleep()方法抛出异常,它将清空中断标志,此时isInterrupted()方法将返回false。 下面的代码演示了isInterrupted()方法的使用: ``` public class InterruptCheck extends Object{ public static void main(String[] args) { Thread t = Thread.currentThread(); System.out.println("Point A: t.isInterrupted()=" + t.isInterrupted()); //待决中断,中断自身 t.interrupt(); System.out.println("Point B: t.isInterrupted()=" + t.isInterrupted()); System.out.println("Point C: t.isInterrupted()=" + t.isInterrupted()); try { Thread.sleep(2000); System.out.println("was NOT interrupted"); } catch (InterruptedException e) { System.out.println("was interrupted"); } //跑出异常后,会清除中断标志,这里会返回false System.out.println("Point D: t.isInterrupted()=" + t.isInterrupted()); } } ``` 运行结果如下: ``` Point A: t.isInterrupted()=false Point B: t.isInterrupted()=true Point C: t.isInterrupted()=true was interrupted Point D: t.isInterrupted()=false ``` ##使用Thread.interrupted()方法判断中断状态 --- 可以使用Thread.interrupted()方法来检查当前线程的中断状态(并隐式重置为false)。又由于它是静态方法,因此不能在特定的线程上使用,而只能报告调用它的线程的中断状态,如果线程被中断,而且中断状态尚不清楚,那么,这个方法返回true。与isInterrupted()不同,它将自动重置中断状态为false,第二次调用Thread.interrupted()方法,总是返回false,除非中断了线程。 如下代码演示了Thread.interrupted()方法的使用: ``` public class InterruptReset extends Object{ public static void main(String[] args) { System.out.println( "Point X: Thread.interrupted()=" + Thread.interrupted()); Thread.currentThread().interrupt(); System.out.println( "Point Y: Thread.interrupted()=" + Thread.interrupted()); System.out.println( "Point Z: Thread.interrupted()=" + Thread.interrupted()); } } ``` 运行结果 ``` Point X: Thread.interrupted()=false Point Y: Thread.interrupted()=true Point Z: Thread.interrupted()=false ``` 从结果中可以看出,当前线程中断自身后,在Y点,中断状态为true,并由Thread.interrupted()自动重置为false,那么下次调用该方法得到的结果便是false。 ##补充 --- yield和join方法的使用 * join方法用线程对象调用,如果在一个线程A中调用另一个线程B的join方法,线程A将会等待线程B执行完毕后再执行。 * yield可以直接用Thread类调用,yield让出CPU执行权给同等级的线程,如果没有相同级别的线程在等待CPU的执行权,则该线程继续执行。 ================================================ FILE: Part2/JavaConcurrent/线程挂起、恢复与终止的正确方法.md ================================================ #线程挂起、恢复与终止的正确方法(含代码) --- ##挂起和恢复线程 Thread 的API中包含两个被淘汰的方法,它们用于临时挂起和重启某个线程,这些方法已经被淘汰,因为它们是不安全的,不稳定的。如果在不合适的时候挂起线程(比如,锁定共享资源时),此时便可能会发生死锁条件——其他线程在等待该线程释放锁,但该线程却被挂起了,便会发生死锁。另外,在长时间计算期间挂起线程也可能导致问题。 下面的代码演示了通过休眠来延缓运行,模拟长时间运行的情况,使线程更可能在不适当的时候被挂起: ``` ``` ================================================ FILE: Part2/JavaSE/ArrayList 、 LinkedList 、 Vector 的底层实现和区别.md ================================================ #Java基础之集合List-ArrayList、LinkedList、Vector的底层实现和区别 1. ArrayList底层实际是采用数组实现的(并且该数组的类型是Object类型的) 2. 如果jdk6,采用Array.copyOf()方法来生成一个新的数组,如果是jdk5,采用的是System.arraycopy()方法(当添加的数据量大于数组的长度的时候) 3. List list = new ArrayList()时,底层会生成一个长度为10的数组来存放对象 4. ArrayList、Vector底部都是采用数组实现的 5. 对于ArrayList,方法都不是同步的,对于Vector,大部分public方法都是同步的 6. LinkedList采用双向循环列表 7. 对于ArrayList,查询速度很快,增加和删除(非最后一个节点)操作非常慢(本质上由数组的特性决定的) 8. 对于LinkedList,查询速度非常慢,增加和删除操作非常快(本质上是由双向循环列表决定的) 参考博客: [http://blog.csdn.net/sundenskyqq/article/details/27630179](http://blog.csdn.net/sundenskyqq/article/details/27630179) ================================================ FILE: Part2/JavaSE/ArrayList源码剖析.md ================================================ ##ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。 ArrayList不是线程安全的,只能在单线程环境下,多线程环境下可以考虑用collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。 ArrayList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问,实现了Cloneable接口,能被克隆。 ArrayList源码剖析 ArrayList的源码如下(加入了比较详细的注释): ``` package java.util; public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable { // 序列版本号 private static final long serialVersionUID = 8683452581122892189L; // ArrayList基于该数组实现,用该数组保存数据 private transient Object[] elementData; // ArrayList中实际数据的数量 private int size; // ArrayList带容量大小的构造函数。 public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); // 新建一个数组 this.elementData = new Object[initialCapacity]; } // ArrayList无参构造函数。默认容量是10。 public ArrayList() { this(10); } // 创建一个包含collection的ArrayList public ArrayList(Collection c) { elementData = c.toArray(); size = elementData.length; if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } // 将当前容量值设为实际元素个数 public void trimToSize() { modCount++; int oldCapacity = elementData.length; if (size < oldCapacity) { elementData = Arrays.copyOf(elementData, size); } } // 确定ArrarList的容量。 // 若ArrayList的容量不足以容纳当前的全部元素,设置 新的容量=“(原始容量x3)/2 + 1” public void ensureCapacity(int minCapacity) { // 将“修改统计数”+1,该变量主要是用来实现fail-fast机制的 modCount++; int oldCapacity = elementData.length; // 若当前容量不足以容纳当前的元素个数,设置 新的容量=“(原始容量x3)/2 + 1” if (minCapacity > oldCapacity) { Object oldData[] = elementData; int newCapacity = (oldCapacity * 3)/2 + 1; //如果还不够,则直接将minCapacity设置为当前容量 if (newCapacity < minCapacity) newCapacity = minCapacity; elementData = Arrays.copyOf(elementData, newCapacity); } } // 添加元素e public boolean add(E e) { // 确定ArrayList的容量大小 ensureCapacity(size + 1); // Increments modCount!! // 添加e到ArrayList中 elementData[size++] = e; return true; } // 返回ArrayList的实际大小 public int size() { return size; } // ArrayList是否包含Object(o) public boolean contains(Object o) { return indexOf(o) >= 0; } //返回ArrayList是否为空 public boolean isEmpty() { return size == 0; } // 正向查找,返回元素的索引值 public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1; } // 反向查找,返回元素的索引值 public int lastIndexOf(Object o) { if (o == null) { for (int i = size-1; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = size-1; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1; } // 反向查找(从数组末尾向开始查找),返回元素(o)的索引值 public int lastIndexOf(Object o) { if (o == null) { for (int i = size-1; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = size-1; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1; } // 返回ArrayList的Object数组 public Object[] toArray() { return Arrays.copyOf(elementData, size); } // 返回ArrayList元素组成的数组 public T[] toArray(T[] a) { // 若数组a的大小 < ArrayList的元素个数; // 则新建一个T[]数组,数组大小是“ArrayList的元素个数”,并将“ArrayList”全部拷贝到新数组中 if (a.length < size) return (T[]) Arrays.copyOf(elementData, size, a.getClass()); // 若数组a的大小 >= ArrayList的元素个数; // 则将ArrayList的全部元素都拷贝到数组a中。 System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; } // 获取index位置的元素值 public E get(int index) { RangeCheck(index); return (E) elementData[index]; } // 设置index位置的值为element public E set(int index, E element) { RangeCheck(index); E oldValue = (E) elementData[index]; elementData[index] = element; return oldValue; } // 将e添加到ArrayList中 public boolean add(E e) { ensureCapacity(size + 1); // Increments modCount!! elementData[size++] = e; return true; } // 将e添加到ArrayList的指定位置 public void add(int index, E element) { if (index > size || index < 0) throw new IndexOutOfBoundsException( "Index: "+index+", Size: "+size); ensureCapacity(size+1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } // 删除ArrayList指定位置的元素 public E remove(int index) { RangeCheck(index); modCount++; E oldValue = (E) elementData[index]; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // Let gc do its work return oldValue; } // 删除ArrayList的指定元素 public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } // 快速删除第index个元素 private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; // 从"index+1"开始,用后面的元素替换前面的元素。 if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); // 将最后一个元素设为null elementData[--size] = null; // Let gc do its work } // 删除元素 public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { // 便利ArrayList,找到“元素o”,则删除,并返回true。 for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } // 清空ArrayList,将全部的元素设为null public void clear() { modCount++; for (int i = 0; i < size; i++) elementData[i] = null; size = 0; } // 将集合c追加到ArrayList中 public boolean addAll(Collection c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacity(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; } // 从index位置开始,将集合c添加到ArrayList public boolean addAll(int index, Collection c) { if (index > size || index < 0) throw new IndexOutOfBoundsException( "Index: " + index + ", Size: " + size); Object[] a = c.toArray(); int numNew = a.length; ensureCapacity(size + numNew); // Increments modCount int numMoved = size - index; if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0; } // 删除fromIndex到toIndex之间的全部元素。 protected void removeRange(int fromIndex, int toIndex) { modCount++; int numMoved = size - toIndex; System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved); // Let gc do its work int newSize = size - (toIndex-fromIndex); while (size != newSize) elementData[--size] = null; } private void RangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException( "Index: "+index+", Size: "+size); } // 克隆函数 public Object clone() { try { ArrayList v = (ArrayList) super.clone(); // 将当前ArrayList的全部元素拷贝到v中 v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(); } } // java.io.Serializable的写入函数 // 将ArrayList的“容量,所有的元素值”都写入到输出流中 private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // Write out element count, and any hidden stuff int expectedModCount = modCount; s.defaultWriteObject(); // 写入“数组的容量” s.writeInt(elementData.length); // 写入“数组的每一个元素” for (int i=0; i