小明经过一年的努力学习终于成为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的权限如下:
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 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 getPersons(InputStream inStream) throws Throwable{
List 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();
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;
}
}