从零开始开发一款Android App

转自:http://www.jianshu.com/p/a58d15ef5c8b

入门篇:
第一篇:开发环境篇
第二篇:材料设计篇
第三篇:规范开发篇
第四篇:从项目开发到上架篇(篇章调整,最后更新)

进阶篇:
第五篇:设计模式篇
第六篇:网络请求篇(上)
    网络请求篇(下)
第七篇:图片处理篇
第八篇:数据库篇
第九篇:开源资源篇

高阶篇:
第十篇:自定义控件篇
第十一篇:跨进程篇

关于Demo

  • 写这个系列的文章的同时,也在做一个小Demo。功能很简单,利用豆瓣电影Api实现一款基于Android的电影集合的App。star me on GitHub!
  • 该项目的数据源来自于豆瓣;

  • 项目用到的第三方类库(博客中写到的类库):ButterKnife, RxJava, Volley, Retrofit2.0, OkHttp, Picasso, Fresco等

看看目前的效果:

从零开始开发一款Android App_第1张图片
Demo.png

入门篇:
第一篇:开发环境篇

Android作为Google的亲儿子,其开发工具Android Studio在2014年底发布1.0版本,目前更新到2.1.1。这是一款很赞的IDE,毕竟power by Intellij Platform。本篇主要介绍Android Studio的一些简单的配置和使用。帮助初学者解决前期使用Android Studio的一些“坑”。

Android Studio

  • 使用Android Studio的时候希望大家能够学会科学上网,否则体验会非常差。

  • Android Studio需要在官网下载的,这给身处墙内的我们造成很大的困扰。这里给出国内下载镜像(同时里面还有一些Android Studio的SDK在线更新的镜像服务器的配置),可供参考。
    *Android Studio2.0以后无论从编译速度还是模拟器的性能和速度都有了很大的提升,大家可以尝试下载最新版本AS。

  • 下载Android Studio完成后,第一次启动又会卡住,弹出 "Fetching android sdk component information" 对话框,这是Android Studio在检查sdk的更新,我们会被墙;
    解决方法:在Android Studio的安装目录的bin文件夹下找到idea.properties文件,用sublime或者其他文本编辑器打开,在文件的末尾加上disable.android.first.run=true,重启AS,即可顺利进行进入界面;

    从零开始开发一款Android App_第2张图片
    Android Studio.png

SDK Manager中的东西哪些需要下载?
打开SDK Manager,很多待下载文件,可是哪些需要下载呢?因为即使有代理,从SDK Manager中下载东西也是很慢的,全部下载会花费很长时间,而且也没有必要。所以我总结了一下,SDK Manager中只需要下载以下几点:

  • Tools文件夹下的内容:这里面是不同版本的SDK编译工具。
  • 各个SDK版本:这里SDK版本需要全部下载。但不是让你把每个API下的所有内容都下载,只需要下载SDK Platform即可,其他内容按需下载。
从零开始开发一款Android App_第3张图片
SDKManagerSample.png

我们不知道从网上clone下来的工程的compileSdkVersion、buildToolVersion、minSdkVersion和targetSdkVersion的版本,所以,提前全部下载下来等着自动适配有很大的好处。

*tips: 建议将Android Studio、gradle和Sdk版本更新到最新

Android Studio插件

选择一些好的Android Studio插件会使得我们的开发事半功倍,下面介绍一些我正在使用的不错的插件,欢迎大家补充:

  1. Android ButterKnife Zelezny:这个插件要配合Jake Wharton大神的ButterKnife使用,后面我会简单介绍到;
  2. Android Paracelable Code generator:序列化代码生成工具,当你的entities里面有一大堆元素需要Paracelable的时候就会发现这个插件的好处。
  3. .gitignore: 设置git 上传的ignore文件
  4. Lifecycle Sorter:根据Activity和fragment的生命周期对生命周期方法的位置进行排序

插件下载的方法:Android Studio -> Preferences -> Plugins ->(Search)
这里还有其他的优秀插件,开发人员可以自行选择。

代码托管

Android Studio中设置git和Github是非常容易的(如果是windows的话需要先下载git),这里不详述。Github上免费的代码托管是public的,所有人都可以看到。如果你不想公开你的项目,或是需要合作开发一个private项目,就找一个国内的免费git托管吧,Coding是一个不错的选择。

开发前准备

Android Studio配置完成后就可以开始开发了,这个项目托管于Github,fork me on Github。

数据源:豆瓣电影Api

Android project相关参数:

  • 开发环境 Android Studio 2.11
  • compileSdkVersion 23
  • buildToolVersion 23.0.3
  • minSdkVersion 15
  • targetSdkVersion 23
  • JDK Version 1.8


第二篇:材料设计篇

Google在I/O 2014上推出Material Design(材料设计)规范后,被外界称赞:“Google 第一次在设计方面超越了Apple。”,随着Android5.0的市场占有率增加,Material Design越来越被开发者和用户所接受。本文内容旨在介绍Material Design在具体开发中的使用;如果有对Material Design不了解的朋友,请先参考官网介绍;

“Material Design”推出很多的新控件,如RecyclerView、CardView、Drawable、FloatingActionButton等等。本篇不讨论这些控件的使用方法,简书的“Android材料设计”专题里面的大神们已经写了很多,如果有控件使用方面的疑虑,可以参考该专题;本篇将讨论一些实际性的问题:

玩转“Material Design”

先教大家一个小技巧:如果项目中想要使用“Material Design”相关东西,新建项目时在"Add an Activity to Mobile"这个选择框时选择"Navigation Drawer Activity",而不是选择“Empty Activity”,这时,新建的项目就会有材料设计的框架。在这个基础上开发,会避免一些麻烦。否则,如果对材料设计的各种style和各类包不熟悉的话,在编译的时候会出很多问题;

从零开始开发一款Android App_第4张图片
Navigation_Drawer.png

新建工程的build.gradle文件中,看到AS已经自动导入了你当前SDK最新的“材料设计”的v7包:

compile 'com.android.support:appcompat-v7:23.3.0'
compile 'com.android.support:design:23.3.0'

运行新建的工程文件,一个带侧滑和沉浸式状态栏的的空项目完成。此时我们就来研究一下如何实现沉浸式状态栏:

沉浸式状态栏

先看看效果图:

从零开始开发一款Android App_第5张图片
沉浸式状态栏.png

沉浸式状态栏:即 将状态栏的颜色改成ToolBar颜色的深色版本。使得状态栏也成为App的一部分,而不是像5.0以下版本那样一成不变的灰色或黑色。

方法一:
在AndroidManifest.xml可以看到Application使用的Theme是自定义的style “AppTheme”。于是,我们打开在values文件夹下看到AppTheme:

这里的colorParimary是Toolbar的颜色,而colorParimaryDark是状态栏的颜色,两个颜色请自行设定;

方法二
同时我们注意到:MainActivity由于需要添加NavigationView,所以要自己写ToolBar。因此这里的Theme可以自定义AppTheme.NoActionBar(写好的Toolbar后会默认使用AppTheme的沉浸式状态栏的配色)。但由于是自定义主题,并不是V7包的内容,则需要在values和values-v21两个文件夹下都写一个同名的style以适配5.0以下版本的机型:
values下的AppTheme.NoActionBar:

values-v21下的AppTheme.NoActionBar:

此时两种设置沉浸式状态栏的方法完成,如果你对沉浸式状态栏的配色不满意,请将这个网站推荐给你的设计师;

ToolBar跟随滑动至消失

效果图:

从零开始开发一款Android App_第6张图片
Toolbar跟随滑动.gif

Toolbar跟随滑动至消失这个效果是一个很容易但是交互性比较强的小动画;

 

   

        
      

从这里看到,我们写Toolbar的时候需要嵌套在AppBarLayout里面,AppBarLayout的好处就是可以让Toolbar配合TabLayout一同使用;要实现Toolbar跟随滑动,只需要在AppBarLayout中添加:

