小明经过一年的努力学习终于成为Android中级工程师了，月薪变成了17k。到了中级工程师，已经可以在公司里干很多体力活了，但是一些很重要的任务小明还不能一个人承担起来，这个时候小明需要学习的内容就很多了，如下所示：
- AIDL：熟悉AIDL，理解其工作原理，懂transact和onTransact的区别；
- Binder：从Java层大概理解Binder的工作原理，懂Parcel对象的使用；
- 多进程：熟练掌握多进程的运行机制，懂Messenger、Socket等；
- 事件分发：弹性滑动、滑动冲突等；
- 玩转View：View的绘制原理、各种自定义View；
- 动画系列：熟悉View动画和属性动画的不同点，懂属性动画的工作原理；
- 懂性能优化、熟悉mat等工具
- 懂点常见的设计模式
学习方法
阅读进阶书籍，阅读Android源码，阅读官方文档并尝试自己写相关的技术文章，需要有一定技术深度和自我思考。在这个阶段的学习过程中，有2个点是比较困扰大家的，一个是阅读源码，另一个是自定义View以及滑动冲突。
如何阅读源码呢？这是个头疼的问题，但是源码必须要读。阅读源码的时候不要深入代码细节不可自拔，要关注代码的流程并尽量挖掘出对应用层开发有用的结论。另外仔细阅读源码中对一个类或者方法的注释，在看不懂源码时，源码中的注释可以帮你更好地了解源码中的工作原理，这个过程虽然艰苦，但是别无他法。
如何玩转自定义View呢？我的建议是不要通过学习自定义view而学习自定义view。为什么这么说呢？因为自定义view的种类太多了，各式各样的绚丽的自定义效果，如何学的玩呢！我们要透过现象看本质，更多地去关注自定义view所需的知识点，这里做如下总结：
- 搞懂view的滑动原理
- 搞懂如何实现弹性滑动
- 搞懂view的滑动冲突
- 搞懂view的measure、layout和draw
- 然后再学习几个已有的自定义view的例子
- 最后就可以搞定自定义view了，所谓万变不离其宗
大概再需要1-2年时间，即可达到高级工程师的技术水平。我个人认为通过《Android开发艺术探索》和《Android群英传》可以缩短这个过程为0.5-1年。注意，达到高级工程师的技术水平不代表就可以立刻成为高级工程师（受机遇、是否跳槽的影响），但是技术达到了，成为高级工程师只是很简单的事。
技术要求：
- 稍微深入的知识点
AIDL、Messenger、Binder、多进程、动画、滑动冲突、自定义View、消息队列等
- 书籍推荐
《Android开发艺术探索》、《Android群英传》
高级工程师
小明成为了梦寐以求的高级工程师，月薪达到了20k，还拿到了一丢丢股票。这个时候小明的Android水平已经不错了，但是小明的目标是资深工程师，小明听说资深工程师月薪可以达到30k+。
为了成为Android资深工程师，需要学习的东西就更多了，并且有些并不是那么具体了，如下所示：
- 继续加深理解”稍微深入的知识点“中所定义的内容
- 了解系统核心机制：
1. 了解SystemServer的启动过程
2. 了解主线程的消息循环模型
3. 了解AMS和PMS的工作原理
4. 能够回答问题”一个应用存在多少个Window？“
5. 了解四大组件的大概工作流程
6. …
- 基本知识点的细节
1. Activity的启动模式以及异常情况下不同Activity的表现
2. Service的onBind和onReBind的关联
3. onServiceDisconnected(ComponentName className)和binderDied的区别
4. AsyncTask在不同版本上的表现细节
5. 线程池的细节和参数配置
6. …
- 熟悉设计模式，有架构意识
学习方法
这个时候已经没有太具体的学习方法了，无非就是看书、看源码和做项目，平时多种总结，尽量将知识融会贯通从而形成一种体系化的感觉。同时这个阶段对架构是有一定要求的，架构是抽象的，但是设计模式是具体的，所以一定要加强下设计模式的学习。关于设计模式的学习，最近一本新书推荐给大家《Android 源码设计模式解析与实战》，既可以学习设计模式，又可能体会到Android源码中的设计思想，我最近也在阅读此书。
- 系统核心机制
- 基本知识点的细节
- 设计模式和架构
- 书籍推荐
《Android开发艺术探索》、《Android 源码设计模式解析与实战》、《Android内核剖析》



没有类的成员变量，可以把该类的方法写成static
ProgressBar可以在子线程里面更新UI


将某一个变量从局部变量移为成员变量快捷键
不会被继承所以要在类的前面加上final

得到屏幕宽和高：
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int screenWidth = displayMetrics.widthPixels;
int screenHeight = displayMetrics.heightPixels;






