一、Android的构成基石——四大组件
1.Activity
2.Service与AIDL
3.Broadcast
4.ContentProvider
二、创造出丰富多彩的UI——View与动画
1.重要的View控件
2.必须掌握的最重要的技能——自定义控件
最为自由的一种实现——自定义View:继承自View创建自定义控件;如有需要自定义View属性,也就是在valus/attrs.xml中定义属性集;在xml中引入命名控件,设置属性;在代码中读取xml中的属性,初始化视图;测量视图的大小;绘制视图内容
View的尺寸测量:EXACTLY,AT_MOST,UNSPECIFIED
Canvas与Paint
自定义ViewGroup
3.Scroller的使用
Scroller是一个帮助View滚动的辅助类。
4.让应用更精彩——动画
Frame动画:一系列图片构成,然后设置给某个View
补间动画:操作某个控件让其展现出旋转、渐变、移动、缩放的一种转换过程。常用属性:alpha,scale,translate,rotate
属性动画:ValueAnimator、ObjectAnimator、AnimatorSet。动画执行时间——TypeEvaluator与TimeInterpolator
三、保证App流畅的关键因素——多线程
1.Android中的消息机制
处理消息的手段——Handler、Looper、与MessageQueue
2.Android中的多线程
多线程的实现——Thread和Runnable
线程的wait、sleep、join和yield
与多线程相关的方法——Callable、Future和FutureTask:Callable与Runnable的功能大致相似,不同的是Callable是一个泛型接口;Runnable与Callable都像“脱缰的野马“,一旦解开缰绳就无法控制。Future就是这类“战马”的标准,Future为线程池制定了可管理的任务标准,它提供了cancel/isDone/get/set函数。Future只是定义了一些规范的接口,FutureTask是Future的实现类,FutureTask会像Thread包装Runnable那样对Runnable和Callable<V>进行包装,Runnable与Callable由构造函数注入。
构建服务器应用程序的有效方法——线程池:ThreadPoolExecutor、ScheduledThreadPoolExecutor
同步集合:CopyOnWriteArrayList、ConcurrentHashMap、BlockingQueue
同步锁:Synchronized、ReentrantLock与Condition、Semaphore、CyclicBarrier、CountDownLatch
AsyncTask的原理:
四、HTTP网络请求
1.HTTP网络请求原理
2.Android中执行网络请求
HttpClient、HttpURLConnection
3.网络框架的设计与实现
五、SQLite数据库
1.SQLite3的基本介绍
2.SQLite中折SQL语句
3.Android中的数据库开发
4.数据库框架ActiveAndroid的使用与基本原理
六、让程序更优秀的技术——性能优化
1.布局优化
引入include,merge的使用,ViewStub视图:viewStub.inflate();,减少布局层级:多使用RelativeLayout,少用AbsoluteLayout;2、在列表中不使用LinearLayout的weight属性;3、用ViewStub加载不常用的布局
2.内存优化
不要使用太多Service、UI不可见或内存紧张时释放内存、检查应该使用多少内存、注意bitmaps、使用优化的数据容器、注意内存开销、注意代码抽象、序列化的数据使用nano protobufs、避免使用依赖注入框架、注意外部库、优化整体性能、使用ProGuard和zipalign、使用多进程
3.内存泄漏
使用Memory Monitor监测内存使用情况
使用LeakCanary:
创建一个application子类:
public class MyApplication extends Application {
private static RefWatcher mRefWatcher;
@Override
public void onCreate() {
super.onCreate();
mRefWatcher = LeakCanary.install(this);
}
public static RefWatcher getRefWatcher() {
return mRefWatcher;
}
}
修改android:name=".MyApplication"
4.性能优化
过度绘制:Develpers Options --> Debug GPU Overdraw --> Show overdraw area
图形渲染:Hierarchy Viewer:tools---Android---Android Device Monitor查看视图层级;Tree View:节点性能分析,黄色、红色是需要优化的地方
TraceView分析每个函数执行时间
Debug.startMethodTracing("text_activity.trace");
Debug.stopMethodTracing();
七、装点程序“门面”——代码规范
1.排版
2.注释
3.命名
4.编码建议
八、让不断升级的系统更好管理——Git版本控制
1.Git起源
2.Git基本原理
3.Git基本配置
4.Git基本命令
5.项目协作——GitHub
九、开发人员必备的技能——单元测试
1.什么是单元测试
2.为什么要做单元测试
3.不写单元测试的借口
4.如何写单元测试
5.测试哪些内容:边界条件、覆盖执行路径
6.模拟所需的功能模块——Mock对象
手动Mock对象、使用Mockito库
7.Android中的单元测试
8.测试驱动开发(TDD)简介
十、六大原则与设计模式
1.面向对象六大原则:单一职责、里氏替换、依赖倒置、开闭、接口隔离、迪米特
2.设计模式
3.避免掉进过度设计的怪圈
4.反模式
十一、重构
1.为什么要重构
2.什么时候重构
3.常用的重构手法
提取子函数、上移函数到父类、下移函数到子类、封装固定的调用逻辑、使用泛型去除重复逻辑、使用对象避免过多的函数、转移函数、将类型码转为状态模式、NullObject、分解“胖”类型
十二、从码农历练成工程师——综合实战
1.项目需求
2.第一版实现
3.第一版存在的问题与重构
类型重命名、去除重复代码、简化复杂函数、明确职责与降低耦合
4.降低复杂性——MVP架构
Presenter交互中间人、View用户界面、Model数据的存取
首先要定义View角色,定义一个MvpView接口代表View角色的抽象定义,它有两个接口分别为加载数据时显示loading和加载完数据之后隐藏loading:
public interface MvpView{
public void onShowLoding();
public void onHideLoding();
}
然后是具体的View角色,对Activity、Fragment定义一个功能接口。例如,对于文章列表Fragment,定义一个ArticleListView,它的功能将从网络或数据库中获取到的文章列表显示到RecyclerView上,然后还有一个功能是第一次从网络上加载到数据 后会将从数据库加载 的缓存文章清除,因此,还需要一个清除功能:
public interface ArticleListView extends MvpView{
public void onFetchedArticles(List<Article> result);
public void clearCacheArticles();
}
然后ArticleListFragment要实现这个接口,在这些接口中实现相应的功能,即此时ArticleListFragment扮演的是这个MvpView角色:
public class ArticleListFragment extends Fragment implements onRefreshListener,onLoadListener,ArticleListView {
protected SwipeRefreshLayout mSwipeRefreshLayout;
protected AutoLoadRecyclerView mRecyclerView;
protected ArticleAdapter mAdapter;
private ArticleListPresenter mPresenter = new ArticleListPresenter();
public void onResume(){
super.onResume();
mPresenter.attach(this);
mPresenter.fetchLastestArticles();
}
public void onRefresh() {
mPresenter.fetchLastestArticles();
}
public void onLoad() {
mPresenter.loadNextPageArticles();
}
public void onFetchedArticles(List<Article> result) {
mAdapter.addItems(result);
}
public void clearCacheArticles() {
mAdapter.clear();
}
public void onShowLoding() {
mSwipeRefreshLayout.setRefreshing(true);
}
public void onHideLoding() {
mSwipeRefreshLayout.setRefreshing(false);
}
public void onDestroy() {
super.onDestroy();
mPresenter.detach();
}
}
从上述程序中可以看到,此时的Fragment很简单,只有一些初始化视图和对视图进行一些简单的代码,相关的业务逻辑都通过ArticleListPresenter来实现。这里又引入了MVP中的另一个核心元素Presenter。Presenter是View与Model交互的中间人,业务逻辑也包含在该角色中。这样就相当于Presenter要持有View对象,而我们的View对象往往是Activity、Fragment,当Activity退出时Presenter如果正在执行一个耗时的网络请求,那么将导致Activity的内存无法被释放而千万内存泄漏。因此,需要定义一个含有关联、取消关联View角色的Presenter基类:
public abstract class BasePresenter<T extends MvpView> {
T mView;
public void attach(T view) {
mView = view;
}
public void detach() {
mView = null;
}
}
其中T就是一个具体的MVP的View。
然后我们定义继承自BasePresenter的ArticleListPresenter类,将ArticleListFragment的业务逻辑转移到该类中:
public class ArticleListPresenter extends BasePresenter<ArticleListView> {
public static final int FIRST_PAGE = 1;
PRIVATE INT MPAGEINDEX = first_page;
ArticleParser mArticleParser = new ArticleParser();
private boolean isCacheLoaded = false;
public void fetchLastestArticles() {
if (!isCacheLoaded) {
mView.fetchedArticles(DatabaseHelper.getInstance().loadArticles());
}
fetchArticlesAsync(FIRST_PAGE);
}
private void fetchArticleAsync(final int page) {
mView.onShowLoding();
HttpFlinger.get(prepareRequestUrl(page),mArticleParse,new DataListener<List<Article>>() {
@Override
public void onComplete(List<Article> result) {
mView.onHideLoding();
if (!isCacheLoaded && result != null) { mView.clearCacheArticles(); isCacheLoaded = true;
}
if (result == null) { return; }
mView.onFetchedArticles(result);
DatabaseHelper.getInstance().saveArticles(result);
updatePageIndex(page,result);
}
});
}
//代码省略
}
从上述程序中可以看到网络请求、数据库操作等相关的业务逻辑都被隔离在Presenter中。这样一来,ArticleListFragment等View角色就只负责处理视图的显示、页面跳转等简单功能,避免了与业务逻辑耦合在Activity、Fragment等View角色中,导致难以维护和修改。而Presenter则负责具体的业务逻辑处理,从数据库、网络获取资源,并且对数据、操作进行一些逻辑处理。这个MVP实际上并没有真正意义上的Model,这是因为目前项目规模可以暂时忽略掉这部分的结构。当业务比较复杂时,可以将获取数据的操作移到一个独立的Model层,Model就是我们的数据源,而不是在Presenter中来控制这个逻辑,此时Model相当于你的数据中心。
5.开启单元测试之路——添加单元测试
public interface MvpView {
public void onShowLoding();
public void onHideLoding();
}
public interface ArticleListView extends MvpView {
public void onFetchedArticles(List<Article> result);
public void clearCacheArticles();
}
public interface ArticleDetailView extends MvpView {
public void onFetchedArticleContent(String html);
}
public abstract class BasePresenter<T extends MvpView> {
protected T mView;
public void attach(T view) {
mView = view;
}
public void detach() {
mView = null;
}
}
public class ArticleListPresenter extends BasePresenter<ArticleListView> {
public static final int FIRST_PAGE = 1;
private int mPageIndex = FIRST_PAGE;
ArticleParser mArticleParser = new ArticleParser();
private boolean isCacheLoaded = false;
/**
* 第一次先从数据库中加载缓存,然后再从网络上获取数据
*/
public void fetchLastestArticles() {
if (!isCacheLoaded) {
mView.onFetchedArticles(DatabaseHelper.getInstance().loadArticles());
}
// 从网络上获取最新的数据
fetchArticlesAsync(FIRST_PAGE);
}
private void fetchArticlesAsync(final int page) {
mView.onShowLoding();
HttpFlinger.get(prepareRequestUrl(page), mArticleParser, new DataListener<List<Article>>() {
@Override
public void onComplete(List<Article> result) {
mView.onHideLoding();
if (!isCacheLoaded && result != null) {
mView.clearCacheArticles();
isCacheLoaded = true;
}
if (result == null) {
return;
}
mView.onFetchedArticles(result);
// 存储文章列表
DatabaseHelper.getInstance().saveArticles(result);
updatePageIndex(page, result);
}
});
}
/**
* 更新下一页的索引,当请求成功且不是第一次请求最新数据时更新索引值。
*
* @param loadPage
* @param result
*/
public void updatePageIndex(int curPage, List<Article> result) {
if (result.size() > 0
&& shouldUpdatePageIndex(curPage)) {
mPageIndex++;
}
}
/**
* 是否应该更新Page索引。更新索引值的时机有两个,一个是首次成功加载最新数据时mPageIndex需要更新;另一个是每次加载更多数据时需要更新.
*
* @param curPage
* @return
*/
private boolean shouldUpdatePageIndex(int curPage) {
return (mPageIndex > 1 && curPage > 1)
|| (curPage == 1 && mPageIndex == 1);
}
public int getPageIndex() {
return mPageIndex;
}
public void loadNextPageArticles() {
fetchArticlesAsync(mPageIndex);
}
private String prepareRequestUrl(int page) {
return "http://www.devtf.cn/api/v1/?type=articles&page=" + page
+ "&count=20&category=1";
}
}
public class ArticleDetailPresenter extends BasePresenter<ArticleDetailView> {
/**
* 加载文章的具体内容,先从数据库中加载,如果数据库中有,那么则不会从网络上获取
*
* @param postId
*/
public void fetchArticleContent(final String postId,String title) {
// 从数据库上获取文章内容缓存
// ArticleDetail cacheDetail =
// DatabaseHelper.getInstance().loadArticleDetail(postId);
// String articleContent = cacheDetail.content;
String articleContent = loadArticleContentFromDB(postId);
if (!TextUtils.isEmpty(articleContent)) {
String htmlContent = HtmlUtls.wrapArticleContent(title, articleContent);
mView.onFetchedArticleContent(htmlContent);
} else {
fetchContentFromServer(postId, title);
}
}
public String loadArticleContentFromDB(String postId) {
return DatabaseHelper.getInstance().loadArticleDetail(postId).content;
}
protected void fetchContentFromServer(final String postId,final String title) {
mView.onShowLoding();
String reqURL = "http://www.devtf.cn/api/v1/?type=article&post_id=" + postId;
HttpFlinger.get(reqURL,
new DataListener<String>() {
@Override
public void onComplete(String result) {
mView.onHideLoding();
if (TextUtils.isEmpty(result)) {
result = "未获取到文章内容~";
}
mView.onFetchedArticleContent(HtmlUtls.wrapArticleContent(title, result));
DatabaseHelper.getInstance().saveArticleDetail(
new ArticleDetail(postId, result));
}
});
}
}