android:fitsSystemWindows="true"

在ToolBar中添加:

app:layout_scrollFlags="scroll|enterAlways|snap"

但是要注意的是:AppBarLayout应该配合CoordinatorLayout使用,同时,ToolBar下的主体部分必须是,RecyclerView、NestedScrollView等实现NestedScrollView接口的可滑动部件;ListView可以在外面嵌套一层NestedScrollView来触发CoordingatorLayout的滑动;布局代码

本篇结束语

本篇仅仅写了“Material Design”的九牛一毛,但是我认为这是上面的两个东西是项目中最常用的,希望我的文章已经解释清楚了。接下来,我的Demo中还会陆续写到RecyclerView、Drawer等控件的使用,虽然不会再另写一篇博客,但是还是希望大家可以关注我的系列文章:从零开始开发一款Android App

刚开始接触“材料设计”这个概念的时候感觉很难,但是在用过一些控件之后,感觉材料设计的人机交互性很强,希望各位能够真正将“材料设计”当成一种工具来实现。



作者:Torang
链接:http://www.jianshu.com/p/47b81f3a0b31
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

第三篇:规范开发篇

我从一个新建的Material Design工程介绍了“沉浸式状态栏”和“ToolBar跟随滑动的”的效果的实现,本篇会结合实际开发写一些Android项目工程下的res文件夹的资源规范问题:

图片资源规范

在Android工程中,drawable和mipmap都可以存放图片资源,但是这两个文件夹有什么区别呢? mipmap下有mipmap-hdpi, mipmap-xhdpi, mipmap-xxhdpi, mipmap-xxxhdpi,这些文件夹下存放什么分辨率的图片资源呢?

drawable 和 mipmap

来看看官方介绍:

drawable/
For bitmap files (PNG, JPEG, or GIF), 9-Patch image files, and XML files that describe Drawable shapes or Drawable objects that contain multiple states (normal, pressed, or focused).
mipmap/
For app launcher icons. The Android system retains the resources in this folder (and density-specific folders such as mipmap-xxxhdpi) regardless of the screen resolution of the device where your app is installed. This behavior allows launcher apps to pick the best resolution icon for your app to display on the home screen.

上面的官方的解释大意是:mipmap文件夹下只是存放不同分辨率的ic_launcher的图标,同时mipmap会在底层对icon进行优化。而drawable文件夹下存放应用中用到的各类图像资源和自定义的xml资源。但是,很多开发者有不同的见解,认为mipmap可以完全代替drawable存放图片资源,看这里。

当出现争议的时候,需要以官方的标准为准!但是官方的标准代码中,这些图片资源是放在那个文件夹下呢?在上一篇文章中提到,我的Demo新建的Navigation Drawer Activity的工程,在这个Demo中,drawer内置了几个图标,我们来看看drawer的几个图标的位置:

从零开始开发一款Android App_第7张图片
drawer.png
从零开始开发一款Android App_第8张图片
drawer图标的位置.png


看到这两个图片,一切都明白了,项目中用到的图片资源都是存放的drawable里面的,而ic_launcher放在mipmap中的,具体原因可以看看这篇文章;好了,项目开发中就接受官方的建议吧。

UI需要做几套图来适配不同分辨率的手机呢?

我们都知道,在xml写布局的时候长度单位是dp,但是UI给的图片的单位是px,我们如何将dp转化为px呢?不同的屏幕分辨率转化转换不同。具体的转化公式和概念本文不给出,如果不清楚可自行Google:
下面直接给出不同的屏幕密度下的px与dp的转化表:

分辨率 ldpi mdpi hdpi xhdpi xxhdpi xxxhdpi
px:dp 0.75 1 1.5 2 3 4

ic_launcher: 在一个新建的项目中可以看到,有5个mipmap文件夹,分别存放不同分辨率的ic_launcher:mipmap-hdpi(72*72px), mipmap-mdpi(48*48px), mipmap-xhdpi(96*96px), mipmap-xxhdpi(144*144px), mipmap-xxxhdpi(196*196px)。所以,根据上个问题的介绍,ic_launcher需要做5种不同的分辨率,

图片资源:首先我们先理解一下,在drawable文件夹下为了适配不同的分辨率也应该分为drawable-hdpi, drawable-ldpi, drawable-mdpi, drawable-xhdpi, drawable-xxhdpi如上图 drawer图标位置.png 。为所有的图片资源都做五种规格显然不现实,那最少需要做几套呢?做一套图行吗?

  • 一般开发会做三套图,对应drawable-hdpi, drawable-xhdpi, drawable-xxhdpi;

    • 同一张图片,放在不同目录下,会生成不同大小的Bitmap。Bitmap的长度和宽度越大,占用的内存就越大。
    • 同一张图片,放在不同的drawable目录下(从drawable-lpdi到drawable-xxhpdi)在同一手机上占用的内存越来越小。
    • 图片在硬盘上占用的大小,与在内存中占用的大小完全不一样。
  • 对App要求不是很高的话,做一套图也是可以的,放在drawable-xxhdpi即可;原因就是为了省内存

结论来自于这篇博客,分析很到位,大家可以结合具体的测试理解。

其他资源

在res文件夹下,除了图片资源,还有values文件夹,该目录下有string, colors, dimens, arrays, styles等资源文件,所有的资源文件都是以xml的格式存储的,这里强烈建议在xml和java代码中来引用资源文件中的资源,而不是直接将字符串、颜色等资源直接写出来,因为这样能方便统一管理,统一修改。

举一个栗子:如果当你做出来一款中文的App以后,你的App想要随着手机系统语言改变App的语言该怎么做呢?如果你所有的string都没有写在strings.xml里,那你就麻烦了!但是如果你规范地写在了strings.xml,恭喜你,只需要翻译就好了;

  • 在res目录下新建一个values-zh-rCN文件夹,将我们的中文资源的strings.xml和arrays.xml复制在该文件夹下;
  • 在原values文件夹下,将strings.xml和arrays.xml中的中文翻译成你自己想要的English就可以了。

完成上面步骤后,你的App就可以跟随系统语言切换不同语言了。

除了字符资源以外,colors、dimens等资源写在xml资源文件中对以后App的版本更新迭代有很大的帮助。所以,在初次开发的时候请不要省去这个步骤。



第四篇:从项目开发到上架篇(篇章调整,最后更新)


进阶篇:
第五篇:设计模式篇

介绍了Android工程res目录下资源的规范。本篇将简单介绍Android开发中MVP模式的包结构和Java设计模式在Android中的应用。

MVP

最近MVP模式很火,可能是因为面向接口编程这种思想逐渐深入人心的缘故。其实MVP的核心就是:将所有的底层接口的功能实现都封装起来,而不让调用者了解到任何实现细节,最终实现用户界面与数据层的高度解耦的一种设计方法。本篇将结合实际的操作来写一写Android MVP模式的实现。

基本概念

M V P 分别代表Model、View、Presenter

  1. View层负责Android界面用户事件和数据展示:一般多为Activity和Fragment;
  2. Model层负责访问数据,如从Server加载的Java Bean、本地的Database、Sharedpreference、文件数据等;
  3. Presenter层是View层与Model层之间数据传输的桥梁;

下图为MVC和MVP模式之间的区别:(其实我们传统的开发模式并不是真正的MVC模式,我认为是一种阉割版的。因为在我们传统编码中Android SDK提供了基于XML的界面框架与存储数据的ContentProvider组件,而开发者则更加专注于业务逻辑的编写。)

从零开始开发一款Android App_第9张图片
MVC-vs-MVP.png

所以我们以往开发模式(MVC)中,如果需要频繁更改显示数据,Activity总是即充当View又充当Controller,那么你的Activity.java文件会出现大量代码,不易于维护和版本迭代。但是当View层和Model层解耦后,就不会出现这种情况。从上图中我们可以看到MVP模式的View层和Model层不发生任何联系,而数据交换与用户操作事件依赖于Presenter层来实现,所以在这一点上,MVP优于传统的编码模式。那么MVP将如何实现呢?

