15.《Android开发进阶 从小工到专家》笔记

一、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));
                    }
                });
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容

  • 第一章:Android基石——四大组件 四大组件:• Activity:负责UI元素的加载与页面之间的跳转,相当于...
    loneyzhou阅读 591评论 0 1
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,988评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,647评论 18 139
  • 关于梦想,朋友圈时不时会见小白说一些感谢一路支持的人的话,亦或是说会让那些现在瞧不起他的人怎样怎样,我暗地里笑他还...
    攀登的木拉阅读 272评论 0 0
  • 在这个北方小城 雨声和落叶揉碎的十月 欲望和躁动化为泥土 没有人在读情诗 干燥的烈风走过青石苔 脚下总是湿的 比起...
    酒色的石头阅读 228评论 5 12