一、下载图片
二、解析xml
三、冒择入希快归堆
四、设计模式
五、数据库分页
文件流
八、多线程
页面上现有ProgressBar控件progressBar，请用书写线程以10秒的的时间完成其进度显示工作。

public class ProgressBarStu extends Activity {

	private ProgressBar progressBar = null;
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.progressbar);
		//从这到下是关键
		progressBar = (ProgressBar)findViewById(R.id.progressBar);
		
		Thread thread = new Thread(new Runnable() {
			
			@Override
			public void run() {
				int progressBarMax = progressBar.getMax();
				try {
					while(progressBarMax!=progressBar.getProgress())
					{
						
						int stepProgress = progressBarMax/10;
						int currentprogress = progressBar.getProgress();
						progressBar.setProgress(currentprogress+stepProgress);
						Thread.sleep(1000);
					}
					
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			}
		});
		
		thread.start();

		//关键结束
	}
	
}

九、Activity启动模式和Intent有关Activity启动的方式

十、数据库
1。 创建一个版本为1的“diaryOpenHelper.db”的数据库，
2. 同时创建一个 “diary” 表（包含一个_id主键并自增长，topic字符型100长度， content字符型1000长度）
3. 在数据库版本变化时请删除diary表，并重新创建出diary表。

public class DBHelper  extends SQLiteOpenHelper {

	public final static String DATABASENAME = "diaryOpenHelper.db";
	public final static int DATABASEVERSION = 1;
	
	String mCreateSQL ="create table diary"+
					"("+
					"_id integer primary key autoincrement,"+
					"topic varchar(100),"+
					"content varchar(1000)"+
					")";
		db.execSQL(sql);

	//创建数据库
	public DBHelper(Context context,String name,CursorFactory factory,int version)
	{
		super(context, name, factory, version);
	}
	//创建表等机构性文件
	public void onCreate(SQLiteDatabase db)
	{
		db.execSQL(mCreateSQL);
	}
	//若数据库版本有更新，则调用此方法
	public void onUpgrade(SQLiteDatabase db,int oldVersion,int newVersion)
	{
		
		String sql = "drop table if exists diary";
		db.execSQL(sql);
		db.execSQL(mCreateSQL);
	}
}


十一、内存泄露情况
1.数据库的cursor没有关闭
2.构造adapter时没有使用缓存contentview
3.Bitmap对象不使用时采用recycle（）释放内存
4.activity中的对象的生命周期大于activity
总结：保存了不可能再被访问的变量类型的引用


十三、Message, Handler, Message Queue, Looper之间的关系
Andriod提供了Handler和Looper来满足线程间的通信.Handler先进先出原则.Looper类用来管理特定线程内对象之间的消息交换(Message Exchange).
1)Looper:
一个线程可以产生一个Looper对象,由它来管理此线程里的MessageQueue(消息队列).
2)Handler:
你可以构造Handler对象来与Looper沟通,以便push新消息到MessageQueue里;或者接收Looper从Message Queue取出所送来的消息.
android中线程与线程，进程与进程之间如何通信。
线程通信使用Handler


十四、系统上安装了多种浏览器，能否指定某浏览器访问指定页面？
在action赋值为android.intent.action.VIEW“时可接收如下scheme为"http"等等类型的data。所以突发奇想，启动该程序后，指定action及Uri，即访问指定网页

十五、SIM卡的文件系统有自己规范,主要是为了和手机通讯,SIM卡本身可以有自己的操作系统，EF就是作存储并和手机通讯用的。

十六、判断手机是否有SD卡
在程序中访问SDCard，你需要申请访问SDCard的权限。在AndroidManifest.xml中加入访问SDCard的权限如下:
<!--在SDCard中创建与删除文件权限-->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!--往SDCard写入数据权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)
Environment.getExternalStorageState()方法用于获取SDCard的状态，如果手机装有SDCard，并且可以进行读写，那么方法返回的状态等于Environment.MEDIA_MOUNTED

十七、堆和栈
十八、传递数据的方式

十九、Android UI中的view刷新：多线程和双缓冲
二十、算法
二十一、排序
二十二、Splash
二十三、View Page
二十四、ViewSwitcher

监测网络状况变化
IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
		mNetReceiver = new NetReceiver();
		registerReceiver(mNetReceiver, intentFilter);

重启应用
Intent i = getBaseContext().getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName());
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);

版本升级只有code变的时候才会出现
先static{}再执行构造函数

JAVA反射机制是在运行状态中，对于任意一个类，都能够知道这个类的所有属性和方法；对于任意一个对象，都能够调用它的任意一个方法和属性；这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

属性动画
ObjectAnimator.ofFloat(ll_article_detail_bottom, "translationY", toolBarBotom, toolBarBotom + toolBarHeight).setDuration(1000).start();