MVP的实现

举一个栗子:网络请求数据,然后显示在用户界面上:
下面的代码的MVP的结构参考这个开源项目,代码结构很清晰。

View层

MvpView

public interface MvpView {
    void startLoading();
    void hideLoading();
    void showError(String msg, View.OnClickListener onClickListener);
}

DataView

  public interface DataView extends MvpView {
    void loadData(UsBoxEntity usBoxEntity);
  }

HotMovieFragment

public class HotMovieFragment extends BaseFragment implements DataView {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        presenter = new HotMoviePresenterImpl(getActivity());
        presenter.attachView(this);
        presenter.loadData();
    }
    /**
      * Presenter 的回调接口,用于更新UI
      */
      @Override
      public void loadData(UsBoxEntity usBoxEntity) {
            log.d(usBoxEntity.getTitle());
            mAdapter = new OutRecyclerAdapter(getActivity(),usBoxEntity);
           recyclerMovie.setAdapter(mAdapter);
            //网络请求到的数据在这里更新UI
      }
}

BaseFragment

public class BaseFragment extends Fragment implements MvpView {
    @Override
    public void startLoading() {    } 
    @Override
    public void hideLoading() {    }
    @Override
    public void showError(String msg) {    }
 }

Presenter层

Presenter

public interface Presenter {
    void attachView(V mvpView);
    void detachView();
}

BasePresenter

public class BasePresenter  implements Presenter {
    private T mMvpView;
    @Override
    public void attachView(T mvpView) {
        mMvpView = mvpView;
    }
    @Override
    public void detachView() {
        mMvpView = null;
    }
    public boolean isViewAttached() {
        return mMvpView != null;
    }
    public T getMvpView() {
        return mMvpView; 
   } 
   public void checkViewAttached() { 
       if (!isViewAttached()) throw new MvpViewNotAttachedException();
    }
    public static class MvpViewNotAttachedException extends RuntimeException{
        public MvpViewNotAttachedException(){
            super("Please call Presenter.attachView(MvpView) before requesting data to the Presenter");
        }
    }
}

HotMoviePresenterImpl

public class HotMoviePresenterImpl extends BasePresenter{
    Context context;    UsBoxEntity usBoxentity;
    public HotMoviePresenterImpl(Context context){        
        this.context = context;
    }
    @Override
    public void attachView(DataView mvpView) {
        super.attachView(mvpView);
    }
    @Override
    public void detachView() {
         super.detachView();
    }
    public void loadData(){
        //在这里写网络请求的代码,从Server中请求到JavaBena UsBoxEntity
        //调用MvpView中的loadData方法,将请求到的数据传回View层,让View层更新数据;
         getMvpView().loadData(usBoxentity);
    }
}

Model层

model层即为网络请求的一些配置(也可以是数据库的请求等),例如:OkHttp、Retrofit等网络请求的配置,由于下一篇将着重讲述Retrofit配合Okhttp的应用,所以这里不详细写。

给出我的Demo的包结构截图供大家参考:

从零开始开发一款Android App_第10张图片
MVP_包结构.png

以上说了很多MVP模式的好,但是并不是说所有的工程都适合MVP。MVP类似于面向接口编程,所以在一个大型项目中会出现很多接口文件,这也是不利于维护的。所以,具体开发还需要根据实际情况选择。

Java设计模式在Android中应用

如果对Java设计模式还不熟悉的朋友,可以先看看《设计模式之禅》这本书。这本书通过对6大设计模式原则进行了全新的解读,对面向接口编程和Java的23种设计模式都做了详细讲述。本篇不会涉及概念性的东西,只是简单说一说隐藏在Android开发中的一些Java设计模式。

适配器模式:对于Android开发人员来说,适配器模式应用非常广泛,例如ListView、RecyclerView、Viewpager等控件的实现,都是需要写一个adapter从而使原本不匹配而无法在一起工作的两个类能够在一起工作;

单例模式:当应用启动后会create一个Application,这个Application就是一个单例模式,从应用启动到关闭都会维持这个Application不会改变。还有,otto、RxBus等开源框架在使用中都会维护一个单例从而保证数据发送的唯一性;

观察者模式:点击事件用到的Listener就是一种订阅,订阅点击事件。还有最近比较火的Rxjava的使用,也是一种观察者模式,它有发布者和观察者,观察者通过消息的订阅来获取发布者发布的消息;

动态代理模式:Retrofit网络请求就是用到了动态网络代理模式,通过代理模式,插入不同的功能框架,来达到定制的网络请求的效果;

响应链模式:Android的事件分发机制;

Java设计模式在Android中的应用很多,这里只写出我认为比较常用的几种,其它的不一一列举,如果想要了解,可以查看这个专题。

本篇如果有不正确的地方,希望大家指出!



作者:Torang
链接:http://www.jianshu.com/p/fa92ca51bdb0
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

第六篇:网络请求篇(上)

简单地写了一些关于Android开发模式MVP的实际操作应用,最后还说了关于Java设计模式在Android中的应用。本篇主要写一些在Android开发中使用RxJava配合Retrofit和OkHttp进行网络请求的操作。

开发前准备

Gradle:最新包的下载地址:RxJava&RxAndroid ,Retrofit,OkHttp, Volley

dependencies {    
  compile fileTree(dir: 'libs', include: ['*.jar'])
  compile 'io.reactivex:rxandroid:1.1.0'
  compile 'io.reactivex:rxjava:1.1.3'
  compile 'com.squareup.retrofit2:retrofit:2.0.2'
  compile 'com.squareup.retrofit2:converter-gson:2.0.2'
  compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
  compile 'com.squareup.okhttp3:okhttp:3.2.0'
  compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'
  compile 'com.mcxiaoke.volley:library:1.0.19'
 }

网络权限:

OkHttp & Volley

Volley(项目暂停维护)

说OkHttp之前不得不说一下Volley,作为曾经比较强大的一个网络请求库它被Google官方接受并于2013年Google I/O上推出,其实Volley内部封装使用的是HttpURLConnection和HttpClient。如果想要深入了解一下Volley可以参考CSDN郭霖大神的四篇文章
由于Volley不是本篇的重点,这里只是给出使用Volley Http Get请求的简单例子:

public void loadDataWithVolley(){
    RequestQueue mQueue = Volley.newRequestQueue(context);
    JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(URL, null,
        new Response.Listener() {
           @Override 
           public void onResponse(JSONObject response) {
                    log.d(response.toString());
                } 
     }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                log.e(error);
            }
      });
      mQueue.add(jsonObjectRequest);
}

通过上面的的代码可以看出:Volley通过维护一个请求队列来实现网络请求。它的底层有一个缓存线程和一个线程池,这样一来Volley的开发效率就比较低了,在上传大文件方面处理也不好,同时在功能拓展性方面也有欠缺。于是,Okhttp逐渐深入人心:

OkHttp3

对于基本的OkHttp3的使用参考官网;
OkHttp是一个高效的Http客户端,其特点:

  1. 支持HTTP2/SPDY黑科技
  2. socket自动选择最好路线,并支持自动重连
  3. 拥有自动维护的socket连接池,减少握手次数
  4. 拥有队列线程池,轻松写并发
  5. 拥有Interceptors轻松处理请求与响应(比如透明GZIP压缩,LOGGING)
  6. 基于Headers的缓存策略

以上内容参考系列文章:OkHttp3源码分析[综述],如果想要深入了解OkHttp的可以参考源码查看文章。

RxJava配合OkHttp3下载 文件&图片

如果你按照我上面的建议,看了OkHttp官网上的代码,并把它用到了项目中,同时没有做其他的操作,编译的时候你会发现问题:

java.lang.IllegalStateException: Fatal Exception thrown on Scheduler.Worker thread.
Caused by: android.os.NetworkOnMainThreadException

这是因为,官网上只是给出了OkHttp同步请求的操作,并没有给出异步操作!!!(OkHttp2.x的时候官网上是有异步操作的例子)OkHttp3和OkHttp2.x的异步请求还是有一些区别的:如果想要了解OkHttp3的异步操作,可以参考这篇文章:
官网上没有给出OkHttp3的异步操作,也许是想要让开发者配合RxJava的使用。如果还不了解RxJava的用法,请先Google一下,这几篇是国内最初翻译的Rxjava文献,可供参考。

下载文件(实时返回下载进度)

RxJava中的事件源(被观察者):这里配合OkHttp进行进行网络操作,

File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
File file = new File(path,"/okhttpdownload");
/**
 * download file with OkHttp and RxJava (rate) 
 */
Observable downloadObservable = Observable.create(new Observable.OnSubscribe() {
    @Override
    public void call(Subscriber subscriber) {
        InputStream inputStream = null;
        OutputStream outputStream = null;
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                      .url("Https://xxxx.txt")
                      .build();
        try{
            Response response = client.newCall(request).execute();
            if (response.isSuccessful()){
                  inputStream = response.body().byteStream();
                  long length = response.body().contentLength();
                  //将文件下载到file路径下
                  outputStream = new FileOutputStream(file);
                  byte data[] = new byte[1024];
                  subscriber.onNext("0%");
                  long total = 0;
                  int count;
                  while ((count = inputStream.read(data)) != -1){
                        total += count;
                        // 返回当前实时进度
                        subscriber.onNext(String.valueOf(total*100/length) + "%");
                        outputStream.write(data,0,count);
                    }
                outputStream.flush();
                outputStream.close();
                inputStream.close();
            }
        }catch (IOException e){
            //告诉订阅者错误信息
            subscriber.onError(e);
        }finally {
            if (inputStream != null){
                try{
                    inputStream.close();
                }catch (IOException e){}
            }
            if (outputStream != null){
                try {
                    outputStream.close();
                }catch (IOException e){}
            }
        }
        //告诉订阅者请求数据结束
        subscriber.onCompleted();
  });

事件源写好后,就要开始使用RxJava订阅事件了;

downloadobservable.subscribeOn(Schedulers.io())
    .onBackpressureBuffer()
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Subscriber() {
        @Override
        public void onCompleted() {
            //接收到事件源的onCompleted后的操作
            //一般是取消downloading dialog的操作
        }
        @Override
        public void onError(Throwable e) {
            //接收事件源的错误信息操作
            log.e(e.toString());
        }
        @Override
        public void onNext(String s) {
            // 接受实时的下载进度
            log.d(s);
        }
    });

我认为RxJava中,设计最精妙的就是 .subscribeOn() 和 .observeOn()两个操作了,它可以轻而易举的实现Android中主线程和IO线程的切换,从而让我们可以完全抛弃Handler和AsyncTask。

下载Bitmap图片

虽然Square专门出了一款图片处理的开源库Picasso(下一篇会介绍到),但是如果想要用OkHttp配合RxJava下载图片并现实该怎么做呢?

还是通过RxJava的框架来实现:

Observable downloadDrawble = Observable.create(new Observable.OnSubscribe() {
    @Override
    public void call(Subscriber subscriber) {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(url)
                .build();
        try {
            Response response = client.newCall(request).execute();
            if (response.isSuccessful()){
                InputStream input = response.body().byteStream();
                Bitmap bitmap = BitmapFactory.decodeStream(input);
                subscriber.onNext(bitmap);
            }
        }catch (IOException e){
            subscriber.onError(e);
        }
        subscriber.onCompleted();
    }
});

其实下载图片这里和上面的文件下载大致无二,只是将inputstream转换成bitmap的格式。这里没有将文件下载到本地,而是下载到内存中,应用关闭后会清除数据。事件的订阅和上文一样,可以在onNext()中更新UI,这里会回调到主线程。



     网络请求篇(下)

我写了一些OkHttp和Volley的对比、RxJava配合OkHttp3实现文件&&图片的下载。本篇我将结合实际写一些RxJava配合Retrofit2.0+OkHttp3的网络请求库的操作;

OkHttp3和Retrofit2.0这些都是Square的东西,所以相互支持。Retrofit2.0的底层网络请求就是OkHttp实现的;

Retrofit2.0+RxJava+OkHttp

Retrofit2.0相对于Retrofit1.x来说最值得期待的功能就是可以配合RxJava使用,自从去年九月份发布以后,Retrofit2.0配合RxJava的使用的教程举不胜举,例如:这一篇,下面贴上我的代码,不做过多解释。因为本篇的重点不在Retrofit2.0与RxJava的使用上,而是Retrofit配合OkHttp的拓展功能。

Service:Retrofit用注解方式进行数据的请求,以下给出使用post上传map表单和get请求数据两种操作,Delete、Put等其它操作,请看官网详解;

public interface CourierInfoService {
    @FormUrlEncoded
    @POST("/api/v3/login")
    Observable login(@FieldMap Map detail); 

    @GET("/api/v3/login")     
    Observable courierLogout(@Query("man_id") String courierId);
}

Retrofit2.0+OkHttp3进行配置;

private static OkHttpClient client = new OkHttpClient
    .Builder()
    //拓展功能:网络请求的log,compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'
    .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
     //拓展功能:数据请求的压缩,下面会解析自定义:
    .addInterceptor(new GzipRequsetInterceptor())
    .build();

private static Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("Https://xxxxx.xxx")
    .client(client)
    //拓展添加RxJava的功能,导入的库:compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
    //使用Gson对Json进行解析,导入的库:compile 'com.squareup.retrofit2:converter-gson:2.0.2'
    .addConverterFactory(GsonConverterFactory.create())
    .build();

使用RxJava进行订阅

retrofit.create(Service.class)
    .login(detailmap)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer() {
        @Override
        public void onCompleted() {}
        @Override
        public void onError(Throwable e) {}
        @Override
        public void onNext(GetData getData) {}
    });

Retrofit网络请求的代码就告一段落了,是不是很容易?尤其是配合RxJava的链式操作,很容易就实现了线程的切换和回调。开发者可以自行尝试一下!

当网络请求数据量非常大、返回的Json数据比较复杂时,将Json数据解析成为JavaBean是一件非常痛苦的事情。分享一个便捷的方法:打开这个网站,将你的Json数据扔进去,然后在右边选择,Source type:JSON,Annotation style:GSON,点击Generate。网站会自动将Json数据自动解析成Java Bean文件(zip格式,解压),最后将所有的文件放进你项目合适的地方就可以使用(如果有报错的注解,删除)。

OkHttp功能拓展

OkHttp的实例的构建应该和Retrofit一样,使用了动态代理的模式,可以随意的定制你自己想要的功能,其是通过 .addInterceptor() 的拓展实现的。

添加网络请求header

在Retrofit1.x版本的时候,添加网络请求的头部需要实例化一个RequestInterceptor,然后在RequestInterceptor中addHeader(xxx,xxx),然后在插入RestAdapter实例中,实现网络请求的header添加;而在Retrofit2.x版本没有RestAdapter的配置了,所以我们要将其写在OkHttp的 .networkInterceptor()中;(官网上讲的添加@Header注解只能对某一个请求有效,下面的方法对所有请求有效。)

client.addNetWorkInterceptor(new HeaderInterceptor())
.....
static class HeaderInterceptor implements Interceptor{
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request originalRequest = chain.request();
        Request compressedRequest = originalRequest.newBuilder()
                 //根据服务器的要求,自行添加IP报文的头部
                .addHeader("User-Agent", "SampleDemo/"+ " (android;" + android.os.Build.VERSION.RELEASE + ";" + android.os.Build.MODEL + ")")
                .addHeader("Content-Type", "application/x-www-form-urlencoded")
                .addHeader("Accept", "*/*")
                .build();
        return chain.proceed(compressedRequest);
    }
}

