小明经过一年的努力学习终于成为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; } }