代替webview
https://github.com/shivasurya/ChromeCustomTabs
https://github.com/4k3R/chrome-custom-tabs
https://github.com/WeRockStar/Android-Chrome-Custom-Tabs

不要定时去请求，请求成功之后再请求

1个字节是8位
只有8种基本类型可以算.其他引用类型都是由java虚拟机决定的自己不能操作
byte 1字节
short 2字节
int 4字节
long 8字节
float 4字节
double 8字节
char 2字节
boolean 1字节


volley网络框架

为什么使用service：
http://www.tuicool.com/articles/Yn2YR3

安卓进程间通信：
http://www.jb51.net/article/37797.htm


Java ->使用 lambda 巩固 性能优化 线程 socket
不同颜色柱状图：
https://bitbucket.org/danielnadeau/holographlibrary/overview 

复制对象（深浅）
弱引用

更改线程中的flag，由false变为true的时候重新执行
两种办法
1.线程终止之后,想重新运行,重新new,然后start
2.变量不要放在while(_flag)里,而是
销毁时外层设为false，暂停只改变里层的
while(_Stop)
{
if(_pause)
{
Thread.Sleep(1000);
}
else
{
//dosomething
}
}

先findid，再setListener，因为初始化listener的时候，case里面的会执行
ndk
contentprovider
自定义控件
算法
设计模式
网络、握手
jni
动画相关
vpn
aidl
widget
surfaceview 视频播放（多媒体）
多线程断点下载
底层源码
openGL
ImageLoader
安卓空格用\u3000
looper、handler
webview

按照这种方式启动的应用杀不死： system(am start -n 包名)
kill pid才能杀死

这样获取sd卡路径：
public static final String EXCEL_DIRECTORY = Environment.getExternalStorageDirectory().getPath() + "/questionnaire/paper/";
不要getAbsolutePath()

自己写一个ps命令，覆盖原有命令，不显示自己应用的pid，那么其他应用无法杀掉

// 复制内容
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("password", password);
clipboard.setPrimaryClip(clip);

获取资源Id：
int resId = getResources().getIdentifier(type, "drawable" , getPackageName());
aidl

messenger

socket

ndk

contentprovider

通知+顽固service
widget
会动的启动图标
换皮肤
EventBus

自定义EmptyView
View emptyView = LayoutInflater.from(getActivity()).inflate(R.layout.reload_layout, null);  
((ViewGroup)mListView.getParent()).addView(emptyView);    
mListView.setEmptyView(emptyView); 


/**
 * 采用Pull解析XML内容
 */
public class PULLPersonService {
	
	/**
	 * 使用pull技术生成xml文件
	 * @param persons
	 * @param writer
	 * @throws Throwable
	 */
	public static void save(List<Person> persons, Writer writer) throws Throwable{
		XmlSerializer serializer = Xml.newSerializer();
		serializer.setOutput(writer);
		serializer.startDocument("UTF-8", true);
		
		serializer.startTag(null, "persons");
		for(Person person : persons){
			serializer.startTag(null, "person");
			serializer.attribute(null, "id", person.getId().toString());
			
			serializer.startTag(null, "name");
			serializer.text(person.getName());
			serializer.endTag(null, "name");
			
			serializer.startTag(null, "age");
			serializer.text(person.getAge().toString());
			serializer.endTag(null, "age");
			
			serializer.endTag(null, "person");
		}
		serializer.endTag(null, "persons");		
		serializer.endDocument();
		writer.flush();
		writer.close();
	}

	/**
	 * 使用pull技术解析xml
	 * @param inStream
	 * @return
	 * @throws Throwable
	 */
	public static List<Person> getPersons(InputStream inStream) throws Throwable{
		List<Person> persons = null;
		Person person = null;
		XmlPullParser parser = Xml.newPullParser();
		parser.setInput(inStream, "UTF-8");
		int eventType = parser.getEventType();//产生第一个事件
		while(eventType!=XmlPullParser.END_DOCUMENT){//只要不是文档结束事件
			switch (eventType) {
			case XmlPullParser.START_DOCUMENT:
				persons = new ArrayList<Person>();
				break;
	
			case XmlPullParser.START_TAG:
				String name = parser.getName();//获取解析器当前指向的元素的名称
				if("person".equals(name)){
					person = new Person();
					person.setId(new Integer(parser.getAttributeValue(0)));
				}
				if(person!=null){
					if("name".equals(name)){
						person.setName(parser.nextText());//获取解析器当前指向元素的下一个文本节点的值
					}
					if("age".equals(name)){
						person.setAge(new Short(parser.nextText()));
					}
				}
				break;
			case XmlPullParser.END_TAG:
				if("person".equals(parser.getName())){
					persons.add(person);
					person = null;
				}
				break;
			}
			eventType = parser.next();
		}
		return persons;
	}
}