网络请求log:

以前是看官方wiki自己写的打log的方法,但是现在有了新的选择,使用OkHttp自带的库,比官网上那个log清晰很多;
官方wiki上给出的打log的方法:

client.addIntercepter(new LoggingIntercepter());
.......
static class LoggingIntercepter implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        long t1 = System.nanoTime();
        log.d(String.format("Sending request %s on %s%n%s",
                request.url(),chain.connection(),request.headers()));
        Response response = chain.proceed(request);
        long t2 = System.nanoTime();
        log.d(String.format("Received response for %s in %.1fms%n%s",
                response.request().url(),(t2-t1)/1e6d, response.headers()));
        return response;
    }
}

但是如果你导入' com.squareup.okhttp3:logging-interceptor:3.2.0'包后,就可以直接使用log;

  client.addIntercepter(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));

这个工具就像一个抓包工具一样,将你网络请求和接收的数据都以log的形式写打印出来;

Gzip压缩

如果你的服务器数据支持Gzip压缩,请使用下面的Interceptor;了解更多,请看官方wiki

client.addInterceptor(new GzipRequestInterceptor());
.......
static class GzipRequsetInterceptor implements Interceptor{
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request originalRequest = chain.request();
        if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
            return chain.proceed(originalRequest);
        }
        Request compressedRequest = originalRequest.newBuilder()
                .header("Content-Encoding","gzip")
                .method(originalRequest.method(),gzip(originalRequest.body()))
                .build();
        return chain.proceed(compressedRequest);
    }
    private RequestBody gzip(final RequestBody body){
        return new RequestBody() {
            @Override
            public MediaType contentType() {
                log.d("gzip!");
                return body.contentType();
            }
            @Override
            public long contentLength() throws IOException {
                return -1;
            }
            @Override
            public void writeTo(BufferedSink sink) throws IOException {
                BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
                body.writeTo(gzipSink);
                gzipSink.close();
            }
        };
    }
}

通过两篇的分析,我基本上将最近比较流行的Android中的网络请求框架写完了;OkHttp是一个很强大的网络请求的客户端,有兴趣的朋友可以深入研究一下;


第七篇:图片处理篇

我分为两部分基本上把最近Android网络请求最火的开源框架说完了。本篇就来讲一讲Android的图片的处理,在Android App上加载图片是很常见的,同时也是很容易造成OOM。

如果你对加载大图、图片缓存、OOM异常等问题不太了解,请先看看郭大神这篇文章,分析的很详细;当然,本篇主要是让你学会使用Picasso和Fresco这两个图片处理库,这两个库的使用方法在网上都很多,而且都非常简单,但是今天我会说点不一样的!

Picasso2 && OkHttp3

我在 第六篇 网络请求篇(上) 的时候为了说OkHttp3,就示例使用OkHttp3加载图片,其实那不是很好的加载图片的方法,Square有一款专门的图片加载工具Picasso:官网地址

Picasso的常规使用

在官网上可以看到,Picasso的使用方法很简单:

 Picasso.with(context).load("path").into(imageView);

通过官网上面的解析,我们还可以了解到Picasso的其它的一些用法,比如:placeholder()、resize()、centerCrop()、error()等等;都可以很简单的实现。

同时,Picasso的内部会默认帮你实现了内存缓存,其大小为内存1/7左右,所以不做详述。因为今天我们要说的是本地缓存,如何用OkHttp3配合Picasso实现本地缓存呢?

Picasso的本地缓存

由于Picasso和OkHttp同属Square公司,所以,他们互相支持调用;
我在写这篇博客之前在google了很久,stackOverflow上面的用OkHttp写缓存都比较简单,也没有比较权威的说法。于是在github上面找到了Jake Wharton大神写的关于Picasso使用OkHttp3的配置,瞬间觉得这就是权威!看源码:

这里不把全部代码贴出来,部分解析:大家需要明确一点Downloader已经实现了DiskLruCache,我们现在需要的是配合OkHttp的网络请求来重写缓存。

public final class OkHttp3Downloader implements Downloader {
    private static final String PICASSO_CACHE = "picasso-cache";
    private static final int MIN_DISK_CACHE_SIZE = 5 * 1024 * 1024; // 5MB
    private static final int MAX_DISK_CACHE_SIZE = 50 * 1024 * 1024; // 50MB

    //新建一个默认的缓存文件夹
    //可以在你手机中/android/data/yourapp/picasso-cache文件夹中可以找到
    private static File createDefaultCacheDir(Context context) {
        File cache = new         
        File(context.getApplicationContext().getCacheDir(), PICASSO_CACHE);
        if (!cache.exists()) {
            //noinspection ResultOfMethodCallIgnored
            cache.mkdirs();
        }
        return cache;
    }

    //计算缓存文件的大小,
    private static long calculateDiskCacheSize(File dir) {
        long size = MIN_DISK_CACHE_SIZE;
        try {
            StatFs statFs = new StatFs(dir.getAbsolutePath());
            long available = ((long) statFs.getBlockCount()) * statFs.getBlockSize();
          // Target 2% of the total space.
          size = available / 50;
          } catch (IllegalArgumentException ignored) {}

          // Bound inside min/max size for disk cache.
        return Math.max(Math.min(size, MAX_DISK_CACHE_SIZE), MIN_DISK_CACHE_SIZE);

    }

    ......

    //网络请求时的本地缓存处理,如何没有网络且有缓存,则从缓存中读取;
    @Override public Response load(Uri uri, int networkPolicy) throws IOException {
        CacheControl cacheControl = null;
        if (networkPolicy != 0) {
            if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
                cacheControl = CacheControl.FORCE_CACHE;
            } else {
                CacheControl.Builder builder = new CacheControl.Builder();
                if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
                    builder.noCache();
                }
                if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
                     builder.noStore();
                }
                cacheControl = builder.build();
            }
       }

      Request.Builder builder = new Request.Builder().url(uri.toString());
      if (cacheControl != null) {
            builder.cacheControl(cacheControl);
      }
        okhttp3.Response response = client.newCall(builder.build()).execute();
        int responseCode = response.code();
        if (responseCode >= 300) {
            response.body().close();
            throw new ResponseException(responseCode + " " + response.message(), networkPolicy,responseCode);
      }
        boolean fromCache = response.cacheResponse() != null;
        ResponseBody responseBody = response.body();
        return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength());
    }
    //关闭App时,如果有缓存,关闭DiskLruCache;
    @Override public void shutdown() {
        if (cache != null) {
            try {
                cache.close();
            } catch (IOException ignored) {}
        }
    }
}

通过上面的代码已经构建出来一个OkHttp3downloader了,现在只需要将它加载到Picasso中去执行就好了;(注意:构建的picasso实例为一个单例,所以需要在Application中去创建这个实例,然后在调用的地方获取picasso单例即可);我是这样写的:

Picasso picasso = new Picasso.Builder(this)
        .downloader(new OkHttp3Downloader(new OkHttpClient()))
        .build();
Picasso.setSingletonInstance(picasso);

调用的时候这样写:

picasso.with(context).load("Path").into(imageView);

好了,Picasso的本地缓存说完了,接下来说一说Picasso的其它的使用方法:
说之前推荐一篇文章:使用Picasso加载带饼状进度条的图片。

Picasso的使用技巧

添加listener:当你的使用picasso加载图片失败的时候,可以通过listener把失败原因打印

Picasso picasso = new Picasso.Builder(this)
        .listener(new Picasso.Listener() {
            @Override
            public void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception) {
                  //图片加载失败时的操作
            }
        })
        .loggingEnabled(true)// 打log
        .indicatorsEnabled(true)// 分辨不同的路径的价值的图片;
        .build();

回调:当图片加载成功和失败时的回调;

 picasso.with(this)
       .load("path")
       .into(imageView, new Callback() {
          @Override
          public void onSuccess() {    }
          @Override
          public void onError() {    }
});

Picasso的相关内容就说完了,如果大家有问题请在评论中指出;

Fresco

Fresco是Facebook开发的一款强大的开源图片处理库,好处不多说,看官方文档,Facebook还是很贴心的,中文版也很清晰;
使用方法(官网代码):

  //使用Facebook自定义的ImageView
 

//图片加载
Uri uri = Uri.parse("https://raw.githubusercontent.com/facebook/fresco/gh-pages/static/fresco-logo.png");
SimpleDraweeView draweeView = (SimpleDraweeView)findViewById(R.id.my_image_view);
draweeView.setImageURI(uri);

当然我们最关心还是缓存技术,在Fresco中实现了三层缓存技术来最大限度地节省空间和CPU时间:两级内存(内存缓存和本地缓存)和一级文件(Bitmap缓存),这里不需要像上面Picasso一样来自己使用OkHttp来做本地缓存了。

关于网络请求方面,Fresco也是支持自定义网络请求的,可以使用OkHttp,官网文档有介绍,不过我认为这里的OkHttp已经不是我们熟悉的Square的OkHttp了,Facebook应该为适配Fresco而做了一些改进。

使用Fresco在提升性能的同时牺牲了不少空间的,可以看看这篇文章:Fresco调研与性能测试。里面结合实际例子展现出来:Fresco占用的Native Heap很大,但是占用Java Heap很小:意思就是说Fresco更多的是使用了OS的资源,而不是使用我们程序所在进程中的资源;

Fresco要说的不多,但是它的使用方法非常多,我们以后想要将这个库应用到实际开发中,还是需要多上手实践一下;官网是最好的教程。

最后看一篇文章,带你看看Fresco有哪些不足之处:Fresco之强大之余的痛楚

Picasso? Fresco?

看了上面的介绍,你是否有疑惑?两个框架同样优秀,到底该选择哪一个使用呢?两者的区别可以在stackoverflow上面找;

下图是我分别使用Picasso和Fresco的例子,如果有兴趣的可以在Github上面把代码Clone下来看看效果:

从零开始开发一款Android App_第11张图片
Picasso&&Fresco.png

(我为什么使用Picasso?因为对Square的信仰!!!)

总结一下:当你的App有大量的图片出现、图片的分辨率非常大的时候,请使用Fresco,因为使用其它框架很容易造成OOM;为了性能而牺牲一些空间资源是值得的;而当App中图片较少、图片的质量不是很高的时候可以使用Picasso:Fresco资源占用太多就不值得了,这时Picasso就会有很大的优势;

当然还有其他的图片加载库,如Glide,使用方法和Picasso几乎一样,有兴趣可以了解一下。

以上就是本篇的全部内容,如果有问题,请在评论中指出!



作者:Torang
链接:http://www.jianshu.com/p/9b93737bfa88
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

第八篇:数据库篇

讲了Picssco和Fresco的图片缓存处理的相关例子,本篇将来说一说Android数据库的相关操作:SqLite和Realm

Android的数据存储的几种方式

  • SharePreferences:轻量级的数据存储,以键值对的形式存储数据,数据存储的格式为xml
  • SQLite:Sql数据库
  • Content Provider:Android提供的数据共享的一种功能,可以获取其他App的数据
  • File文件存储:I/O文件存储;
  • 网络存储:

上面五种方式都能够存储数据,使用情境不同,本篇介绍的是SQLite,然后再介绍一款开源数据库Realm的使用:

SQLite

SQLite使用的是标准的Sql语句,增、删、改、查等操作与其他数据库无异,只要熟悉sql语句,再了解一下格式SQLite操作就能正确使用SQLite;

SQLite使用请参考Android开发的文档;文档中有很好的例子,下面解释部分;

public class FeedReaderDbHelper extends SQLiteOpenHelper {
    // If you change the database schema, you must increment the database version.
    public static final int DATABASE_VERSION = 1;
    public static final String DATABASE_NAME = "FeedReader.db";
    public FeedReaderDbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
    public void onCreate(SQLiteDatabase db) {
        //新建表操作
        db.execSQL(SQL_CREATE_ENTRIES);
    }
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // 表的更新操作
        db.execSQL(SQL_DELETE_ENTRIES);
        onCreate(db);
    }
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        onUpgrade(db, oldVersion, newVersion);
    }
}

存入数据库

  SQLiteDatabase db = mDbHelper.getWritableDatabase();
  // 通过一个新建的ContentValues以键值对的方式传值
  ContentValues values = new ContentValues();
  values.put(FeedEntry.COLUMN_NAME_ENTRY_ID, id);
  values.put(FeedEntry.COLUMN_NAME_TITLE, title);
  values.put(FeedEntry.COLUMN_NAME_CONTENT, content);
  // 插入新的一个行,返回该item的主键
  long newRowId;
  newRowId = db.insert(         
         FeedEntry.TABLE_NAME,
         FeedEntry.COLUMN_NAME_NULLABLE,
         values);

读取数据库

SQLiteDatabase db = mDbHelper.getReadableDatabase();
// 定义一个 projection[] ,projection是你想获取数据库的列数据
String[] projection = {
    FeedEntry._ID,
    FeedEntry.COLUMN_NAME_TITLE,
    FeedEntry.COLUMN_NAME_UPDATED, 
    ...    };

// 数据以某种方式排序
CursorString sortOrder =    FeedEntry.COLUMN_NAME_UPDATED + " DESC";

Cursor c = db.query(
    FeedEntry.TABLE_NAME,  // 查询的表名
    projection,                               // 返回的列数据
    selection,                                // 查询列要求
    selectionArgs,                            // 查询值的要求
    null,                                     // 分组语句
    null,                                     // 分组过滤操作
    sortOrder                                 // 排序规则   );

通过Cursor的值获取方式读取数据

cursor.moveToFirst();
long itemId = cursor.getLong(
    cursor.getColumnIndexOrThrow(FeedEntry._ID));

Sqlite需要注意的

  • 一个Application维护一个数据库文件(.db文件),每个数据库文件中可以有多张表;
  • 在数据库增删改查操作用到的FeedReaderDbHelper(extends SQLiteOpenHelper)实例mDbHelper需要在Application中的OnCreat()中创建,而且全局只需要一个实例;(和前面介绍的picasso实例构造相同)
  • 数据库操作属于耗时操作,不能在主线程调用;

Realm && RxJava

虽然Google官方使用Sqlite来作为Android的数据库,但是看到上面的简介和代码后感觉还是觉得庞然大物,如果编程基础比较少的童鞋一定不会感觉到容易,所以现在就来说一说另外一种开源数据库Realm:官方文档

官方文档介绍的Realm的用法非常详细,并且相对于SQLite来说创建数据表是非常简单的,所以本篇的重点不在这里。本篇主要讲一讲Realm如何配合Retrofit+RxJava使用:

先对照看看Realm给出的例子:从Person数据表中读取出"githubUserName",然后使用retrofit做网络请求,得到GithubUser,将GithubUser的数据传入到UserViewModel中,最后通过UI显示出来;(这里只是截取RxJava的操作部分)

看代码:

subscription = realm.where(Person.class).isNotNull("githubUserName").findAllSortedAsync("name").asObservable()
    .filter(new Func1, Boolean>() {
        @Override
        public Boolean call(RealmResults persons) {
          // 过滤没有被出的数据行
          return persons.isLoaded();
        }
    })
    .flatMap(new Func1, Observable>() {
          @Override
          public Observable call(RealmResults persons) {
              // 一个一个将RealmResults 传入Observable
              return Observable.from(persons);
      }
    })
    .flatMap(new Func1>() {
          @Override
          public Observable call(Person person) {
            // 使用Retrofit进行网络请求,得到GitHubUser类型的数据
           return api.user(person.getGithubUserName());
      }
    })
    .map(new Func1() {
          @Override
          public UserViewModel call(GitHubUser gitHubUser) {
              //使用Map将GitHubUser数据转换成UserViewModel类型
              return new UserViewModel(gitHubUser.name, gitHubUser.public_repos, gitHubUser.public_gists);
       }
    })
      .observeOn(AndroidSchedulers.mainThread())
     // Retrofit put us on a worker thread. Move back to UI
      .subscribe(new Action1() {
          @Override
          public void call(UserViewModel user) {
                // 打印UI的数据
                TextView userView = new TextView(RetrofitExample.this);
                userView.setText(String.format(Locale.US, "%s : %d/%d",user.getUsername(), user.getPublicRepos(), user.getPublicGists()));
                container.addView(userView);
          }
      }, new Action1() {
        @Override
      public void call(Throwable throwable) {
          throwable.printStackTrace();
      }
});

好吧,我承认RxJava让上面代码的可读性变差了,但是这里的RxJava链式操作写的非常漂亮,对filter、flatmap、map等几个操作符的使用的也非常到位(膜拜ing!!!),如果你了解这些操作符,那么你就会喜欢上RxJava这种编程方式。如果不了解RxJava操作符的童鞋可以先看看这篇文章;

Android数据库是比较轻量级的,数据存储量不宜过大、创建的数据表不宜太复杂。我们常用的操作在官网上全部可以找得到,这里算是给大家一个引子。当然Square公司的sqlbrite也是一个不错的选择;

至于上面说的两个数据库,大家可以自行选择使用:

  • SQLite是官方推荐的数据库,而且里面很多“坑”也已经被开发者们踩过了,开发过程中遇到的任何问题应该都可以找到解决方法;但是SQLite的使用的方法非常麻烦!
  • 虽然目前Realm还是beta版本,但是使用简单,功能也越发强大,并且基本上完全满足我们需求,同时它还能够结合很多流行的开源库来使用,写起来还是很“酷”的;

如果上面有不正确的地方,请大家指正!



第九篇:开源资源篇

简单讲了SQLite的使用方法和开源数据库Realm配合RxJava的使用方法;本篇我主要总结上面用到的开源库,让大家避免重复造轮子;

开源项目:阅读别人的代码是一种更好学习的方式

  • gank.io:每天提供技术干货的App;
  • coding:Coding官方客户端;
  • u2020:Jake Whart大神写的App,必须支持!

这里查看更多的Github 上的Android优秀开源项目;

开源库:避免重复造轮子

  • RxJava:RxJava 是一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库, 简单来说它就是一个实现异步操作的库, RxJava 的优点在于一个词 "简洁",配合RxJava的操作符使用,让开发更加高效;
  • Retrofit:目前最流行的HTTP请求库
  • OkHttp:Square的另一网络请求库
  • Picasso/ Fresco/glide:图片加载库(square/facebook/google)
  • Realm-Java:专为移动平台打造的数据库
  • json在线解析:将json快速解析成为Java Bean
  • ButterKnife:强大的View注入框架,免去很多findviewbyid的操作,配合Android Studio的插件使用效果更好;
  • EventBus/Otto/RxBus:三者都是事件管理总线,让消息传递过程中逻辑代码与View层高度解耦;建议使用RxBus,毕竟是RxJava拓展;
  • leakcanary:内存泄漏检测;

几个关于UI的网站:个人开发者的福利

  • Material Design:Material Design的官方介绍;
  • materialup:每天更新一些优秀的设计,MD风格和iOS风格都有;
  • dribbble:做设计的应该都知道这个网站;
  • materialpalette:MD风格颜色搭配的神器;
  • awesome android ui:github上面总结出来的各种MD使用;
  • easyicon:图标搜索神器;
  • 花瓣网:国内最棒的图片搜索工具;

我认为较好的资源已经全部放在上面了,欢迎大家补充!


star me on Github
本篇结束以后,我们就基本上可以独自开发一些Android的小型的App了,接下来的两篇我会介绍一些比较进阶性的开发,如Android自定义控件和跨进程通信;



高阶篇:
第十篇:自定义控件篇

我整理了一些开源的资源供大家开发参考,至此,也就完成了Android App开发的基本流程;本篇我就结合实际来谈一谈Android的自定义控件的流程;

自定义控件是每个Android开发人员都需要经历的一个过程,在实际开发过程中,需要自定义控件的地方很多,很多自定义控件也往往伴随着动画的使用,今天就和大家一起做一个loading的自定义控件,如下图;

从零开始开发一款Android App_第12张图片
screenshot.gif

好吧,这个loading的控件是我在github上找:看源代码;

先说说概念

 为什么要自定义控件?

  • 提升用户交互体验;
  • 搭配App的整体风格;
  • 优化布局;
  • ......

 自定义控件分为两种:

  1. 继承自View;
  2. 继承自TextView、ImageView等现成的控件;

第一种需要我们从无到有来实现我们想要的控件,第二种只需要在原有的控件功能的基础上来拓展一些功能;

再说说流程

自定义控价的基本流程:

  1. 自定义属性的声明(类似于xml中的android:width)
    • 在res/values/attrs.xml定义声明
    • 在xml文件中使用或者在view构造函数中获取
  2. 实现onMeasure(),控件的测量
    • MeasureSpec.getMode(xxx)和MeasureSpec.getSize(xxx),获取用户从xml中设置的控件的大小;(Mode的三种模式,EXACTLY, AT_MOST, UNSPECIFIED)
    • setMeasuredDimension
    • requestLayout()
  3. 实现onLayout(),控件的布局的位置(只在ViewGroup中使用)
    • 布局子View的位置
  4. 实现onDraw()方法,控件的绘制
    • 绘制内容
    • 使用Canvas api中的一些方法
    • 当发生改变时,重新绘制invalidate() postInvalidate()
  5. onTachEvent()/onInterceptTouchEvent(ViewGroup)
    • 用户交互实现
    • 触摸事件、点击事件、多指触控等
    • viewGroup中拦截子View事件拦截

Code Time

按照上面的基本流程,我们就来实现一下loading的自定义。
通过分析:我们发现要实现上面的loading需要分为两个步骤:

  1. 自定义小球,实现一个loading的小球
  2. 取5个第一步中自定义出来的小球,水平排列起来,然后再进行小球的动画渲染

自定义小球:就是我们自定义View的第一种,extends View
1、自定义属性:
分析:我们的小球只需要改变颜色,则在attrs.xml中将颜色属性定义出来;

  
      
          
      
 

2、自定义View:

public class SingleBall extends View {

    private int loadingColor = Color.BLUE;
    ......
    // 初始化View,应用自定义属性
    private void initView(AttributeSet attrs){
        if (null != attrs){
            // 在这里获取我们自定义的各个属性
            TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.SingleBall);
            loadingColor = typedArray.getColor(R.styleable.SingleBall_single_ball_color,Color.BLUE);
            typedArray.recycle();
        }
        //初始化画笔,设置颜色,类型等
        paint = new Paint();
        paint.setColor(loadingColor);
        paint.setStyle(Paint.Style.FILL);
        //这个函数是为了防止边缘锯齿
        paint.setAntiAlias(true);
    }

    // 由于只要关注小球的大小,而且在xml中指定就可以,
    // 所以不需要重写onMeasure方法
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = w;
        height = h;
    }
     // 用canvas画一个圆
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(width/2, height/2, width/2, paint);
    }
    //设置小球的颜色
    public void setLoadingColor(int color){
        loadingColor = color;
        paint.setColor(color);
        postInvalidate();
    }
    public int getLoadingColor(){
        return loadingColor;
    }
}

实现loading
1、xml布局:将五个小球布局到LinearLayout中;


    
    ......
    ......
    

2、自定义LinearLayout:在LinearLayout的基础上拓展了小球的动画效果

public class MyLoading extends LinearLayout {

    private boolean isStart = false;
    public MyLoading(Context context) {
        super(context);
        initView(context);
    }
    ......
    private void initView(Context context) {
        LayoutInflater.from(context).inflate(R.layout.single_ball_loading, this, true);
    } 
     // inflate方法执行结束后执行这个方法
   @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        cradleBallOne = (SingleBall) findViewById(R.id.ball_one);
        cradleBallTwo = (SingleBall) findViewById(R.id.ball_two);
        cradleBallThree = (SingleBall) findViewById(R.id.ball_three);
        cradleBallFour = (SingleBall) findViewById(R.id.ball_four);
        cradleBallFive = (SingleBall) findViewById(R.id.ball_five);
        initAnim();
    }
    RotateAnimation rotateLeftAnimation;//旋转动画效果
    RotateAnimation rotateRightAnimation;
    TranslateAnimation shakeLeftAnimation;// 位移动画效果
    TranslateAnimation shakeRightAnimation;

    //部分代码:初始化动画效果
    //动画:左右两边的小球会向上旋转30度,中间的小球会有小幅度的水平位移震动
    private void initAnim() {
        rotateRightAnimation = new RotateAnimation(0, -DEGREE, RotateAnimation.RELATIVE_TO_SELF, PIVOT_X, RotateAnimation.RELATIVE_TO_SELF, PIVOT_Y);
        rotateRightAnimation.setRepeatCount(1);
        rotateRightAnimation.setRepeatMode(Animation.REVERSE);
        rotateRightAnimation.setDuration(DURATION);
        rotateRightAnimation.setInterpolator(new LinearInterpolator());
        rotateRightAnimation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
            }
            @Override
            public void onAnimationEnd(Animation animation) {
                if (isStart)
                    startRightAnim();
            }
            @Override
            public void onAnimationRepeat(Animation animation) {
            }
        });
        shakeLeftAnimation = new TranslateAnimation(0, SHAKE_DISTANCE, 0, 0);
        shakeLeftAnimation.setDuration(DURATION);
        shakeLeftAnimation.setInterpolator(new CycleInterpolator(2));
        ......
        ......      

    }
    private void startRightAnim() {
        cradleBallTwo.startAnimation(shakeRightAnimation);
        cradleBallThree.startAnimation(shakeRightAnimation);
        cradleBallFour.startAnimation(shakeRightAnimation);
    }
    //动画开始
    public void start() {
        if (!isStart) {
            isStart = true;
            startLeftAnim();
        }
    }
    //动画停止
    public void stop() {
        isStart = false;
        cradleBallOne.clearAnimation();
        cradleBallTwo.clearAnimation();
        cradleBallThree.clearAnimation();
        cradleBallFour.clearAnimation();
        cradleBallFive.clearAnimation();
    }
    public boolean isStart() {
        return isStart;
    }
    public void setLoadingColor(int color) {
        cradleBallOne.setLoadingColor(color);
        cradleBallTwo.setLoadingColor(color);
        cradleBallThree.setLoadingColor(color);
        cradleBallFour.setLoadingColor(color);
        cradleBallFive.setLoadingColor(color);
    }
}

Android SDK为我们提供了四种动画效果:

  • AlphaAnimation 透明度动画效果
  • ScaleAnimation 缩放动画效果
  • TranslateAnimation 位移动画效果
  • RotateAnimation 旋转动画效果

我们实现loading的时候用到了位移动画效果和旋转动画效果两种,由于动画不是本篇的重点,所以这里就不深入介绍了。如果有兴趣可以自行google。

使用Myloading
Xml:

Java:

  (MyLoading)findViewById(R.id.loading).start()/.stop()

至此,自定义loading控件结束!


其实自定义控件并不是我们所认为的那么难,只是我们不了解而已!如果多练习几次,自定义控件也就变的很容易了。大家也可以看看其他的自定义控件的文章如:自定义view之仿QQ健康ArcProgressbar

如果发现有错误的地方,大家可以在评论中指出!



第十一篇:跨进程篇

我为大家介绍了Android开发中自定义控件,同时也讲解了Github 上的一个开源loading的实现,本篇就来说一下Android的跨进程通信;

说起Android的跨进程通信,我们通常会听到IPC、Binder和AIDL这些名词,但是这些都代表什么意思呢?今天我们就主要来说一说跨进程的概念。

IPC(进程间通信)

大家都知道,Android系统是Linux内核,在Linux系统中为了维护系统的稳定,采用了进程间隔离的原则,即各个进程之间是互相独立,互不影响的。在Android系统中,也充分利用了这一原则,不允许将某个App应用程序的进程与系统的进程绑定,从而提高了Android系统的稳定性;而不同进程之间相互配合、相互支持、相互通信,就要依赖于IPC(进程间通信);

Linux 系统为进程间通信提供了很多方法,具体的看这里,常用的有:

  • 管道(Pipe)及命名管道(named pipe)
  • 信号(Signal)
  • 消息队列(Message)
  • 共享内存
  • 信号量(semaphore)
  • 套接字(Socket)
  • ......

但是Android系统作为嵌入式移动操作系统,其通信相对于Linux还有一定的特殊性:如内存受限、权限问题等。针对Android平台的特殊性,上面Linux IPC方法在这里并不适用,即不能完全复用。所以,在Android平台推出其特有的IPC--Binder。

Binder

上面提到Binder是Android平台特有的IPC机制,其实Binder就是对IPC的具体实现和具体实行;

Binder通信采用的是client/server通信结构,Binder的架构分为:client、server、Service Manager和Binder驱动程序。

看一张我盗来的图:

从零开始开发一款Android App_第13张图片
Binder架构.png

解释一下上图:处于用户态的client和server之间的通信是由Binder驱动程序和Service Manager两个组件协助,这两个组件Android系统已经实现好并运行在系统内核中,开发者只需要按照框架规范实现client和server接口来进行Android 跨进程;

IPC的过程:Client通过Binder驱动程序向Service Manager发送请求,在Service Manager查找系统中已经注册了的Service,如果和Client的请求相匹配,给Client返回Binder对象,请求结束。通信开始建立;

AIDL

上面提到,开发者需要按照规范实现client和server接口来进行跨进程通信,这里实现的接口就要用到我们AIDL(接口定义语言):它可以用于让某个Service与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service的功能;
举一个栗子,在Service中写一个加法的方法,然后在Client中调用方法。Service和Client不在同一进程中;看一下代码;

写一个.aidl接口文件,MyAidl.aidl

interface MyAidl { 
    int add(int a, int b);
}

保存之后,Android Studio会在 generated/source/aidl/debug/<包名> 的目录下生成对应的java文件:

public interface MyAidl extends android.os.IInterface{
     public int add(int a, int b);
     public static abstract class Stub extends android.os.Binder implements <包名>.MyAidl
}

接下来在Service实现接口并重写方法,Client中实现方法的调用即可;由于这个过程比较复杂,所以这里推荐几篇blog,写更加详细,大家可以参考一下:

Android Service完全解析,关于服务你所需知道的一切(下)
AIDL与Binder框架浅谈
Android Binder 完全解析


Android跨进程的基本概念就是这样,希望我将IPC、Binder和AIDL三个概念给大家讲解清楚了。由于以前没有实践过,所以我在本篇没有添加写代码,希望大家谅解!各位如果发现有错误的地方,请在评论指出;



最后的话

该系列的文章对Android开发的内容涉及较广,从最基础的开发工具的使用到项目的打包发布,从Android材料设计到网络开发,从数据库的应用到自定义控件。本文也许不能面面俱到地将Android开发的内容全部罗列,但是这一定是对初学者的一份学习大纲。如果读者对其他方面的开发内容有需求,可以在评论留言,我将整理出来分享给大家。感谢大家的支持!



你可能感兴趣的:(从零开始开发一款Android App)