标签: Android 开发

Android 开发笔记本选择

 

想买一台笔记本做 Android 开发,之前一直习惯在 Mac 下面开发,所以更倾向于 Mac,问题来了。

M1 架构的 Mac,听说模拟器支持的稀烂,而我会经常使用模拟器。 Macbook Pro 16 寸的,CPU 还是 9 代的,现在入,总觉得差点意思。

有人建议买个高配的 Intel CPU 的 Windows 笔记本,弄成黑苹果,感觉太麻烦了。

所以想整个舒服点的笔记本做开发,现在入 Macbook Pro 16 寸是否合适,听有传言说 9 月份就要发布新的 16 寸的,也不知道是否有 Intel CPU 版本,相当的头疼。

各位彦祖们给个意见吧,多谢

20 条回复    2021-07-27 15:55:29 +08:00

kop1989
    1

kop1989   6 天前   ❤️ 1

1 、有 M1 支持的 Android Studio 和 AVG 。
2 、目前蹩脚的是 AVG 很难安装市面的第三方 APK,因为第三方多数都是 armeabi-v7a 。

所以如果你没有在模拟器上装第三方 apk 的打算,那么目前 M1 是可以胜任的。

lz 剩下的决策顾虑就跟 Android 开发环境没什么关系了。

jinhan13789991
    2

jinhan13789991   6 天前

买 Macbook Pro,出了新版再换
skye
    3

skye   5 天前

mac pro 16 的 intel 版本,m1 搞 android 开发会让你怀疑人生
ntop
    4

ntop   5 天前

我觉得就买 M1 就好,问题都是可以解决的。但是旧款的本本不能变成新款,买新款早享受。
sankemao
    5

sankemao   5 天前

不知道 win11 自带的 android 怎么样
Ackvincent
    6

Ackvincent   5 天前

win 的笔记本装个 Ubuntu 不也挺香的?
个人推荐 dell 的 xps 系列或者 Thinkpad 的 x 系列。
我自己用的是一台 i7-10710U+32G+1T ( SSD )+2T (机械), 系统是 Kali,日常使用爽歪歪。
eminemcola
    7

eminemcola   5 天前

如果一定要买 Mac 的话还是等等看 16 寸 Intel rmbp 会不会更新吧。现目前的 M1 Mac 还没有达到可以在工作环境中完成 Android 开发的程度。
可以参考这篇文章:
https://medium.com/mobile-app-development-publication/apple-m1-is-just-not-ready-for-mobile-development-yet-95735f84d8db
zjsxwc
    8

zjsxwc   5 天前 via Android

买苹果的钱还不如买顶配国产笔记本装 Linux,用 Linux 开发安卓,

话说有了蓝湖后,不再需要用 ps 等软件来看 psd 稿子像素大小,有浏览器就行

对于安卓开发者来说,现在 Linux 完全够用,*少数情况软件不够就 win 虚拟机凑合用。

gouki
    9

gouki   5 天前

@eminemcola #7 除了不太能用模拟器。真机其实不影响
gouki
    10

gouki   5 天前

@zjsxwc #8 这个倒确实。

quella
    11

quella   4 天前

我现在用 m1 模拟器还可以吧,挺流畅。
zpxshl
    12

zpxshl   4 天前 via Android

linux 开发 android 和 mac 开发体验还是不一样的。
有时间且乐于折腾的话,当我没说
20015jjw
    13

20015jjw   4 天前 via Android

看你项目大小
反正我的项目 16 寸顶中顶挺卡
还是得 mac pro 才行
aladdinding
    14

aladdinding   4 天前

开发的话别买 m1 的
2bab
    15

2bab   4 天前

@eminemcola 其实这篇文章下面的评论都是觉得自己用在生产环境没有问题的…Medium 把评论隐藏起来有时候不太友好…我在自己的 M1 Mac Mini 做 Android side project 也觉得不错了~
reanfly
    16

reanfly   3 天前

我也是 android 开发,苹果等等吧,m1 内存 16g 真有点勉强。等不到 m2 了。我的 15 年 mbp 实在是 hold 不住。目前暂时买了 amd 5900hx 笔记本,快到是挺快,win 系统开发还是没有 mac os 舒服呀。
juncat
    17

juncat   3 天前

M1 开发 Android 的话,有的库可能会找不到 osx-aarch_64 的依赖包,一般新版本会更新支持,但是如果是公司项目不方便升级的话,就很难受,例如 [protocolbuffers]( https://github.com/protocolbuffers/protobuf/pull/8557), [AndResGuard]( https://github.com/shwenzhang/AndResGuard/issues/444)。
longforus
    18

longforus   3 天前   ❤️ 1

@Ackvincent mac 我也有,黑苹果也用过,搞到*后还是觉得
win10+wsl 方便好用,不但日常开发,逆向也行,各种工具都 OK,我支持
dingwen07
    19

dingwen07   3 天前 via iPhone

Linux Desktop
Kmmoonlight
    20

Kmmoonlight   2 天前

Android 开发用真机

框架模式 MVC 在Android中的使用

在学习Android开发2年的历史中,基本掌握了Android的基础知识。越到后面的学习越感觉困难,一来是自认为android没啥可学的了(自认为的,其实还有很多知识科学),二来网络上的很多框架已经帮我们做了太多的事情了,我们只需要画画UI就可以了,感觉Android开发没有太多的技术含金量。*近闲来无事,开始总结之前学过的知识点,想着是否应该学点其他的东西呢?总不能局限于Android基础知识吧。慢慢的探索发现在大的项目工程中,一个好的框架,好的设计模式,能减少很大的工作量。因此接下来两篇博客来学习一下Android中常用的两种框架设计模式 MVC和MVP。

MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。其中M层处理数据,业务逻辑等;V层处理界面的显示结果;C层起到桥梁的作用,来控制V层和M层通信以此来达到分离视图显示和业务逻辑层。说了这么多,听着感觉很抽象,废话不多说,我们来看看MVC在Android开发中是怎么应用的吧!

 

MVC for Android

在Android开发中,比较流行的开发框架模式采用的是MVC框架模式,采用MVC模式的好处是便于UI界面部分的显示和业务逻辑,数据处理分开。那么Android项目中哪些代码来充当M,V,C角色呢?

M层:适合做一些业务逻辑处理,比如数据库存取操作,网络操作,复杂的算法,耗时的任务等都在model层处理。 V层:应用层中处理数据显示的部分,XML布局可以视为V层,显示Model层的数据结果。 C层:在Android中,Activity处理用户交互问题,因此可以认为Activity是控制器,Activity读取V视图层的数据(eg.读取当前EditText控件的数据),控制用户输入(eg.EditText控件数据的输入),并向Model发送数据请求(eg.发起网络请求等)。

接下来我们通过一个获取天气预报数据的小项目来解读 MVC for Android。先上一个界面图:

加载中...

Controller控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package com.***.androidmvcdemo.controller;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.***.androidmvcdemo.R;
import com.***.androidmvcdemo.entity.Weather;
import com.***.androidmvcdemo.entity.WeatherInfo;
import com.***.androidmvcdemo.model.OnWeatherListener;
import com.***.androidmvcdemo.model.WeatherModel;
import com.***.androidmvcdemo.model.WeatherModelImpl;
public class MainActivity extends ActionBarActivity implements OnWeatherListener, View.OnClickListener {
    private WeatherModel weatherModel;
    private Dialog loadingDialog;
    private EditText cityNOInput;
    private TextView city;
    private TextView cityNO;
    private TextView temp;
    private TextView wd;
    private TextView ws;
    private TextView sd;
    private TextView wse;
    private TextView time;
    private TextView njd;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        weatherModel = new WeatherModelImpl();
        initView();
    }
    /**
     * 初始化View
     */
    private void initView() {
        cityNOInput = findView(R.id.et_city_no);
        city = findView(R.id.tv_city);
        cityNO = findView(R.id.tv_city_no);
        temp = findView(R.id.tv_temp);
        wd = findView(R.id.tv_WD);
        ws = findView(R.id.tv_WS);
        sd = findView(R.id.tv_SD);
        wse = findView(R.id.tv_WSE);
        time = findView(R.id.tv_time);
        njd = findView(R.id.tv_njd);
        findView(R.id.btn_go).setOnClickListener(this);
        loadingDialog = new ProgressDialog(this);
        loadingDialog.setTitle(加载天气中...);
    }
    /**
     * 显示结果
     *
     * @param weather
     */
    public void displayResult(Weather weather) {
        WeatherInfo weatherInfo = weather.getWeatherinfo();
        city.setText(weatherInfo.getCity());
        cityNO.setText(weatherInfo.getCityid());
        temp.setText(weatherInfo.getTemp());
        wd.setText(weatherInfo.getWD());
        ws.setText(weatherInfo.getWS());
        sd.setText(weatherInfo.getSD());
        wse.setText(weatherInfo.getWSE());
        time.setText(weatherInfo.getTime());
        njd.setText(weatherInfo.getNjd());
    }
    /**
     * 隐藏进度对话框
     */
    public void hideLoadingDialog() {
        loadingDialog.dismiss();
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_go:
                loadingDialog.show();
                weatherModel.getWeather(cityNOInput.getText().toString().trim(), this);
                break;
        }
    }
    @Override
    public void onSuccess(Weather weather) {
        hideLoadingDialog();
        displayResult(weather);
    }
    @Override
    public void onError() {
        hideLoadingDialog();
        Toast.makeText(this, 获取天气信息失败, Toast.LENGTH_SHORT).show();
    }
    private  T findView(int id) {
        return (T) findViewById(id);
    }
}

从上面代码可以看到,Activity持有了WeatherModel模型的对象,当用户有点击Button交互的时候,Activity作为Controller控制层读取View视图层EditTextView的数据,然后向Model模型发起数据请求,也就是调用WeatherModel对象的方法 getWeathre()方法。当Model模型处理数据结束后,通过接口OnWeatherListener通知View视图层数据处理完毕,View视图层该更新界面UI了。然后View视图层调用displayResult()方法更新UI。至此,整个MVC框架流程就在Activity中体现出来了。

Model模型

来看看WeatherModelImpl代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.***.androidmvcdemo.model;
/**
 * Description:请求网络数据接口
 * User: ***
 * Date: 2015/6/3
 * Time: 15:40
 */
public interface WeatherModel {
    void getWeather(String cityNumber, OnWeatherListener listener);
}
................
package com.***.androidmvcdemo.model;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.***.androidmvcdemo.entity.Weather;
import com.***.androidmvcdemo.volley.VolleyRequest;
/**
 * Description:从网络获取天气信息接口实现
 * User: ***
 * Date: 2015/6/3
 * Time: 15:40
 */
public class WeatherModelImpl implements WeatherModel {
    @Override
    public void getWeather(String cityNumber, final OnWeatherListener listener) {
        /*数据层操作*/
        VolleyRequest.newInstance().newGsonRequest(http://www.weather.com.cn/data/sk/ + cityNumber + .html,
                Weather.class, new Response.Listener() {
                    @Override
                    public void onResponse(Weather weather) {
                        if (weather != null) {
                            listener.onSuccess(weather);
                        } else {
                            listener.onError();
                        }
                    }
                }, new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        listener.onError();
                    }
                });
    }
}

以上代码看出,这里设计了一个WeatherModel模型接口,然后实现了接口WeatherModelImpl类。controller控制器activity调用WeatherModelImpl类中的方法发起网络请求,然后通过实现OnWeatherListener接口来获得网络请求的结果通知View视图层更新UI 。至此,Activity就将View视图显示和Model模型数据处理隔离开了。activity担当contronller完成了model和view之间的协调作用。

至于这里为什么不直接设计成类里面的一个getWeather()方法直接请求网络数据?你考虑下这种情况:现在代码中的网络请求是使用Volley框架来实现的,如果哪天老板非要你使用Afinal框架实现网络请求,你怎么解决问题?难道是修改 getWeather()方法的实现? no no no,这样修改不仅破坏了以前的代码,而且还不利于维护, 考虑到以后代码的扩展和维护性,我们选择设计接口的方式来解决着一个问题,我们实现另外一个WeatherModelWithAfinalImpl类,继承自WeatherModel,重写里面的方法,这样不仅保留了以前的WeatherModelImpl类请求网络方式,还增加了WeatherModelWithAfinalImpl类的请求方式。Activity调用代码无需要任何修改。

MVC使用总结

利用MVC设计模式,使得这个天气预报小项目有了很好的可扩展和维护性,当需要改变UI显示的时候,无需修改Contronller(控制器)Activity的代码和Model(模型)WeatherModel模型中的业务逻辑代码,很好的将业务逻辑和界面显示分离。

在Android项目中,业务逻辑,数据处理等担任了Model(模型)角色,XML界面显示等担任了View(视图)角色,Activity担任了Contronller(控制器)角色。contronller(控制器)是一个中间桥梁的作用,通过接口通信来协同 View(视图)和Model(模型)工作,起到了两者之间的通信作用。

什么时候适合使用MVC设计模式?当然一个小的项目且无需频繁修改需求就不用MVC框架来设计了,那样反而觉得代码过度设计,代码臃肿。一般在大的项目中,且业务逻辑处理复杂,页面显示比较多,需要模块化设计的项目使用MVC就有足够的优势了。

4.在MVC模式中我们发现,其实控制器Activity主要是起到解耦作用,将View视图和Model模型分离,虽然Activity起到交互作用,但是找Activity中有很多关于视图UI的显示代码,因此View视图和Activity控制器并不是完全分离的,也就是说一部分View视图和Contronller控制器Activity是绑定在一个类中的。

MVC的优点:

(1)耦合性低。所谓耦合性就是模块代码之间的关联程度。利用MVC框架使得View(视图)层和Model(模型)层可以很好的分离,这样就达到了解耦的目的,所以耦合性低,减少模块代码之间的相互影响。

(2)可扩展性好。由于耦合性低,添加需求,扩展代码就可以减少修改之前的代码,降低bug的出现率。

(3)模块职责划分明确。主要划分层M,V,C三个模块,利于代码的维护。

Android 开发者必看的 RxJava 详解

前言

我从去年开始使用 RxJava ,到现在一年多了。今年加入了 Flipboard 后,看到 Flipboard 的 Android 项目也在使用 RxJava ,并且使用的场景越来越多 。而*近这几个月,我也发现国内越来越多的人开始提及 RxJava 。有人说『RxJava 真是太好用了』,有人说『RxJava 真是太难用了』,另外更多的人表示:我真的百度了也谷歌了,但我还是想问: RxJava 到底是什么?

鉴于 RxJava 目前这种既火爆又神秘的现状,而我又在一年的使用过程中对 RxJava 有了一些理解,我决定写下这篇文章来对 RxJava 做一个相对详细的、针对 Android 开发者的介绍。

这篇文章的目的有两个: 1. 给对 RxJava 感兴趣的人一些入门的指引 2. 给正在使用 RxJava 但仍然心存疑惑的人一些更深入的解析

  • RxJava 到底是什么
  • RxJava 好在哪
  • API 介绍和原理简析
    • 1. 概念:扩展的观察者模式
      • 观察者模式
      • RxJava 的观察者模式
    • 2. 基本实现
      • 1) 创建 Observer
      • 2) 创建 Observable
      • 3) Subscribe (订阅)
      • 4) 场景示例
        • a. 打印字符串数组
        • b. 由 id 取得图片并显示
    • 3. 线程控制 —— Scheduler (一)
      • 1) Scheduler 的 API (一)
      • 2) Scheduler 的原理 (一)
    • 4. 变换
      • 1) API
      • 2) 变换的原理:lift()
      • 3) compose: 对 Observable 整体的变换
    • 5. 线程控制:Scheduler (二)
      • 1) Scheduler 的 API (二)
      • 2) Scheduler 的原理(二)
      • 3) 延伸:doOnSubscribe()
  • RxJava 的适用场景和使用方式
    • 1. 与 Retrofit 的结合
    • 2. RxBinding
    • 3. 各种异步操作
    • 4. RxBus
  • *后
    • 关于作者:
    • 为什么写这个?

在正文开始之前的*后,放上 GitHub 链接和引入依赖的 gradle 代码: Github:
https://github.com/ReactiveX/RxJava
https://github.com/ReactiveX/RxAndroid
引入依赖:
compile 'io.reactivex:rxjava:1.0.14'
compile 'io.reactivex:rxandroid:1.0.1'
(版本号是文章发布时的*新稳定版)

另外,感谢 RxJava 核心成员流火枫林的技术支持和内测读者代码家、鲍永章、drakeet、马琳、有时放纵、程序亦非猿、大头鬼、XZoomEye、席德雨、TCahead、Tiiime、Ailurus、宅学长、妖孽、大大大大大臣哥、NicodeLee的帮助,以及周伯通招聘的赞助。

RxJava 到底是什么

一个词:异步

RxJava 在 GitHub 主页上的自我介绍是 “a library for composing asynchronous and event-based programs using observable sequences for the Java VM”(一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库)。这就是 RxJava ,概括得非常精准。

然而,对于初学者来说,这太难看懂了。因为它是一个『总结』,而初学者更需要一个『引言』。

其实, RxJava 的本质可以压缩为异步这一个词。说到根上,它就是一个实现异步操作的库,而别的定语都是基于这之上的。

RxJava 好在哪

换句话说,『同样是做异步,为什么人们用它,而不用现成的 AsyncTask / Handler / XXX / … ?』

一个词:简洁

异步操作很关键的一点是程序的简洁性,因为在调度过程比较复杂的情况下,异步代码经常会既难写也难被读懂。 Android 创造的 AsyncTask 和Handler ,其实都是为了让异步代码更加简洁。RxJava 的优势也是简洁,但它的简洁的与众不同之处在于,随着程序逻辑变得越来越复杂,它依然能够保持简洁。

举个例子

假设有这样一个需求:界面上有一个自定义的视图 imageCollectorView ,它的作用是显示多张图片,并能使用 addImage(Bitmap) 方法来任意增加显示的图片。现在需要程序将一个给出的目录数组 File[] folders 中每个目录下的 png 图片都加载出来并显示在 imageCollectorView 中。需要注意的是,由于读取图片的这一过程较为耗时,需要放在后台执行,而图片的显示则必须在 UI 线程执行。常用的实现方式有多种,我这里贴出其中一种:

new Thread() {
    @Override
    public void run() {
        super.run();
        for (File folder : folders) {
            File[] files = folder.listFiles();
            for (File file : files) {
                if (file.getName().endsWith(".png")) {
                    final Bitmap bitmap = getBitmapFromFile(file);
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            imageCollectorView.addImage(bitmap);
                        }
                    });
                }
            }
        }
    }
}.start();

而如果使用 RxJava ,实现方式是这样的:

Observable.from(folders)
    .flatMap(new Func1<File, Observable<File>>() {
        @Override
        public Observable<File> call(File file) {
            return Observable.from(file.listFiles());
        }
    })
    .filter(new Func1<File, Boolean>() {
        @Override
        public Boolean call(File file) {
            return file.getName().endsWith(".png");
        }
    })
    .map(new Func1<File, Bitmap>() {
        @Override
        public Bitmap call(File file) {
            return getBitmapFromFile(file);
        }
    })
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<Bitmap>() {
        @Override
        public void call(Bitmap bitmap) {
            imageCollectorView.addImage(bitmap);
        }
    });

那位说话了:『你这代码明明变多了啊!简洁个毛啊!』大兄弟你消消气,我说的是逻辑的简洁,不是单纯的代码量少(逻辑简洁才是提升读写代码速度的必杀技对不?)。观察一下你会发现, RxJava 的这个实现,是一条从上到下的链式调用,没有任何嵌套,这在逻辑的简洁性上是具有优势的。当需求变得复杂时,这种优势将更加明显(试想如果还要求只选取前 10 张图片,常规方式要怎么办?如果有更多这样那样的要求呢?再试想,在这一大堆需求实现完两个月之后需要改功能,当你翻回这里看到自己当初写下的那一片迷之缩进,你能保证自己将迅速看懂,而不是对着代码重新捋一遍思路?)。

另外,如果你的 IDE 是 Android Studio ,其实每次打开某个 Java 文件的时候,你会看到被自动 Lambda 化的预览,这将让你更加清晰地看到程序逻辑:

Observable.from(folders)
    .flatMap((Func1) (folder) -> { Observable.from(file.listFiles()) })
    .filter((Func1) (file) -> { file.getName().endsWith(".png") })
    .map((Func1) (file) -> { getBitmapFromFile(file) })
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe((Action1) (bitmap) -> { imageCollectorView.addImage(bitmap) });

如果你习惯使用 Retrolambda ,你也可以直接把代码写成上面这种简洁的形式。而如果你看到这里还不知道什么是 Retrolambda ,我不建议你现在就去学习它。原因有两点:1. Lambda 是把双刃剑,它让你的代码简洁的同时,降低了代码的可读性,因此同时学习 RxJava 和 Retrolambda 可能会让你忽略 RxJava 的一些技术细节;2. Retrolambda 是 Java 6/7 对 Lambda 表达式的非官方兼容方案,它的向后兼容性和稳定性是无法保障的,因此对于企业项目,使用 Retrolambda 是有风险的。所以,与很多 RxJava 的推广者不同,我并不推荐在学习 RxJava 的同时一起学习 Retrolambda。事实上,我个人虽然很欣赏 Retrolambda,但我从来不用它。

在Flipboard 的 Android 代码中,有一段逻辑非常复杂,包含了多次内存操作、本地文件操作和网络操作,对象分分合合,线程间相互配合相互等待,一会儿排成人字,一会儿排成一字。如果使用常规的方法来实现,肯定是要写得欲仙欲死,然而在使用 RxJava 的情况下,依然只是一条链式调用就完成了。它很长,但很清晰。

所以, RxJava 好在哪?就好在简洁,好在那把什么复杂逻辑都能穿成一条线的简洁。

API 介绍和原理简析

这个我就做不到一个词说明了……因为这一节的主要内容就是一步步地说明 RxJava 到底怎样做到了异步,怎样做到了简洁。

1. 概念:扩展的观察者模式

RxJava 的异步实现,是通过一种扩展的观察者模式来实现的。

观察者模式

先简述一下观察者模式,已经熟悉的可以跳过这一段。

观察者模式面向的需求是:A 对象(观察者)对 B 对象(被观察者)的某种变化高度敏感,需要在 B 变化的一瞬间做出反应。举个例子,新闻里喜闻乐见的警察抓小偷,警察需要在小偷伸手作案的时候实施抓捕。在这个例子里,警察是观察者,小偷是被观察者,警察需要时刻盯着小偷的一举一动,才能保证不会漏过任何瞬间。程序的观察者模式和这种真正的『观察』略有不同,观察者不需要时刻盯着被观察者(例如 A 不需要每过 2ms 就检查一次 B 的状态),而是采用注册(Register)或者称为订阅(Subscribe)的方式,告诉被观察者:我需要你的某某状态,你要在它变化的时候通知我。 Android 开发中一个比较典型的例子是点击监听器 OnClickListener 。对设置 OnClickListener 来说, View 是被观察者, OnClickListener 是观察者,二者通过 setOnClickListener() 方法达成订阅关系。订阅之后用户点击按钮的瞬间,Android Framework 就会将点击事件发送给已经注册的 OnClickListener 。采取这样被动的观察方式,既省去了反复检索状态的资源消耗,也能够得到*高的反馈速度。当然,这也得益于我们可以随意定制自己程序中的观察者和被观察者,而警察叔叔明显无法要求小偷『你在作案的时候务必通知我』。

OnClickListener 的模式大致如下图:

OnClickListener 观察者模式

如图所示,通过 setOnClickListener() 方法,Button 持有 OnClickListener 的引用(这一过程没有在图上画出);当用户点击时,Button 自动调用 OnClickListener 的 onClick() 方法。另外,如果把这张图中的概念抽象出来(Button -> 被观察者、OnClickListener -> 观察者、setOnClickListener() -> 订阅,onClick() -> 事件),就由专用的观察者模式(例如只用于监听控件点击)转变成了通用的观察者模式。如下图:

通用观察者模式

而 RxJava 作为一个工具库,使用的就是通用形式的观察者模式。

RxJava 的观察者模式

RxJava 有四个基本概念:Observable (可观察者,即被观察者)、 Observer (观察者)、 subscribe (订阅)、事件。Observable 和 Observer 通过 subscribe() 方法实现订阅关系,从而 Observable 可以在需要的时候发出事件来通知 Observer

与传统观察者模式不同, RxJava 的事件回调方法除了普通事件 onNext() (相当于 onClick() / onEvent())之外,还定义了两个特殊的事件:onCompleted() 和 onError()

  • onCompleted(): 事件队列完结。RxJava 不仅把每个事件单独处理,还会把它们看做一个队列。RxJava 规定,当不会再有新的 onNext() 发出时,需要触发 onCompleted() 方法作为标志。
  • onError(): 事件队列异常。在事件处理过程中出异常时,onError() 会被触发,同时队列自动终止,不允许再有事件发出。
  • 在一个正确运行的事件序列中, onCompleted() 和 onError() 有且只有一个,并且是事件序列中的*后一个。需要注意的是,onCompleted() 和 onError() 二者也是互斥的,即在队列中调用了其中一个,就不应该再调用另一个。

RxJava 的观察者模式大致如下图:

RxJava 的观察者模式

2. 基本实现

基于以上的概念, RxJava 的基本实现主要有三点:

1) 创建 Observer

Observer 即观察者,它决定事件触发的时候将有怎样的行为。 RxJava 中的 Observer 接口的实现方式:

Observer<String> observer = new Observer<String>() {
    @Override
    public void onNext(String s) {
        Log.d(tag, "Item: " + s);
    }

    @Override
    public void onCompleted() {
        Log.d(tag, "Completed!");
    }

    @Override
    public void onError(Throwable e) {
        Log.d(tag, "Error!");
    }
};

除了 Observer 接口之外,RxJava 还内置了一个实现了 Observer 的抽象类:Subscriber。 Subscriber 对 Observer 接口进行了一些扩展,但他们的基本使用方式是完全一样的:

Subscriber<String> subscriber = new Subscriber<String>() {
    @Override
    public void onNext(String s) {
        Log.d(tag, "Item: " + s);
    }

    @Override
    public void onCompleted() {
        Log.d(tag, "Completed!");
    }

    @Override
    public void onError(Throwable e) {
        Log.d(tag, "Error!");
    }
};

不仅基本使用方式一样,实质上,在 RxJava 的 subscribe 过程中,Observer 也总是会先被转换成一个 Subscriber 再使用。所以如果你只想使用基本功能,选择 Observer 和 Subscriber 是完全一样的。它们的区别对于使用者来说主要有两点:

  1. onStart(): 这是 Subscriber 增加的方法。它会在 subscribe 刚开始,而事件还未发送之前被调用,可以用于做一些准备工作,例如数据的清零或重置。这是一个可选方法,默认情况下它的实现为空。需要注意的是,如果对准备工作的线程有要求(例如弹出一个显示进度的对话框,这必须在主线程执行), onStart() 就不适用了,因为它总是在 subscribe 所发生的线程被调用,而不能指定线程。要在指定的线程来做准备工作,可以使用 doOnSubscribe() 方法,具体可以在后面的文中看到。
  2. unsubscribe(): 这是 Subscriber 所实现的另一个接口 Subscription 的方法,用于取消订阅。在这个方法被调用后,Subscriber 将不再接收事件。一般在这个方法调用前,可以使用 isUnsubscribed() 先判断一下状态。 unsubscribe() 这个方法很重要,因为在 subscribe() 之后, Observable 会持有 Subscriber 的引用,这个引用如果不能及时被释放,将有内存泄露的风险。所以*好保持一个原则:要在不再使用的时候尽快在合适的地方(例如 onPause() onStop() 等方法中)调用 unsubscribe() 来解除引用关系,以避免内存泄露的发生。
2) 创建 Observable

Observable 即被观察者,它决定什么时候触发事件以及触发怎样的事件。 RxJava 使用 create() 方法来创建一个 Observable ,并为它定义事件触发规则:

Observable observable = Observable.create(new Observable.OnSubscribe<String>() {
    @Override
    public void call(Subscriber<? super String> subscriber) {
        subscriber.onNext("Hello");
        subscriber.onNext("Hi");
        subscriber.onNext("Aloha");
        subscriber.onCompleted();
    }
});

可以看到,这里传入了一个 OnSubscribe 对象作为参数。OnSubscribe 会被存储在返回的 Observable 对象中,它的作用相当于一个计划表,当 Observable 被订阅的时候,OnSubscribe 的 call() 方法会自动被调用,事件序列就会依照设定依次触发(对于上面的代码,就是观察者Subscriber 将会被调用三次 onNext() 和一次 onCompleted())。这样,由被观察者调用了观察者的回调方法,就实现了由被观察者向观察者的事件传递,即观察者模式。

这个例子很简单:事件的内容是字符串,而不是一些复杂的对象;事件的内容是已经定好了的,而不像有的观察者模式一样是待确定的(例如网络请求的结果在请求返回之前是未知的);所有事件在一瞬间被全部发送出去,而不是夹杂一些确定或不确定的时间间隔或者经过某种触发器来触发的。总之,这个例子看起来毫无实用价值。但这是为了便于说明,实质上只要你想,各种各样的事件发送规则你都可以自己来写。至于具体怎么做,后面都会讲到,但现在不行。只有把基础原理先说明白了,上层的运用才能更容易说清楚。

create() 方法是 RxJava *基本的创造事件序列的方法。基于这个方法, RxJava 还提供了一些方法用来快捷创建事件队列,例如:

  • just(T...): 将传入的参数依次发送出来。
Observable observable = Observable.just("Hello", "Hi", "Aloha");
// 将会依次调用:
// onNext("Hello");
// onNext("Hi");
// onNext("Aloha");
// onCompleted();
  • from(T[]) / from(Iterable<? extends T>) : 将传入的数组或 Iterable 拆分成具体对象后,依次发送出来。
String[] words = {"Hello", "Hi", "Aloha"};
Observable observable = Observable.from(words);
// 将会依次调用:
// onNext("Hello");
// onNext("Hi");
// onNext("Aloha");
// onCompleted();

上面 just(T...) 的例子和 from(T[]) 的例子,都和之前的 create(OnSubscribe) 的例子是等价的。

3) Subscribe (订阅)

创建了 Observable 和 Observer 之后,再用 subscribe() 方法将它们联结起来,整条链子就可以工作了。代码形式很简单:

observable.subscribe(observer);
// 或者:
observable.subscribe(subscriber);

有人可能会注意到, subscribe() 这个方法有点怪:它看起来是『observalbe 订阅了 observer / subscriber』而不是『observer / subscriber 订阅了 observalbe』,这看起来就像『杂志订阅了读者』一样颠倒了对象关系。这让人读起来有点别扭,不过如果把 API 设计成 observer.subscribe(observable) / subscriber.subscribe(observable) ,虽然更加符合思维逻辑,但对流式 API 的设计就造成影响了,比较起来明显是得不偿失的。

Observable.subscribe(Subscriber) 的内部实现是这样的(仅核心代码):

// 注意:这不是 subscribe() 的源码,而是将源码中与性能、兼容性、扩展性有关的代码剔除后的核心代码。
// 如果需要看源码,可以去 RxJava 的 GitHub 仓库下载。
public Subscription subscribe(Subscriber subscriber) {
    subscriber.onStart();
    onSubscribe.call(subscriber);
    return subscriber;
}

可以看到,subscriber() 做了3件事:

  1. 调用 Subscriber.onStart() 。这个方法在前面已经介绍过,是一个可选的准备方法。
  2. 调用 Observable 中的 OnSubscribe.call(Subscriber) 。在这里,事件发送的逻辑开始运行。从这也可以看出,在 RxJava 中, Observable 并不是在创建的时候就立即开始发送事件,而是在它被订阅的时候,即当 subscribe() 方法执行的时候。
  3. 将传入的 Subscriber 作为 Subscription 返回。这是为了方便 unsubscribe().

整个过程中对象间的关系如下图:

关系静图

或者可以看动图:

关系静图

除了 subscribe(Observer) 和 subscribe(Subscriber) ,subscribe() 还支持不完整定义的回调,RxJava 会自动根据定义创建出 Subscriber 。形式如下:

Action1<String> onNextAction = new Action1<String>() {
    // onNext()
    @Override
    public void call(String s) {
        Log.d(tag, s);
    }
};
Action1<Throwable> onErrorAction = new Action1<Throwable>() {
    // onError()
    @Override
    public void call(Throwable throwable) {
        // Error handling
    }
};
Action0 onCompletedAction = new Action0() {
    // onCompleted()
    @Override
    public void call() {
        Log.d(tag, "completed");
    }
};

// 自动创建 Subscriber ,并使用 onNextAction 来定义 onNext()
observable.subscribe(onNextAction);
// 自动创建 Subscriber ,并使用 onNextAction 和 onErrorAction 来定义 onNext() 和 onError()
observable.subscribe(onNextAction, onErrorAction);
// 自动创建 Subscriber ,并使用 onNextAction、 onErrorAction 和 onCompletedAction 来定义 onNext()、 onError() 和 onCompleted()
observable.subscribe(onNextAction, onErrorAction, onCompletedAction);

简单解释一下这段代码中出现的 Action1 和 Action0。 Action0 是 RxJava 的一个接口,它只有一个方法 call(),这个方法是无参无返回值的;由于 onCompleted() 方法也是无参无返回值的,因此 Action0 可以被当成一个包装对象,将 onCompleted() 的内容打包起来将自己作为一个参数传入 subscribe() 以实现不完整定义的回调。这样其实也可以看做将 onCompleted() 方法作为参数传进了 subscribe(),相当于其他某些语言中的『闭包』。 Action1 也是一个接口,它同样只有一个方法 call(T param),这个方法也无返回值,但有一个参数;与 Action0 同理,由于 onNext(T obj) 和 onError(Throwable error) 也是单参数无返回值的,因此 Action1 可以将 onNext(obj) 和 onError(error) 打包起来传入 subscribe() 以实现不完整定义的回调。事实上,虽然 Action0 和 Action1 在 API 中使用*广泛,但 RxJava 是提供了多个 ActionX 形式的接口 (例如 Action2Action3) 的,它们可以被用以包装不同的无返回值的方法。

注:正如前面所提到的,Observer 和 Subscriber 具有相同的角色,而且 Observer 在 subscribe() 过程中*终会被转换成 Subscriber 对象,因此,从这里开始,后面的描述我将用 Subscriber 来代替 Observer ,这样更加严谨。

4) 场景示例

下面举两个例子:

为了把原理用更清晰的方式表述出来,本文中挑选的都是功能尽可能简单的例子,以至于有些示例代码看起来会有『画蛇添足』『明明不用 RxJava 可以更简便地解决问题』的感觉。当你看到这种情况,不要觉得是因为 RxJava 太啰嗦,而是因为在过早的时候举出真实场景的例子并不利于原理的解析,因此我刻意挑选了简单的情景。

a. 打印字符串数组

将字符串数组 names 中的所有字符串依次打印出来:

String[] names = ...;
Observable.from(names)
    .subscribe(new Action1<String>() {
        @Override
        public void call(String name) {
            Log.d(tag, name);
        }
    });
b. 由 id 取得图片并显示

由指定的一个 drawable 文件 id drawableRes 取得图片,并显示在 ImageView 中,并在出现异常的时候打印 Toast 报错:

int drawableRes = ...;
ImageView imageView = ...;
Observable.create(new OnSubscribe<Drawable>() {
    @Override
    public void call(Subscriber<? super Drawable> subscriber) {
        Drawable drawable = getTheme().getDrawable(drawableRes));
        subscriber.onNext(drawable);
        subscriber.onCompleted();
    }
}).subscribe(new Observer<Drawable>() {
    @Override
    public void onNext(Drawable drawable) {
        imageView.setImageDrawable(drawable);
    }

    @Override
    public void onCompleted() {
    }

    @Override
    public void onError(Throwable e) {
        Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
    }
});

正如上面两个例子这样,创建出 Observable 和 Subscriber ,再用 subscribe() 将它们串起来,一次 RxJava 的基本使用就完成了。非常简单。

然而,

这并没有什么diao用

在 RxJava 的默认规则中,事件的发出和消费都是在同一个线程的。也就是说,如果只用上面的方法,实现出来的只是一个同步的观察者模式。观察者模式本身的目的就是『后台处理,前台回调』的异步机制,因此异步对于 RxJava 是至关重要的。而要实现异步,则需要用到 RxJava 的另一个概念: Scheduler 。

3. 线程控制 —— Scheduler (一)

在不指定线程的情况下, RxJava 遵循的是线程不变的原则,即:在哪个线程调用 subscribe(),就在哪个线程生产事件;在哪个线程生产事件,就在哪个线程消费事件。如果需要切换线程,就需要用到 Scheduler (调度器)。

1) Scheduler 的 API (一)

在RxJava 中,Scheduler ——调度器,相当于线程控制器,RxJava 通过它来指定每一段代码应该运行在什么样的线程。RxJava 已经内置了几个 Scheduler ,它们已经适合大多数的使用场景:

  • Schedulers.immediate(): 直接在当前线程运行,相当于不指定线程。这是默认的 Scheduler
  • Schedulers.newThread(): 总是启用新线程,并在新线程执行操作。
  • Schedulers.io(): I/O 操作(读写文件、读写数据库、网络信息交互等)所使用的 Scheduler。行为模式和 newThread() 差不多,区别在于 io() 的内部实现是是用一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下 io() 比 newThread() 更有效率。不要把计算工作放在 io() 中,可以避免创建不必要的线程。
  • Schedulers.computation(): 计算所使用的 Scheduler。这个计算指的是 CPU 密集型计算,即不会被 I/O 等操作限制性能的操作,例如图形的计算。这个 Scheduler 使用的固定的线程池,大小为 CPU 核数。不要把 I/O 操作放在 computation() 中,否则 I/O 操作的等待时间会浪费 CPU。
  • 另外, Android 还有一个专用的 AndroidSchedulers.mainThread(),它指定的操作将在 Android 主线程运行。

有了这几个 Scheduler ,就可以使用 subscribeOn() 和 observeOn() 两个方法来对线程进行控制了。 * subscribeOn(): 指定 subscribe() 所发生的线程,即 Observable.OnSubscribe 被激活时所处的线程。或者叫做事件产生的线程。 * observeOn(): 指定 Subscriber 所运行在的线程。或者叫做事件消费的线程。

文字叙述总归难理解,上代码:

Observable.just(1, 2, 3, 4)
    .subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
    .observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
    .subscribe(new Action1<Integer>() {
        @Override
        public void call(Integer number) {
            Log.d(tag, "number:" + number);
        }
    });

上面这段代码中,由于 subscribeOn(Schedulers.io()) 的指定,被创建的事件的内容 1234 将会在 IO 线程发出;而由于 observeOn(AndroidScheculers.mainThread()) 的指定,因此 subscriber 数字的打印将发生在主线程 。事实上,这种在 subscribe() 之前写上两句 subscribeOn(Scheduler.io()) 和 observeOn(AndroidSchedulers.mainThread()) 的使用方式非常常见,它适用于多数的 『后台线程取数据,主线程显示』的程序策略。

而前面提到的由图片 id 取得图片并显示的例子,如果也加上这两句:

int drawableRes = ...;
ImageView imageView = ...;
Observable.create(new OnSubscribe<Drawable>() {
    @Override
    public void call(Subscriber<? super Drawable> subscriber) {
        Drawable drawable = getTheme().getDrawable(drawableRes));
        subscriber.onNext(drawable);
        subscriber.onCompleted();
    }
})
.subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
.observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
.subscribe(new Observer<Drawable>() {
    @Override
    public void onNext(Drawable drawable) {
        imageView.setImageDrawable(drawable);
    }

    @Override
    public void onCompleted() {
    }

    @Override
    public void onError(Throwable e) {
        Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
    }
});

那么,加载图片将会发生在 IO 线程,而设置图片则被设定在了主线程。这就意味着,即使加载图片耗费了几十甚至几百毫秒的时间,也不会造成丝毫界面的卡顿。

2) Scheduler 的原理 (一)

RxJava 的 Scheduler API 很方便,也很神奇(加了一句话就把线程切换了,怎么做到的?而且 subscribe() 不是*外层直接调用的方法吗,它竟然也能被指定线程?)。然而 Scheduler 的原理需要放在后面讲,因为它的原理是以下一节《变换》的原理作为基础的。

好吧这一节其实我屁也没说,只是为了让你安心,让你知道我不是忘了讲原理,而是把它放在了更合适的地方。

4. 变换

终于要到牛逼的地方了,不管你激动不激动,反正我是激动了。

RxJava 提供了对事件序列进行变换的支持,这是它的核心功能之一,也是大多数人说『RxJava 真是太好用了』的*大原因。所谓变换,就是将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列。概念说着总是模糊难懂的,来看 API。

1) API

首先看一个 map() 的例子:

Observable.just("images/logo.png") // 输入类型 String
    .map(new Func1<String, Bitmap>() {
        @Override
        public Bitmap call(String filePath) { // 参数类型 String
            return getBitmapFromPath(filePath); // 返回类型 Bitmap
        }
    })
    .subscribe(new Action1<Bitmap>() {
        @Override
        public void call(Bitmap bitmap) { // 参数类型 Bitmap
            showBitmap(bitmap);
        }
    });

这里出现了一个叫做 Func1 的类。它和 Action1 非常相似,也是 RxJava 的一个接口,用于包装含有一个参数的方法。 Func1 和 Action 的区别在于, Func1 包装的是有返回值的方法。另外,和 ActionX 一样, FuncX 也有多个,用于不同参数个数的方法。FuncX 和 ActionX 的区别在 FuncX 包装的是有返回值的方法。

可以看到,map() 方法将参数中的 String 对象转换成一个 Bitmap 对象后返回,而在经过 map() 方法后,事件的参数类型也由 String 转为了 Bitmap。这种直接变换对象并返回的,是*常见的也*容易理解的变换。不过 RxJava 的变换远不止这样,它不仅可以针对事件对象,还可以针对整个事件队列,这使得 RxJava 变得非常灵活。我列举几个常用的变换:

  • map(): 事件对象的直接变换,具体功能上面已经介绍过。它是 RxJava *常用的变换。 map() 的示意图: map() 示意图
  • flatMap(): 这是一个很有用但非常难理解的变换,因此我决定花多些篇幅来介绍它。 首先假设这么一种需求:假设有一个数据结构『学生』,现在需要打印出一组学生的名字。实现方式很简单:
Student[] students = ...;
Subscriber<String> subscriber = new Subscriber<String>() {
    @Override
    public void onNext(String name) {
        Log.d(tag, name);
    }
    ...
};
Observable.from(students)
    .map(new Func1<Student, String>() {
        @Override
        public String call(Student student) {
            return student.getName();
        }
    })
    .subscribe(subscriber);

很简单。那么再假设:如果要打印出每个学生所需要修的所有课程的名称呢?(需求的区别在于,每个学生只有一个名字,但却有多个课程。)首先可以这样实现:

Student[] students = ...;
Subscriber<Student> subscriber = new Subscriber<Student>() {
    @Override
    public void onNext(Student student) {
        List<Course> courses = student.getCourses();
        for (int i = 0; i < courses.size(); i++) {
            Course course = courses.get(i);
            Log.d(tag, course.getName());
        }
    }
    ...
};
Observable.from(students)
    .subscribe(subscriber);

依然很简单。那么如果我不想在 Subscriber 中使用 for 循环,而是希望 Subscriber 中直接传入单个的 Course对象呢(这对于代码复用很重要)?用 map() 显然是不行的,因为 map() 是一对一的转化,而我现在的要求是一对多的转化。那怎么才能把一个 Student 转化成多个 Course 呢?

这个时候,就需要用 flatMap() 了:

Student[] students = ...;
Subscriber<Course> subscriber = new Subscriber<Course>() {
    @Override
    public void onNext(Course course) {
        Log.d(tag, course.getName());
    }
    ...
};
Observable.from(students)
    .flatMap(new Func1<Student, Observable<Course>>() {
        @Override
        public Observable<Course> call(Student student) {
            return Observable.from(student.getCourses());
        }
    })
    .subscribe(subscriber);

从上面的代码可以看出, flatMap() 和 map() 有一个相同点:它也是把传入的参数转化之后返回另一个对象。但需要注意,和 map() 不同的是, flatMap() 中返回的是个 Observable 对象,并且这个 Observable 对象并不是被直接发送到了 Subscriber 的回调方法中。 flatMap() 的原理是这样的:1. 使用传入的事件对象创建一个 Observable 对象;2. 并不发送这个 Observable, 而是将它激活,于是它开始发送事件;3. 每一个创建出来的 Observable 发送的事件,都被汇入同一个 Observable ,而这个 Observable 负责将这些事件统一交给 Subscriber 的回调方法。这三个步骤,把事件拆成了两级,通过一组新创建的 Observable 将初始的对象『铺平』之后通过统一路径分发了下去。而这个『铺平』就是 flatMap() 所谓的 flat。

flatMap() 示意图:

flatMap() 示意图

扩展:由于可以在嵌套的 Observable 中添加异步代码, flatMap() 也常用于嵌套的异步操作,例如嵌套的网络请求。示例代码(Retrofit + RxJava):

networkClient.token() // 返回 Observable<String>,在订阅时请求 token,并在响应后发送 token
    .flatMap(new Func1<String, Observable<Messages>>() {
        @Override
        public Observable<Messages> call(String token) {
            // 返回 Observable<Messages>,在订阅时请求消息列表,并在响应后发送请求到的消息列表
            return networkClient.messages();
        }
    })
    .subscribe(new Action1<Messages>() {
        @Override
        public void call(Messages messages) {
            // 处理显示消息列表
            showMessages(messages);
        }
    });

传统的嵌套请求需要使用嵌套的 Callback 来实现。而通过 flatMap() ,可以把嵌套的请求写在一条链中,从而保持程序逻辑的清晰。

  • throttleFirst(): 在每次事件触发后的一定时间间隔内丢弃新的事件。常用作去抖动过滤,例如按钮的点击监听器: RxView.clickEvents(button) // RxBinding 代码,后面的文章有解释 .throttleFirst(500, TimeUnit.MILLISECONDS) // 设置防抖间隔为 500ms .subscribe(subscriber); 妈妈再也不怕我的用户手抖点开两个重复的界面啦。

此外, RxJava 还提供很多便捷的方法来实现事件序列的变换,这里就不一一举例了。

2) 变换的原理:lift()

这些变换虽然功能各有不同,但实质上都是针对事件序列的处理和再发送。而在 RxJava 的内部,它们是基于同一个基础的变换方法: lift(Operator)。首先看一下 lift() 的内部实现(仅核心代码):

// 注意:这不是 lift() 的源码,而是将源码中与性能、兼容性、扩展性有关的代码剔除后的核心代码。
// 如果需要看源码,可以去 RxJava 的 GitHub 仓库下载。
public <R> Observable<R> lift(Operator<? extends R, ? super T> operator) {
    return Observable.create(new OnSubscribe<R>() {
        @Override
        public void call(Subscriber subscriber) {
            Subscriber newSubscriber = operator.call(subscriber);
            newSubscriber.onStart();
            onSubscribe.call(newSubscriber);
        }
    });
}

这段代码很有意思:它生成了一个新的 Observable 并返回,而且创建新 Observable 所用的参数 OnSubscribe 的回调方法 call() 中的实现竟然看起来和前面讲过的 Observable.subscribe() 一样!然而它们并不一样哟~不一样的地方关键就在于第二行 onSubscribe.call(subscriber) 中的 onSubscribe 所指代的对象不同(高能预警:接下来的几句话可能会导致身体的严重不适)——

  • subscribe() 中这句话的 onSubscribe 指的是 Observable 中的 onSubscribe 对象,这个没有问题,但是 lift() 之后的情况就复杂了点。
  • 当含有 lift() 时:
    1.lift() 创建了一个 Observable 后,加上之前的原始 Observable,已经有两个 Observable 了;
    2.而同样地,新 Observable 里的新 OnSubscribe 加上之前的原始 Observable 中的原始 OnSubscribe,也就有了两个 OnSubscribe
    3.当用户调用经过 lift() 后的 Observable 的 subscribe() 的时候,使用的是 lift() 所返回的新的 Observable ,于是它所触发的 onSubscribe.call(subscriber),也是用的新 Observable 中的新 OnSubscribe,即在 lift() 中生成的那个 OnSubscribe
    4.而这个新 OnSubscribe 的 call() 方法中的 onSubscribe ,就是指的原始 Observable 中的原始 OnSubscribe ,在这个 call() 方法里,新 OnSubscribe 利用 operator.call(subscriber) 生成了一个新的 SubscriberOperator 就是在这里,通过自己的 call() 方法将新 Subscriber 和原始 Subscriber 进行关联,并插入自己的『变换』代码以实现变换),然后利用这个新 Subscriber 向原始 Observable 进行订阅。
    这样就实现了 lift() 过程,有点像一种代理机制,通过事件拦截和处理实现事件序列的变换。

精简掉细节的话,也可以这么说:在 Observable 执行了 lift(Operator) 方法之后,会返回一个新的 Observable,这个新的 Observable 会像一个代理一样,负责接收原始的 Observable 发出的事件,并在处理后发送给 Subscriber

如果你更喜欢具象思维,可以看图:

lift() 原理图

或者可以看动图:

lift 原理动图

两次和多次的 lift() 同理,如下图:

两次 lift

举一个具体的 Operator 的实现。下面这是一个将事件中的 Integer 对象转换成 String 的例子,仅供参考:

observable.lift(new Observable.Operator<String, Integer>() {
    @Override
    public Subscriber<? super Integer> call(final Subscriber<? super String> subscriber) {
        // 将事件序列中的 Integer 对象转换为 String 对象
        return new Subscriber<Integer>() {
            @Override
            public void onNext(Integer integer) {
                subscriber.onNext("" + integer);
            }

            @Override
            public void onCompleted() {
                subscriber.onCompleted();
            }

            @Override
            public void onError(Throwable e) {
                subscriber.onError(e);
            }
        };
    }
});

讲述 lift() 的原理只是为了让你更好地了解 RxJava ,从而可以更好地使用它。然而不管你是否理解了 lift() 的原理,RxJava 都不建议开发者自定义 Operator 来直接使用 lift(),而是建议尽量使用已有的 lift() 包装方法(如 map() flatMap() 等)进行组合来实现需求,因为直接使用 lift() 非常容易发生一些难以发现的错误。

3) compose: 对 Observable 整体的变换

除了 lift() 之外, Observable 还有一个变换方法叫做 compose(Transformer)。它和 lift() 的区别在于, lift() 是针对事件项和事件序列的,而 compose() 是针对 Observable 自身进行变换。举个例子,假设在程序中有多个 Observable ,并且他们都需要应用一组相同的 lift() 变换。你可以这么写:

observable1
    .lift1()
    .lift2()
    .lift3()
    .lift4()
    .subscribe(subscriber1);
observable2
    .lift1()
    .lift2()
    .lift3()
    .lift4()
    .subscribe(subscriber2);
observable3
    .lift1()
    .lift2()
    .lift3()
    .lift4()
    .subscribe(subscriber3);
observable4
    .lift1()
    .lift2()
    .lift3()
    .lift4()
    .subscribe(subscriber1);

你觉得这样太不软件工程了,于是你改成了这样:

private Observable liftAll(Observable observable) {
    return observable
        .lift1()
        .lift2()
        .lift3()
        .lift4();
}
...
liftAll(observable1).subscribe(subscriber1);
liftAll(observable2).subscribe(subscriber2);
liftAll(observable3).subscribe(subscriber3);
liftAll(observable4).subscribe(subscriber4);

可读性、可维护性都提高了。可是 Observable 被一个方法包起来,这种方式对于 Observale 的灵活性似乎还是增添了那么点限制。怎么办?这个时候,就应该用 compose() 来解决了:

public class LiftAllTransformer implements Observable.Transformer<Integer, String> {
    @Override
    public Observable<String> call(Observable<Integer> observable) {
        return observable
            .lift1()
            .lift2()
            .lift3()
            .lift4();
    }
}
...
Transformer liftAll = new LiftAllTransformer();
observable1.compose(liftAll).subscribe(subscriber1);
observable2.compose(liftAll).subscribe(subscriber2);
observable3.compose(liftAll).subscribe(subscriber3);
observable4.compose(liftAll).subscribe(subscriber4);

像上面这样,使用 compose() 方法,Observable 可以利用传入的 Transformer 对象的 call 方法直接对自身进行处理,也就不必被包在方法的里面了。

compose() 的原理比较简单,不附图喽。

5. 线程控制:Scheduler (二)

除了灵活的变换,RxJava 另一个牛逼的地方,就是线程的自由控制。

1) Scheduler 的 API (二)

前面讲到了,可以利用 subscribeOn() 结合 observeOn() 来实现线程控制,让事件的产生和消费发生在不同的线程。可是在了解了 map() flatMap() 等变换方法后,有些好事的(其实就是当初刚接触 RxJava 时的我)就问了:能不能多切换几次线程?

答案是:能。因为 observeOn() 指定的是 Subscriber 的线程,而这个 Subscriber 并不是(严格说应该为『不一定是』,但这里不妨理解为『不是』)subscribe() 参数中的 Subscriber ,而是 observeOn() 执行时的当前 Observable 所对应的 Subscriber ,即它的直接下级 Subscriber 。换句话说,observeOn() 指定的是它之后的操作所在的线程。因此如果有多次切换线程的需求,只要在每个想要切换线程的位置调用一次 observeOn() 即可。上代码:

Observable.just(1, 2, 3, 4) // IO 线程,由 subscribeOn() 指定
    .subscribeOn(Schedulers.io())
    .observeOn(Schedulers.newThread())
    .map(mapOperator) // 新线程,由 observeOn() 指定
    .observeOn(Schedulers.io())
    .map(mapOperator2) // IO 线程,由 observeOn() 指定
    .observeOn(AndroidSchedulers.mainThread) 
    .subscribe(subscriber);  // Android 主线程,由 observeOn() 指定

如上,通过 observeOn() 的多次调用,程序实现了线程的多次切换。

不过,不同于 observeOn() , subscribeOn() 的位置放在哪里都可以,但它是只能调用一次的。

又有好事的(其实还是当初的我)问了:如果我非要调用多次 subscribeOn() 呢?会有什么效果?

这个问题先放着,我们还是从 RxJava 线程控制的原理说起吧。

2) Scheduler 的原理(二)

其实, subscribeOn() 和 observeOn() 的内部实现,也是用的 lift()。具体看图(不同颜色的箭头表示不同的线程):

subscribeOn() 原理图:

subscribeOn() 原理

observeOn() 原理图:

observeOn() 原理

从图中可以看出,subscribeOn() 和 observeOn() 都做了线程切换的工作(图中的 “schedule…” 部位)。不同的是, subscribeOn() 的线程切换发生在 OnSubscribe 中,即在它通知上一级 OnSubscribe 时,这时事件还没有开始发送,因此 subscribeOn() 的线程控制可以从事件发出的开端就造成影响;而 observeOn() 的线程切换则发生在它内建的 Subscriber 中,即发生在它即将给下一级 Subscriber 发送事件时,因此 observeOn() 控制的是它后面的线程。

*后,我用一张图来解释当多个 subscribeOn() 和 observeOn() 混合使用时,线程调度是怎么发生的(由于图中对象较多,相对于上面的图对结构做了一些简化调整):

线程控制综合调用

图中共有 5 处含有对事件的操作。由图中可以看出,①和②两处受*个 subscribeOn() 影响,运行在红色线程;③和④处受*个 observeOn() 的影响,运行在绿色线程;⑤处受第二个 onserveOn() 影响,运行在紫色线程;而第二个 subscribeOn() ,由于在通知过程中线程就被*个 subscribeOn() 截断,因此对整个流程并没有任何影响。这里也就回答了前面的问题:当使用了多个 subscribeOn() 的时候,只有*个 subscribeOn() 起作用。

3) 延伸:doOnSubscribe()

然而,虽然超过一个的 subscribeOn() 对事件处理的流程没有影响,但在流程之前却是可以利用的。

在前面讲 Subscriber 的时候,提到过 Subscriber 的 onStart() 可以用作流程开始前的初始化。然而 onStart()由于在 subscribe() 发生时就被调用了,因此不能指定线程,而是只能执行在 subscribe() 被调用时的线程。这就导致如果 onStart() 中含有对线程有要求的代码(例如在界面上显示一个 ProgressBar,这必须在主线程执行),将会有线程非法的风险,因为有时你无法预测 subscribe() 将会在什么线程执行。

而与 Subscriber.onStart() 相对应的,有一个方法 Observable.doOnSubscribe() 。它和 Subscriber.onStart() 同样是在 subscribe() 调用后而且在事件发送前执行,但区别在于它可以指定线程。默认情况下, doOnSubscribe() 执行在 subscribe() 发生的线程;而如果在 doOnSubscribe() 之后有 subscribeOn() 的话,它将执行在离它*近的 subscribeOn() 所指定的线程。

示例代码:

Observable.create(onSubscribe)
    .subscribeOn(Schedulers.io())
    .doOnSubscribe(new Action0() {
        @Override
        public void call() {
            progressBar.setVisibility(View.VISIBLE); // 需要在主线程执行
        }
    })
    .subscribeOn(AndroidSchedulers.mainThread()) // 指定主线程
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(subscriber);

如上,在 doOnSubscribe()的后面跟一个 subscribeOn() ,就能指定准备工作的线程了。

RxJava 的适用场景和使用方式

1. 与 Retrofit 的结合

Retrofit 是 Square 的一个著名的网络请求库。没有用过 Retrofit 的可以选择跳过这一小节也没关系,我举的每种场景都只是个例子,而且例子之间并无前后关联,只是个抛砖引玉的作用,所以你跳过这里看别的场景也可以的。

Retrofit 除了提供了传统的 Callback 形式的 API,还有 RxJava 版本的 Observable 形式 API。下面我用对比的方式来介绍 Retrofit 的 RxJava 版 API 和传统版本的区别。

以获取一个 User 对象的接口作为例子。使用Retrofit 的传统 API,你可以用这样的方式来定义请求:

@GET("/user")
public void getUser(@Query("userId") String userId, Callback<User> callback);

在程序的构建过程中, Retrofit 会把自动把方法实现并生成代码,然后开发者就可以利用下面的方法来获取特定用户并处理响应:

getUser(userId, new Callback<User>() {
    @Override
    public void success(User user) {
        userView.setUser(user);
    }

    @Override
    public void failure(RetrofitError error) {
        // Error handling
        ...
    }
};

而使用 RxJava 形式的 API,定义同样的请求是这样的:

@GET("/user")
public Observable<User> getUser(@Query("userId") String userId);

使用的时候是这样的:

getUser(userId)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer<User>() {
        @Override
        public void onNext(User user) {
            userView.setUser(user);
        }

        @Override
        public void onCompleted() {
        }

        @Override
        public void onError(Throwable error) {
            // Error handling
            ...
        }
    });

看到区别了吗?

当 RxJava 形式的时候,Retrofit 把请求封装进 Observable ,在请求结束后调用 onNext() 或在请求失败后调用 onError()

对比来看, Callback 形式和 Observable 形式长得不太一样,但本质都差不多,而且在细节上 Observable 形式似乎还比 Callback 形式要差点。那 Retrofit 为什么还要提供 RxJava 的支持呢?

因为它好用啊!从这个例子看不出来是因为这只是*简单的情况。而一旦情景复杂起来, Callback 形式马上就会开始让人头疼。比如:

假设这么一种情况:你的程序取到的 User 并不应该直接显示,而是需要先与数据库中的数据进行比对和修正后再显示。使用 Callback 方式大概可以这么写:

getUser(userId, new Callback<User>() {
    @Override
    public void success(User user) {
        processUser(user); // 尝试修正 User 数据
        userView.setUser(user);
    }

    @Override
    public void failure(RetrofitError error) {
        // Error handling
        ...
    }
};

有问题吗?

很简便,但不要这样做。为什么?因为这样做会影响性能。数据库的操作很重,一次读写操作花费 10~20ms 是很常见的,这样的耗时很容易造成界面的卡顿。所以通常情况下,如果可以的话一定要避免在主线程中处理数据库。所以为了提升性能,这段代码可以优化一下:

getUser(userId, new Callback<User>() {
    @Override
    public void success(User user) {
        new Thread() {
            @Override
            public void run() {
                processUser(user); // 尝试修正 User 数据
                runOnUiThread(new Runnable() { // 切回 UI 线程
                    @Override
                    public void run() {
                        userView.setUser(user);
                    }
                });
            }).start();
    }

    @Override
    public void failure(RetrofitError error) {
        // Error handling
        ...
    }
};

性能问题解决,但……这代码实在是太乱了,迷之缩进啊!杂乱的代码往往不仅仅是美观问题,因为代码越乱往往就越难读懂,而如果项目中充斥着杂乱的代码,无疑会降低代码的可读性,造成团队开发效率的降低和出错率的升高。

这时候,如果用 RxJava 的形式,就好办多了。 RxJava 形式的代码是这样的:

getUser(userId)
    .doOnNext(new Action1<User>() {
        @Override
        public void call(User user) {
            processUser(user);
        })
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer<User>() {
        @Override
        public void onNext(User user) {
            userView.setUser(user);
        }

        @Override
        public void onCompleted() {
        }

        @Override
        public void onError(Throwable error) {
            // Error handling
            ...
        }
    });

后台代码和前台代码全都写在一条链中,明显清晰了很多。

再举一个例子:假设 /user 接口并不能直接访问,而需要填入一个在线获取的 token ,代码应该怎么写?

Callback 方式,可以使用嵌套的 Callback

@GET("/token")
public void getToken(Callback<String> callback);

@GET("/user")
public void getUser(@Query("token") String token, @Query("userId") String userId, Callback<User> callback);

...

getToken(new Callback<String>() {
    @Override
    public void success(String token) {
        getUser(token, userId, new Callback<User>() {
            @Override
            public void success(User user) {
                userView.setUser(user);
            }

            @Override
            public void failure(RetrofitError error) {
                // Error handling
                ...
            }
        };
    }

    @Override
    public void failure(RetrofitError error) {
        // Error handling
        ...
    }
});

倒是没有什么性能问题,可是迷之缩进毁一生,你懂我也懂,做过大项目的人应该更懂。

而使用 RxJava 的话,代码是这样的:

@GET("/token")
public Observable<String> getToken();

@GET("/user")
public Observable<User> getUser(@Query("token") String token, @Query("userId") String userId);

...

getToken()
    .flatMap(new Func1<String, Observable<User>>() {
        @Override
        public Observable<User> onNext(String token) {
            return getUser(token, userId);
        })
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer<User>() {
        @Override
        public void onNext(User user) {
            userView.setUser(user);
        }

        @Override
        public void onCompleted() {
        }

        @Override
        public void onError(Throwable error) {
            // Error handling
            ...
        }
    });

用一个 flatMap() 就搞定了逻辑,依然是一条链。看着就很爽,是吧?

2016/03/31 更新,加上我写的一个 Sample 项目:
rengwuxian RxJava Samples

好,Retrofit 部分就到这里。

2. RxBinding

RxBinding 是 Jake Wharton 的一个开源库,它提供了一套在 Android 平台上的基于 RxJava 的 Binding API。所谓 Binding,就是类似设置 OnClickListener 、设置 TextWatcher 这样的注册绑定对象的 API。

举个设置点击监听的例子。使用 RxBinding ,可以把事件监听用这样的方法来设置:

Button button = ...;
RxView.clickEvents(button) // 以 Observable 形式来反馈点击事件
    .subscribe(new Action1<ViewClickEvent>() {
        @Override
        public void call(ViewClickEvent event) {
            // Click handling
        }
    });

看起来除了形式变了没什么区别,实质上也是这样。甚至如果你看一下它的源码,你会发现它连实现都没什么惊喜:它的内部是直接用一个包裹着的 setOnClickListener() 来实现的。然而,仅仅这一个形式的改变,却恰好就是 RxBinding 的目的:扩展性。通过 RxBinding 把点击监听转换成 Observable 之后,就有了对它进行扩展的可能。扩展的方式有很多,根据需求而定。一个例子是前面提到过的 throttleFirst() ,用于去抖动,也就是消除手抖导致的快速连环点击:

RxView.clickEvents(button)
    .throttleFirst(500, TimeUnit.MILLISECONDS)
    .subscribe(clickAction);

如果想对 RxBinding 有更多了解,可以去它的 GitHub 项目 下面看看。

3. 各种异步操作

前面举的 Retrofit 和 RxBinding 的例子,是两个可以提供现成的 Observable 的库。而如果你有某些异步操作无法用这些库来自动生成 Observable,也完全可以自己写。例如数据库的读写、大图片的载入、文件压缩/解压等各种需要放在后台工作的耗时操作,都可以用 RxJava 来实现,有了之前几章的例子,这里应该不用再举例了。

4. RxBus

RxBus 名字看起来像一个库,但它并不是一个库,而是一种模式,它的思想是使用 RxJava 来实现了 EventBus ,而让你不再需要使用 Otto 或者 GreenRobot 的 EventBus。至于什么是 RxBus,可以看这篇文章。顺便说一句,Flipboard 已经用 RxBus 替换掉了 Otto ,目前为止没有不良反应。

*后

对于 Android 开发者来说, RxJava 是一个很难上手的库,因为它对于 Android 开发者来说有太多陌生的概念了。可是它真的很牛逼。因此,我写了这篇《给 Android 开发者的 RxJava 详解》,希望能给始终搞不明白什么是 RxJava 的人一些入门的指引,或者能让正在使用 RxJava 但仍然心存疑惑的人看到一些更深入的解析。无论如何,只要能给各位同为 Android 工程师的你们提供一些帮助,这篇文章的目的就达到了。

 

为什么写这个?

与两三年前的境况不同,中国现在已经不缺初级 Android 工程师,但中级和高级工程师严重供不应求。因此我决定从今天开始不定期地发布我的技术分享,只希望能够和大家共同提升,通过我们的成长来解决一点点国内互联网公司人才稀缺的困境,也提升各位技术党的收入。所以,不仅要写这篇,我还会写更多。至于内容的定位,我计划只定位真正的干货,一些边边角角的小技巧和炫酷的黑科技应该都不会写,总之希望每篇文章都能帮读者提升真正的实力。

AndroidStudio介绍,代理配置,特性,技巧及从eclipse迁移到as

前言:
Android Studio 是google官方正式开发Android应用程序的开发工具,这玩意是基于IntelliJ IDEA,AndroidStudio 提供如下功能:


    Flexible Gradle-based build system
    Build variants and multiple apk file generation
    Code templates to help you build common app features
    Rich layout editor with support for drag and drop theme editing
    lint tools to catch performance, usability, version compatibility, and other problems
    ProGuard and app-signing capabilities
    Built-in support for Google Cloud Platform, making it easy to integrate Google Cloud Messaging and App Engine
    And much more

 

简单说:
灵活的gradle编译系统
编译变量和多apk的生成(变量是指不同产品的标识,比如debug和release)
代码模板帮助我们构建具有相同特性的app
支持布局拖拽和主题编辑的布局编译器
line tools 工具
签名支持
google 云平台

For specific Android Studio how-to documentation, see the pages in the Workflow section, such as Managing Projects from Android Studio and Building and Running from Android Studio. For a summary of the latest changes to Android Studio, see the Android Studio Release Notes.

如果你用as做Android开发,可以看看这些workflow这部分文档,比如在as中管理工程,编译和运行app以及as的历史版本。


1,Project and File Structure 工程目录结构及对应的文件结构
这里写图片描述
Project 整个工程目录结构
Packages每个项的目目录结构,包含src和res
Android每个项目的目录结构,包含src,res, manifests和整体的gradle配置文件
2,Android Build System Android编译系统,也就是使用gradle

3,Debug and Performance 调试及各种性能工具

Android Studio provides a number of improvements to assist you in debugging and improving the performance of your code, including an improved virtual device management, inline debugging, and performance analysis tools.

4,Android Virtual Device (AVD) Manager 模拟器管理

5,Inline debugging 调试

6,Memory and CPU monitor 内存和cpu监听

7,Heap dump 内存镜像,可以看到内存的

8,Allocation tracker 内存分配和跟踪

9,Data file access 数据存储

10,Code inspections 代码检查及命令行模式

11,Annotations in Android Studio 注解包的依赖

12,Inferring nullability 在你使用分析工具的时候,null的验证
To run a nullability analysis in Android Studio, select the Analyze > Infer Nullity menu option. Android Studio inserts the Android @Nullable and @NonNull annotations in detected locations in your code. After running a null analysis, it’s good practice to verify the injected annotations.

13,Log messages log信息


AndroidStudio Configuration

SDK Manager 更新配置
Settings –>Appearance –>System Settings–> Updates

Update channels 更新渠道

Canary channel:  二进制
Dev channel:     开发
Beta channel:    测试
Stable channel:  稳定

这里写图片描述

Proxy Settings 代理配置:
这里写图片描述

For application-specific HTTP proxy settings, set the proxy settings in the build.gradle file as required for each application module.
每个app的配置:

apply plugin: 'com.android.application'

android {
    ...

    defaultConfig {
        ...
        systemProp.http.proxyHost=proxy.company.com
        systemProp.http.proxyPort=443
        systemProp.http.proxyUser=userid
        systemProp.http.proxyPassword=password
        systemProp.http.auth.ntlm.domain=domain
    }
    ...
}

 

For project-wide HTTP proxy settings, set the proxy settings in the gradle/gradle.properties file.
整个工程的配置:

# Project-wide Gradle settings.
...

systemProp.http.proxyHost=proxy.company.com
systemProp.http.proxyPort=443
systemProp.http.proxyUser=username
systemProp.http.proxyPassword=password
systemProp.http.auth.ntlm.domain=domain

systemProp.https.proxyHost=proxy.company.com
systemProp.https.proxyPort=443
systemProp.https.proxyUser=username
systemProp.https.proxyPassword=password
systemProp.https.auth.ntlm.domain=domain

...

 

Features
不做过多介绍:
Translations Editor i18N的适配编辑器
Android Wear and TV support 手环和tv开发的支持
Fingerprint Support 指纹识别的支持
Developer Services google 的开发服务

Ads using AdMob
Analytics Google Analytics
Authentication using Google Sign-in
Notifications using Google Cloud Messaging

 

Public and Private Resources 公用私有资源,这个值得研究

Tips and Tricks
开发技巧和秘籍:部分快捷键,反编译,注解,布局的所有机型视图

Migrating from Eclipse ADT
迁移工作,从eclipse迁移到AndroidStudio
Project Structure 工程目录:
整体对比:
这里写图片描述

目录结构对比图:

这里写图片描述这里写图片描述
这里写图片描述

Mainfest对比:
这里写图片描述

Application ID for package identification

apply plugin: 'com.android.application'

android {
   compileSdkVersion 19
   buildToolsVersion "19.1"

   defaultConfig {
       applicationId "com.example.my.app"
       minSdkVersion 15
       targetSdkVersion 19
       versionCode 1
       versionName "1.0"
   }
 ...

 

Build variants 不同包名打包配置:

productFlavors {
     pro {
          applicationId = "com.example.my.pkg.pro"
     }
     free {
          applicationId = "com.example.my.pkg.free"
     }
}

buildTypes {
    debug {
          applicationIdSuffix ".debug"
    }
}
....

 

Dependencies 依赖库

dependencies {
   /*  ,exclude: 'android-support-v13.jar'' */ /*哪个jar不被编译*/
   compile fileTree(dir: 'libs', include: ['*.jar'])
   /*compile files('libs/*.jar')*/
   compile 'com.android.support:appcompat-v7:22.0.0'
   // 可以不适用appcompat-v7
}

 

 // Module dependency  依赖库
    compile project(":lib")

    // Remote binary dependency
    compile 'com.android.support:appcompat-v7:19.0.1'

    // Local binary dependency  本地jar
    compile fileTree(dir: 'libs', include: ['*.jar'])

 

Gradle-based Build Process gradle 命令

assemble
assembleDebug
assembleRelease

 

直接在Terminal 中输入gradlew assemble
这个会下载对应的gradle版本:
对应的配置在 gradle\wrapper\gradle-wrapper.properties

#Wed Apr 10 15:27:10 PDT 2013
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip

 

如果想看所有的命令: Gradle 默认在右边
这里写图片描述

Using the Android Studio build system’s declarative logic
宏定义

def getVersionCode) {
      def code = …
      return code
}

android {
    defaultConfig {
        versionCode getVersionCode()
              …
    }
}

 

Resource Optimization res优化
Resource shrinking res缩减

android {
    ...

    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'),
            'proguard-rules.pro'
        }
    }
}

Filtering language resources 语言资源过滤:
apply plugin: 'android'

android {
    compileSdkVersion 22
    buildToolsVersion "22.0.1"

    defaultConfig {
        minSdkVersion 8
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
        resConfigs "en", "de", "es" //Define the included language resources.
        resConfigs "mdpi", "hdpi"
    }
...

 

Files to ignore 忽略文件配置:

A number of Android Studio files are typically not added to version control as these are temporary files or files that get overwritten with each build. These files are listed in an exclusion file, such as .gitignore, for the project and each app module. Typically, the following files are excluded from version control:

.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures

Spring For Android初步

Spring For Android初步
Spring for Android 支持在Android环境下使用Spring框架,其意在简化Android本地开发。这包括能够在你的Android应用程序的Rest客户端使用RestTemplate。除了本次主要讲解的网络请求部分,Spring for Android还提供了基于OAuth支持将Spring Social的功能集成到Android应用程序, 授权客户机和实现流行的社交网站,如Twitter和Facebook。

RestTemplate Module

Spring的RestTemplate是一个健壮的、流行的基于java的REST client。Spring Android的RestTemplate模块提供一个能够在Android环境下工作的RestTemplate版本。

RestTemplate类是SFA RestTemplate组件包的核心类。他在概念上类似Spring框架其他同级项目中提供的模板类。RestTemlpate的行为表现取决于他提供的那些回调方法,同时通过配置合适的HttpMessageConverter 类,用户可以高效的将对象转换成HTTP请求中的信息或从响应消息中将信息转换回对应的对象。

每当创建一个新的RestTemplate实例,构造器就会生成配套的辅助对象用于支持RestTemplate的功能。以下是RestTemplate组件中一些提供支持的模块介绍。

HTTP Client

RestTemplate 提供了抽象的RESTful HTTP 请求的模块,内部实现中,RestTemplate 封装了原生的Android HTTP 客户端连接包。这包括标准的J2SE连接组件(用SimpleClientHttpRequestFactory封装)和HttpComponents HttpClient组件(用HttpComponentsClientHttpRequestFactory封装)。默认情况下具体使用哪种ClientHttpRequestFactory 取决于Android的版本。

@SuppressWarnings(“deprecation”)
protected HttpAccessor() {
if (httpClient43Present) {
this.requestFactory = new HttpComponentsClientHttpRequestFactory();
}
else if (okHttpPresent) {
this.requestFactory = new OkHttpClientHttpRequestFactory();
}
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
this.requestFactory = new SimpleClientHttpRequestFactory();
}
else {
this.requestFactory = new org.springframework.http.client.HttpComponentsAndroidClientHttpRequestFactory();
}
}
(2.3之后建议使用标准的J2SE连接组件)

 

Gzip压缩器

HTTP规范中允许通过在请求头中加入Accept-Encoding参数指定传输字节流的压缩方式。目前为止,RestTemplate 可以通过Gzip压缩组件支持在发送和接受时通过gzip(仅支持gzip)格式压缩过的数据。

对象-Json转换

如果需要支持这种转换,SFA的RestTemplate需要第三方Json数据映射包的支持。目前SFA提供三种包支持:Jackson JSON Processor, Jackson 2.x, 和 Google Gson。虽然Jackson 系列是有名的JSON解析包,但Gson的包更小,更适合生成小型的Android程序安装包。
SFA 将支持代码放在包 org.springframework.http.converter.json中。

RSS和Atom 摘要文件支持

RSS和Atom的支持也需要第三方包, Android ROME Feed Reader包就提供了相应的功能。

SFA 将支持代码放在包org.springframework.http.converter.feed 中。

所需要的包文件

spring-android-rest-template-{version}.jar

spring-android-core-{version}.jar

 

RestTemplate 构造器

下面列出四种RestTemplate 的构造方法。默认的构造方法不包含消息的转换器,因此必须自己添加消息转换器。

如上文所讲,如果需要一个特别ClientHttpRequestFactory ,也可以向构造器中传入一个ClientHttpRequestFactory 的实例参数。

RestTemplate();

RestTemplate(boolean includeDefaultConverters);

RestTemplate(ClientHttpRequestFactory requestFactory);

RestTemplate(boolean includeDefaultConverters, ClientHttpRequestFactory requestFactory);

 

RestTemplate API简介

RestTemplate 对应主要的六种Http 访问方法封装了6类高层调用方法,通过他们可以非常轻松且高效的完成对RESTful 服务的服务请求。

RestTemplate 的API方法命名遵循同样的规则:  method+return

比如getForObject() 将调用HTTP GET方法,同时将HTTP响应数据转换成你指定的对象类型并返回。而postForLocation() 将调用HTTP POST方法,将给定的对象转换成HTTP请求内容发送,返回的信息中将标识新的对象资源所在的URL。

当HTTP请求发生异常时,异常信息将被封装在RestClientException 中并被抛出,可以通过在RestTemplate中实现ResponseErrorHandler来捕获并处理。

restTemplate.setErrorHandler(errorHandler);

/*捕获http请求发生异常RestClientException*/

restTemplate.setErrorHandler(new ResponseErrorHandler() {

@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
//获得请求响应,通过判断执行不同的操作。。。
return false;//返回true表示有错误,false表示没有错误
}

@Override
public void handleError(ClientHttpResponse response) throws IOException {
//处理相应的错误操作。。。
//该方法只有在hasError方法中返回true时才会执行
}
});
网络请求方法(其他请求方式类似,可参考官方在线的API文档,链接在参考资料中):

HTTP GET

public <T> T getForObject(String url, Class<T> responseType, Object… urlVariables) throws RestClientException;

public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> urlVariables) throws RestClientException;

public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException;

public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object… urlVariables);

public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> urlVariables);

public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException;

 

HTTP POST

public URI postForLocation(String url, Object request, Object… urlVariables) throws RestClientException;

public URI postForLocation(String url, Object request, Map<String, ?> urlVariables);

public URI postForLocation(URI url, Object request) throws RestClientException;

public <T> T postForObject(String url, Object request, Class<T> responseType, Object… uriVariables);

public <T> T postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables);

public <T> T postForObject(URI url, Object request, Class<T> responseType) throws RestClientException;

public <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Object… uriVariables);

public <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;

public <T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException;

HTTP 消息转换器

对象通过getForObject(), getForEntity(), postForLocation(), postForEntity(), postForObject() and put() 发送或者返回消息时都是通过HttpMessageConverter 的具体实现,从而转换成Http请求或者从Http应答中转换。从下面的HttpMessageConverter  API我们能对他的功能有个更深的认识。

public interface HttpMessageConverter<T> {
//判断给定的类型是否可以被转换器读取
//clazz 用于测试是否能够读取
//mediaType 读取的网络媒介类型,如果不指定,可以为null
//如果返回true表示可读,false表示其他情况
boolean canRead(Class<?> clazz, MediaType mediaType);
// 判断给定的类型是否可以被转换器写入
//clazz 用于测试是否能够写入
//mediaType 写入的网络媒介类型,如果不指定,可以为null
//如果返回true表示可写,false表示其他情况
boolean canWrite(Class<?> clazz, MediaType mediaType);

// 返回可被该转换器支持的header头消息类型
List<MediaType> getSupportedMediaTypes();

// 将给定的inputMessage消息转换成相应的类型对象并返回
// clazz 返回的类型,这个类型必须预先通过canRead方法,返回true
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;

// 将给定的对象转换后写入outputMessage
// t 写入outputMessage的对象,这个对象的类型必须预先通过canWrite方法,并返回true
// contentType  写入时的媒介类型,如果为null的话,则必须使用默认的内容类型转换器,否则,媒介类型必须预先通过canWrite方法且返回值为true
// outputMessage  要写入的消息
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
其中,SFA框架已经提供了现成的MediaType(即mime类型)。

部分类型:

{ “.323”, “text/h323” },

{ “.3gp”, “video/3gpp” },

{ “.aab”, “application/x-authoware-bin” },

{ “.aam”, “application/x-authoware-map” },

{ “.aas”, “application/x-authoware-seg” },

{ “.acx”, “application/internet-property-stream” },

{ “.ai”, “application/postscript” },

{ “.aif”, “audio/x-aiff” },

{ “.aifc”, “audio/x-aiff” },

{ “.aiff”, “audio/x-aiff” },

{ “.als”, “audio/X-Alpha5” },

{ “.amc”, “application/x-mpeg” },

{ “.ani”, “application/octet-stream” },

{ “.apk”, “application/vnd.android.package-archive” },

{ “.asc”, “text/plain” }

默认的消息转换器

基于性能的选择考虑,默认的RestTemplate无参构造方法不包含任何消息转换器。但是,如果调用了布尔版本的构造方法并传递true,则构造器会为一些主要的mime类型增加转换器。当然自己也可以编写自定义的转换器,并通过messageConverters属性添加。
RestTemplate生成的转换器包括:ByteArrayHttpMessageConverter, StringHttpMessageConverter, 和ResourceHttpMessageConverter。如果Android的版本在2.2或以上,那么XmlAwareFormHttpMessageConverter 和SourceHttpMessageConverter也会被添加。否则,若在2,.1版本,因为缺少对XML的必要支持,这两个转换器会被FormHttpMessageConverter 代替。我们来看看代码中是如何初始化的。

如果不设置,则使用该默认的消息转换器:

/**
* Identifies and initializes default {@link HttpMessageConverter} implementations.
*/

private static class DefaultMessageConverters {
private static final boolean javaxXmlTransformPresent =
ClassUtils.isPresent(“javax.xml.transform.Source”, RestTemplate.class.getClassLoader());

private static final boolean simpleXmlPresent =
ClassUtils.isPresent(“org.simpleframework.xml.Serializer”, RestTemplate.class.getClassLoader());

private static final boolean jackson2Present =
ClassUtils.isPresent(“com.fasterxml.jackson.databind.ObjectMapper”, RestTemplate.class.getClassLoader()) &&
ClassUtils.isPresent(“com.fasterxml.jackson.core.JsonGenerator”, RestTemplate.class.getClassLoader());

private static final boolean gsonPresent =
ClassUtils.isPresent(“com.google.gson.Gson”, RestTemplate.class.getClassLoader());

public static void init(List<HttpMessageConverter<?>> messageConverters) {
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
messageConverters.add(new ResourceHttpMessageConverter());

// if javax.xml.transform is not available, fall back to standard Form message converter
if (javaxXmlTransformPresent) {
messageConverters.add(new SourceHttpMessageConverter<Source>());
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
}
else {
messageConverters.add(new FormHttpMessageConverter());
}
if (simpleXmlPresent) {
messageConverters.add(new SimpleXmlHttpMessageConverter());
}
if (jackson2Present) {
messageConverters.add(new MappingJackson2HttpMessageConverter());
}
else if (gsonPresent) {
messageConverters.add(new GsonHttpMessageConverter());
}
}
}
ByteArrayHttpMessageConverter

该转换器可以从HTTP请求或应答中读写字节数组,默认情况下,该转换器支持所有的media types (*/*),同时写数据会以Content-Type为application/octet-stream(任意二进制数据)的形式。

FormHttpMessageConverter

能处理格式化的数据。支持用application/x-www-form-urlencoded(格式化编码数据)和multipart/form-data(多组件格式数据)的读写转换,格式化的数据通常用一个MultiValueMap<String, String>数据结构来读写。

XmlAwareFormHttpMessageConverter

是FormHttpMessageConverter的扩展,通过SourceHttpMessageConverter增加了对XML转换的支持。

ResourceHttpMessageConverter

能处理资源。支持对所有类型的读操作,写资源支持application/octet-stream。

SourceHttpMessageConverter

可以将请求或应答数据转换成javax.xml.transform.Source类型,只支持DOMSource, SAXSource, 和StreamSource三种类型。默认情况下支持text/xml和 application/xml两种XML可扩展标记语言MIME类型。

StringHttpMessageConverter

可以将请求或应答数据转换成String类型,支持所有的text media类型(text/*),支持Content-Type为text/plain(原文数据)写数据。

SimpleXmlHttpMessageConverter

支持对XML格式的读写处理,需要第三方包Simple XML serializer。XML的映射工作可以通过该包提供的注解方式进行配置。如果有需要提供附加的控制功能,可以通过切面的形式通过一个可定制的Serializer进行功能的加强。默认情况下,支持读写application/xml, text/xml, 和application/*+xml这几种MIME类型。

需要注意的是,此框架和Spring OXM并不兼容。他是用在SFA中的独立的第三方XML串行化实现。基于maven的依赖如下

<dependency>
<groupId>org.simpleframework</groupId>
<artifactId>simple-xml</artifactId>
<version>${simple-version}</version>
</dependency>

MappingJackson2HttpMessageConverter
基于Jackson框架提供对请求与应答数据的Json-Obejct转换。对于Json的映射配置可以Jackson提供的注解支持。当串行化/反串行化需要时也支持切面式的控制管理,默认支持application/json类型的读写。
请注意该转换器和GsonHttpMessageConverter 转换器都支持application/json类型的数据转换,因此*好只添加一款转换器,因为RestTemplate 会优先选择他*早匹配到的转换器进行数据转换,所以两款转换器都添加会导致不可预料的结果。

MappingJackson2HttpMessageConverter如果不是通过maven而是手工添加,还需要在lib包中引入jackson-annotations和jackson-core jars。

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-version}</version>
</dependency>

也可以通过MappingJacksonHttpMessageConverter支持 Jackson JSON Processor包。同样,手工导入需要引入jackson-core-asl jar。

<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>${jackson-version}</version>
</dependency>
GsonHttpMessageConverter
基本上和MappingJackson2HttpMessageConverter的功能一直,也支持注解和切面式。

<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson-version}</version>
</dependency>
(以上也可以使用gradle插件进行配置)

使用gzip压缩

// 添加 Accept-Encoding 消息头属性,设置为gzip
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAcceptEncoding(ContentCodingType.GZIP);
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
// 注意调用的方法
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);

Gzip压缩能有效的减少通讯传输的信息量。Gzip格式传输必须在服务器端被支持。通过将内容的请求头Accept-Encoding设置为gzip,可以向服务器端请求使用gzip压缩传输。如果服务器端支持,将返回压缩后的数据。RestTemplate 将检查Content-Encoding属性判断应答返回数据是否是压缩过的,是则用GZIPInputStream 去解压缩。目前Content-Encoding仅支持gzip格式。
需要注意,如果在2.3或以后的版本使用基于J2SE的标准连接SimpleClientHttpRequestFactory,Android框架会自动在Accept-Encoding中设置gzip,如果希望是gzip压缩失效,必须在header信息中增加其他标志值:
HttpHeaders requestHeaders = new HttpHeaders();
//增加如下标识
requestHeaders.setAcceptEncoding(ContentCodingType.IDENTITY);
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new StringHttpMessageConverter
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);
通过GET方式获取JSON数据

首先需要定义一个与返回的JSON格式对应的POJO类。

public class Event {
private Long id;
private String title;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public String setTitle(String title) {
this.title = title;
}
}
使用Rest请求:

RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
Event[] events = restTemplate.getForObject(url, Event[].class);

也可以通过设置请求头Accept信息的方式
// 显式设置 Accept header
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAccept(Collections.singletonList(new MediaType(“application”,”json”)));
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
ResponseEntity<Event[]> responseEntity = restTemplate.exchange(url, HttpMethod.GET, requestEntity, Event[].class);
Event[] events = responseEntity.getBody();

其中MappingJackson2HttpMessageConverter可以换成GsonHttpMessageConverter,作为另一种实现JSON解析的选择。

 

通过GET方式获取 XML 数据

我们使用刚刚用过的Event对象来演示XML的使用方法,注意Event的类及属性都有注解。

@Root
public class Event {
@Element
private Long id;
@Element
private String title;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public String setTitle(String title) {
this.title = title;
}
}
如果需要解析一组Event的数据,我们还需要定义基于List的包装类。

@Root(name=”events”)
public class EventList {
@ElementList(inline=true)
private List<Event> events;
public List<Event> getEvents() {
return events;
}
public void setEvents(List<Event> events) {
this.events = events;
}
}
编写请求代码如下:

RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new SimpleXmlHttpMessageConverter());
EventList eventList = restTemplate.getForObject(url, EventList.class);
和刚才一样,也可以通过设置请求头Accept信息的方式:

HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAccept(Collections.singletonList(new MediaType(“application”,”xml”)));
HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new SimpleXmlHttpMessageConverter());
ResponseEntity<EventList> responseEntity = restTemplate.exchange(url, HttpMethod.GET, requestEntity, EventList.class);
EventList eventList = responseEntity.getBody();
通过POST的方式发送 JSON 数据

我们定义了如下的POJO用于演示:

public class Message {
private long id;
private String subject;
private String text;
public void setId(long id) {
this.id = id;
}
public long getId() {
return id;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getSubject() {
return subject;
}
public void setText(String text) {
this.text = text;
}
public String getText() {
return text;
}
}
接着是编辑数据和发送请求的代码

// 编辑数据
Message message = new Message();
message.setId(555);
message.setSubject(“test subject”);
message.setText(“test text”);
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
String response = restTemplate.postForObject(url, message, String.class);
同样,通过设置请求头Content-Type信息也是可以的

// 编辑数据
Message message = new Message();
message.setId(555);
message.setSubject(“test subject”);
message.setText(“test text”);
// 设置 Content-Type
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(new MediaType(“application”,”json”));
HttpEntity<Message> requestEntity = new HttpEntity<Message>(message, requestHeaders);
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
String result = responseEntity.getBody();
(注意:如果想用spring for android的json解析功能,服务器需要返回的type类型应该为application/json而不是通常的text/html)

其他: Spring for Android还提供了三方授权,本身提供facebook和twtter的三方授,需要第三方包的支持。提供了Spring Social组件用于创建自己的连接工厂,并提供加密和保存信息的功能。

Spring for Android的核心部分比较强调服务器和客户端之间的数据交流,服务器需要确实返回指定的MIME类型,客户端才能进行更加简便的数据解析。

android 实现类似个人中心的界面设计

From:android 实现类似个人中心的界面设计

上效果图:
这里写图片描述
这里写图片描述

先理清设计思路:
1、外层用linearlayout包裹,linearlayout采用shape,搭上描边、圆角和填充背景色。
2、里层采用relativelayout填充进textview、imageview。
思路搞清后,很简单就两步。
先上布局代码:

  1. <LinearLayout style=“@style/PersonalMainLayoutStyle” >
  2. <RelativeLayout style=“@style/FindBottomStyle” >
  3. <TextView
  4. style=“@style/PersonalTextStyle”
  5. android:text=“我的订单” />
  6. <ImageView
  7. android:id=“@+id/iv_drop_down”
  8. style=“@style/PersonalRightIconStyle”
  9. android:src=“@drawable/android_list_idex” />
  10. </RelativeLayout>
  11. </LinearLayout>

 

linearlayout布局属性代码:

  1. <style name=“PersonalMainLayoutStyle”>
  2. <item name=“android:layout_width”>match_parent</item>
  3. <item name=“android:layout_height”>wrap_content</item>
  4. <item name=“android:layout_margin”>10dp</item>
  5. <item name=“android:background”>@drawable/background_corners</item>
  6. <item name=“android:orientation”>vertical</item>
  7. <item name=“android:padding”>1dp</item>
  8. </style>

 

relativelayout布局属性代码:

  1. <style name=“FindBottomStyle”>
  2. <item name=“android:layout_width”>match_parent</item>
  3. <item name=“android:layout_height”>30dp</item>
  4. <item name=“android:layout_margin”>5dp</item>
  5. <item name=“android:background”>@drawable/more_activity_item_selector_bottom_corners</item>
  6. </style>

 

textview和imageview的属性代码可以自己设计了。

下面是drawable的设计代码.
看到上边relativelayout的item中引用了drawable-more_activity_item_selector_bottom_corners,个人感觉好像没什么卵用,主要是linearlayout的drawable,但是我没试,还是贴出来吧
relativelayout-drawable:

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <selector
  3. xmlns:android=“http://schemas.android.com/apk/res/android”>
  4. <item android:state_pressed=“true”>
  5. <shape>
  6. <solid android:color=“#ffffe381” />
  7. <stroke android:width=“0.0dip” android:color=“#ffcccccc” />
  8. <corners android:bottomLeftRadius=“6.0dip” android:bottomRightRadius=“6.0dip” />
  9. </shape>
  10. </item>
  11. <item>
  12. <shape>
  13. <solid android:color=“#ffffffff” />
  14. <stroke android:width=“0.0dip” android:color=“#ffcccccc” />
  15. <corners android:bottomLeftRadius=“6.0dip” android:bottomRightRadius=“6.0dip” />
  16. </shape>
  17. </item>
  18. </selector>

 

linearlayout-drawable:

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <selector xmlns:android=“http://schemas.android.com/apk/res/android”>
  3. <item><shape>
  4. <solid android:color=“#ffffffff” />
  5. <stroke android:width=“1.0dip” android:color=“#ffcccccc” />
  6. <corners android:radius=“6.0dip” />
  7. </shape></item>
  8. </selector>

 

Android自动化构建之Ant多渠道打包实践(下)

前言
上一篇(Android自动化构建之Ant多渠道打包实践(上))已经介绍了Android的apk是如何构建的,本篇博客继续Ant打包的实践过程。

集成友盟统计SDK
这里以友盟统计为例,对各个渠道进行统计,我们需要先集成它的SDK

配置权限

<!– 权限 –>
<uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE” >
</uses-permission>
<uses-permission android:name=”android.permission.ACCESS_WIFI_STATE” />
<uses-permission android:name=”android.permission.INTERNET” >
</uses-permission>
<uses-permission android:name=”android.permission.READ_PHONE_STATE” >
</uses-permission>

渠道配置

<!– 友盟统计配置 –>
<meta-data
android:name=”UMENG_APPKEY”
android:value=”56f0b1ef67e58eded700015b” >
</meta-data>
<meta-data android:name=”UMENG_CHANNEL” android:value=”Umeng” />

使用Ant打包的时候替换的渠道号就是<meta-data android:name=”UMENG_CHANNEL” android:value=”Umeng” /> 将Umeng替换为具体的渠道,比如将Umeng替换为xiaomi。

定义build.properties文件
这个文件定义了Ant脚本要用到的一些参数值,我们的渠道也是定义在这里,具体看代码:

#project name and version
project.name=AntBuild
project.version=4.1.4

#android platform version
android-platform=android-19

#keysore file
ketstore.file=release.keystore
key.alias=release.keystore
key.alias.password=123456
key.store.password=123456

#publish channel
channelname=Umeng
channelkey=360,QQ,xiaomi,liangxiang
key=360,QQ,xiaomi,liangxiang

#library project
library-dir=../Library
library-dir2=../Library2
# generate R.java for libraries. Separate libraries with ‘:’.
extra-library-packages=

#filnal out dir
out.dir=publish

完整的Ant脚本
<?xml version=”1.0″ encoding=”UTF-8″?>
<project name=”iReaderApp” default=”deploy” >

<!–打包配置 –>
<property file=”build.properties” />

<!– ANT环境变量 –>
<property environment=”env” />

<!– 版本 –>
<property name=”version” value=”${project.version}” />

<!– 应用名称 –>
<property name=”appName” value=”${project.name}” />

<!– SDK目录(获取操作系统环境变量ANDROID_SDK_HOME的值) –>
<property name=”sdk-folder” value=”${env.ANDROID_SDK_HOME}” />

<!– SDK指定平台目录 –>
<property name=”sdk-platform-folder” value=”${sdk-folder}/platforms/android-19″/>

<!– SDK中tools目录 –>
<property name=”sdk-tools” value=”${sdk-folder}/tools” />

<!– SDK指定平台中tools目录 –>
<property name=”sdk-platform-tools” value=”${sdk-folder}/build-tools/android-4.4.2″ />

<!– 使用到的命令 –>
<property name=”aapt” value=”${sdk-platform-tools}/aapt” />

<!– 第三方library –>
<property name=”library-dir” value=”${library-dir}” />
<property name=”library-dir2″ value=”${library-dir2}” />

<!– 使用到的命令(当前系统为windows,如果系统为linux,可将.bat文件替换成相对应的命令) –>
<property name=”aapt” value=”${sdk-platform-tools}/aapt” />
<property name=”aidl” value=”${sdk-platform-tools}/aidl” />
<property name=”dx” value=”${sdk-platform-tools}/dx.bat” />
<property name=”apkbuilder” value=”${sdk-tools}/apkbuilder.bat” />
<property name=”jarsigner” value=”${env.JAVA_HOME}/bin/jarsigner” />
<property name=”zipalign” value=”${sdk-tools}/zipalign” />

<!– 编译需要的jar; 如果项目使用到地图服务则需要maps.jar –>
<property name=”android-jar” value=”${sdk-platform-folder}/android.jar” />
<property name=”proguard-home” value=”${sdk-tools}/proguard/lib” />

<!– 编译aidl文件所需的预处理框架文件framework.aidl –>
<property name=”framework-aidl” value=”${sdk-platform-folder}/framework.aidl” />

<!– 清单文件 –>
<property name=”manifest-xml” value=”AndroidManifest.xml” />

<!– 源文件目录 –>
<property name=”resource-dir” value=”res” />
<property name=”asset-dir” value=”assets” />

<!– java源文件目录 –>
<property name=”srcdir” value=”src” />
<property name=”srcdir-ospath” value=”${basedir}/${srcdir}” />

<!– 外部类库所在目录 –>
<property name=”external-lib” value=”libs” />
<property name=”external-compile-lib” value=”compile-libs” />

<property name=”external-lib-ospath” value=”${basedir}/${external-lib}” />
<property name=”external-compile-lib-ospath” value=”${basedir}/${external-compile-lib}” />

<property name=”external-library-dir-lib-ospath” value=”${library-dir}/${external-lib}” />
<property name=”external-library-dir2-lib-ospath” value=”${library-dir2}/${external-lib}” />

<!– 使用第三方的ant包,使ant支持for循环–>
<taskdef resource=”net/sf/antcontrib/antcontrib.properties”>
<classpath>
<pathelement location=”${external-lib-ospath}/ant-contrib-1.0b3.jar” />
</classpath>
</taskdef>

<property name=”channelname” value=”${channelname}” />
<property name=”channelkey” value=”${channelkey}” />

<!– 渠道名:渠道号 –>
<!– <property name=”key” value=”UMENG_CHANNEL:goapk,UMENG_CHANNEL:QQ” /> –>
<property name=”key” value=”${key}” />

<!–循环打包 –>
<target name=”deploy”>
<foreach target=”modify_manifest” list=”${key}” param=”nameandchannel” delimiter=”,”>
</foreach>
</target>

<target name=”modify_manifest”>
<!– 获取渠道名字 –>
<!– <propertyregex override=”true” property=”channelname” input=”${nameandchannel}” regexp=”(.*):” select=”\1″ /> –>
<!– 获取渠道号码 –>
<propertyregex override=”true” property=”channelkey” input=”${nameandchannel}” regexp=”(.*)” select=”\1″ />
<!– 正则匹配替换渠道号(这里pattern里的内容要与mainfest文件的内容一致,包括顺序,空格) –>
<replaceregexp flags=”g” byline=”false” encoding=”UTF-8″>
<regexp pattern=’meta-data android:name=”UMENG_CHANNEL” android:value=”(.*)”‘ />
<substitution expression=’meta-data android:name=”UMENG_CHANNEL” android:value=”${channelkey}”‘ />
<fileset dir=”” includes=”AndroidManifest.xml” />
</replaceregexp>
<antcall target=”zipalign” />
</target>

<!– 初始化工作 –>
<target name=”init”>
<echo>目录初始化….</echo>

<!– 生成R文件的相对目录 –>
<var name=”outdir-gen” value=”gen” />

<!– 编译后的文件放置目录 –>
<var name=”outdir-bin” value=”${out.dir}/${channelkey}” />

<!– 生成class目录 –>
<var name=”outdir-classes” value=”${outdir-bin}/otherfile” />
<var name=”outdir-classes-ospath” value=”${basedir}/${outdir-classes}” />

<!– classes.dex相关变量 –>
<var name=”dex-file” value=”classes.dex” />
<var name=”dex-path” value=”${outdir-bin}/${dex-file}” />
<var name=”dex-ospath” value=”${basedir}/${dex-path}” />

<!– 经过aapt生成的资源包文件 –>
<var name=”resources-package” value=”${outdir-bin}/resources.ap_” />
<var name=”resources-package-ospath” value=”${basedir}/${resources-package}” />

<!– 未认证apk包 –>
<var name=”out-unsigned-package” value=”${outdir-bin}/${appName}-unsigned.apk” />
<var name=”out-unsigned-package-ospath” value=”${basedir}/${out-unsigned-package}” />

<!– 证书文件 –>
<var name=”keystore-file” value=”${basedir}/${ketstore.file}” />
<!– <span style=”white-space:pre”> </span> 当前时间 –>
<!– <span style=”white-space:pre”> </span><tstamp> –>
<!– <span style=”white-space:pre”> </span> <format property=”nowtime” pattern=”yyyyMMdd”></format>–>
<!– <span style=”white-space:pre”> </span></tstamp> –>

<!– 已认证apk包 –>
<var name=”out-signed-package” value=”${outdir-bin}/${appName}_${channelkey}_${version}.apk” />
<var name=”out-signed-package-ospath” value=”${basedir}/${out-signed-package}” />
<delete dir=”${outdir-bin}” />
<mkdir dir=”${outdir-bin}” />
<mkdir dir=”${outdir-classes}” />
</target>

<!– 根据工程中的资源文件生成R.java文件 –>
<target name=”gen-R” depends=”init”>
<echo>生成R.java文件….</echo>
<exec executable=”${aapt}” failonerror=”true”>
<arg value=”package” />
<arg value=”-m” />
<arg value=”–auto-add-overlay” />
<arg value=”-J” />

<!–R.java文件的生成路径–>
<arg value=”${outdir-gen}” />

<!– 指定清单文件 –>
<arg value=”-M” />
<arg value=”${manifest-xml}” />

<!– 指定资源目录 –>
<arg value=”-S” />
<arg value=”${resource-dir}” />

<arg value=”-S” />
<arg value=”${library-dir}/res” /><!– 注意点:同时需要调用Library的res–>

<arg value=”-S” />
<arg value=”${library-dir2}/res” /><!– 注意点:同时需要调用Library的res–>

<!– 导入类库 –>
<arg value=”-I” />
<arg value=”${android-jar}” />
</exec>
</target>

<!– 编译aidl文件 –>
<target name=”aidl” depends=”gen-R”>
<echo>编译aidl文件….</echo>
<apply executable=”${aidl}” failonerror=”true”>
<!– 指定预处理文件 –>
<arg value=”-p${framework-aidl}” />
<!– aidl声明的目录 –>
<arg value=”-I${srcdir}” />
<!– 目标文件目录 –>
<arg value=”-o${outdir-gen}” />
<!– 指定哪些文件需要编译 –>
<fileset dir=”${srcdir}”>
<include name=”**/*.aidl” />
</fileset>
</apply>
</target>

<!– 将工程中的java源文件编译成class文件 –>
<target name=”compile” depends=”aidl”>
<echo>java源文件编译成class文件….</echo>

<!– 库应用1编译class 生成的class文件全部保存到outdir-classes目录下–>
<javac encoding=”UTF-8″ destdir=”${outdir-classes}” bootclasspath=”${android-jar}”>
<src path=”${library-dir}/src” /><!– 库应用源码 –>
<src path=”${outdir-gen}” /><!– R.java 资源类的导入 –>
<classpath>
<fileset dir=”${external-library-dir-lib-ospath}” includes=”*.jar” /><!– 第三方jar包需要引用,用于辅助编译 –>
</classpath>
</javac>

<!– 库应用2编译class –>
<javac encoding=”UTF-8″ destdir=”${outdir-classes}” bootclasspath=”${android-jar}”>
<src path=”${library-dir2}/src” /><!– 库应用源码 –>
<src path=”${outdir-gen}” /><!–生成的class文件全部保存到bin/classes目录下 –>
<classpath>
<fileset dir=”${external-library-dir2-lib-ospath}” includes=”*.jar” /><!– 第三方jar包需要引用,用于辅助编译 –>
</classpath>
</javac>

<!– 主应用编译class –>
<javac encoding=”UTF-8″ destdir=”${outdir-classes}” bootclasspath=”${android-jar}” >
<compilerarg line=”-encoding UTF-8 ” />
<!– <compilerarg line=”-encoding UTF-8 “/> –>
<src path=”${basedir}/src” /><!– 工程源码–>
<src path=”${outdir-gen}” /><!–R.java 资源类的导入 –>

<!– 编译java文件依赖jar包的位置 –>
<classpath>
<fileset dir=”${external-lib}” includes=”*.jar” /><!– 第三方jar包需要引用,用于辅助编译 –>
<!– <fileset dir=”${external-compile-lib}” includes=”*.jar”/>第三方jar包需要引用,用于辅助编译
–> <fileset dir=”${external-library-dir-lib-ospath}” includes=”*.jar” /><!– 第三方jar包需要引用,用于辅助编译 –>
</classpath>
</javac>
</target>

<!–执行代码混淆–>

<!– 将.class文件转化成.dex文件 –>
<target name=”dex” depends=”compile” unless=”do.not.compile”>
<echo>将.class文件转化成.dex文件….</echo>
<exec executable=”${dx}” failonerror=”true”>
<arg value=”–dex” />

<!– 输出文件 –>
<arg value=”–output=${dex-ospath}” />

<!– 要生成.dex文件的源classes和libraries –>
<arg value=”${outdir-classes-ospath}” />
<arg value=”${external-lib-ospath}” />
<!– <arg value=”${external-library-dir-lib-ospath}” />
<arg value=”${external-library-dir2-lib-ospath}” /> –>
</exec>
</target>

<!– 将资源文件放进输出目录 –>
<target name=”package-res-and-assets”>
<echo>将资源文件放进输出目录….</echo>
<exec executable=”${aapt}” failonerror=”true”>
<arg value=”package” />
<arg value=”-f” />
<arg value=”-M” />
<arg value=”${manifest-xml}” />

<arg value=”-S” />
<arg value=”${resource-dir}” />

<arg value=”-S”/>
<arg value=”${library-dir}/res”/>

<arg value=”-S”/>
<arg value=”${library-dir2}/res”/>

<arg value=”-A” />
<arg value=”${asset-dir}” />
<arg value=”-I” />
<arg value=”${android-jar}” />
<arg value=”-F” />
<arg value=”${resources-package}” />
<arg value=”–auto-add-overlay” />
</exec>
</target>

<!– 打包成未签证的apk –>
<target name=”package” depends=”dex,package-res-and-assets”>
<echo>打包成未签证的apk….</echo>
<java classpath=”${sdk-tools}/lib/sdklib.jar” classname=”com.android.sdklib.build.ApkBuilderMain”>

<!– 输出路径 –>
<arg value=”${out-unsigned-package-ospath}” />
<arg value=”-u” />
<arg value=”-z” />

<!– 资源压缩包 –>
<arg value=”${resources-package-ospath}” />
<arg value=”-f” />

<!– dex压缩文件 –>
<arg value=”${dex-ospath}” />

<arg value=”-rj” />
<arg value=”${external-lib-ospath}”/>

<!– 将主项目libs下面的so库打包 –>
<arg value=”-nf” />
<arg value=”${external-lib-ospath}” />
</java>
</target>

<!– 对apk进行签证 –>
<target name=”jarsigner” depends=”package”>
<echo>Packaging signed apk for release…</echo>
<exec executable=”${jarsigner}” failonerror=”true”>
<arg value=”-keystore” />
<arg value=”${keystore-file}” />
<arg value=”-storepass” />
<arg value=”${key.store.password}” />
<arg value=”-keypass” />
<arg value=”${key.alias.password}” />
<arg value=”-signedjar” />
<arg value=”${out-signed-package-ospath}” />
<arg value=”${out-unsigned-package-ospath}” />
<!– 不要忘了证书的别名 –>
<arg value=”${key.alias}” />
</exec>
</target>

<!– 发布 –>
<target name=”release” depends=”jarsigner”>
<!– 删除未签证apk –>
<delete file=”${out-unsigned-package-ospath}” />
<echo>APK is released. path:${out-signed-package-ospath}</echo>
<echo>删除其他文件,*后只保留apk</echo>
<delete dir=”${outdir-classes}”/>
<delete file=”${dex-ospath}” />
<delete file=”${resources-package-ospath}” />
<echo>生成apk完成</echo>
</target>

<!– 打包的应用程序进行优化 –>
<target name=”zipalign” depends=”release”>
<exec executable=”${zipalign}” failonerror=”true”>
<arg value=”-v” />
<arg value=”4″ />
<arg value=”${out-signed-package-ospath}” />
<arg value=”${out-signed-package-ospath}-zipaligned.apk” />
</exec>
</target>

</project>

上面就是完整的Ant脚本,实现了自动化构建和多渠道的打包,笔者在实践的过程踩过不少坑才*终把apk包成功打出。

这里总结下可能遇到的坑:
– 生成R.java文件,一定要注意先后顺序,主项目之后才到关联项目
– 编译生成class文件,可能会遇到找不到类,一定要按照添加库的顺序来编译class文件
– 替换渠道号的时候,Ant中pattern里的内容要与mainfest文件的内容一致,包括顺序,空格),笔者试过格式化后代码之后就不能写入成功

build.bat脚本

@echo off
call ant -buildfile “build.xml” deploy
echo done
pause
exit

测试结果
我们可以在项目中的publish目录下生成不同渠道的apk文件:

%title插图%num

安装apk到设备,启动之后在友盟后台集成测试,看app发布的渠道:

%title插图%num
Demo例子欢迎大家star
https://github.com/devilWwj/Android-Tech/tree/master/AntBuildTest

总结
实现Ant多渠道打包整个过程还是比较繁琐的,主要在Ant脚本上,比较容易出错,需要对命令比较了解,但确实能够缩短我们打渠道包的时间,基于本次实践是基于Eclipse,目前Android Studio使用gradle来实现多渠道打包,以后会把gradle进行多渠道打包的实现分享给大家,大家可以对比下这两种打包方式的区别,主要目的是更加深入的了解apk的构建过程。

如何快速实现项目上传至GitHub(详细步骤)

前言:

本文主要讲解如何将Android Studio项目上传至GitHub,在此之前,先介绍几个概念。

Android Studio:是谷歌推出一个Android集成开发工具,基于IntelliJ IDEA,类似 Eclipse ADT,Android Studio 提供了集成的 Android 开发工具用于开发和调试。

Git(分布式版本控制系统):是一个开源的分布式版本控制系统,可以有效、高速的处理从很小到非常大的项目版本管理。

GitHub:是一个面向开源及私有软件项目的托管平台,因为只支持Git作为唯一的版本库格式进行托管,故名GitHub。

 

准备:

1、安装Android Studio,并新建一个项目,下载地址

2、需要在本地安装Git版本控制系统,下载地址

3、在GitHub网站上注册一个账号,GitHub官网

 

步骤:

1、先安装Git

下载Git的exe,双击exe,一直next

记录下安装路径,默认安装在D:\Program Files\Git

 

2、将Android Studio与Git关联

打开Android Studio

进入File->Settings->Version Control(展开)->Git

在Path to Git executable栏位输入安装Git的路径,如下图所示:

%title插图%num

 

点击【Test】按钮,若出现Git executed successfully,则说明配置成功,同时也会提示Git的版本号,如下图:

%title插图%num

 

3、配置GitHub登录信息

进入File->Settings->Version Control(展开)->GitHub

在Login/Password栏位输入在GitHub网站注册的账号/密码,如下图:

%title插图%num

 

点击【Test】,提示Connection successful,则说明配置成功

%title插图%num

 

4、上传项目到GitHub

进入VCS->Import into Version Control->Share Project on GitHub,如下图:

%title插图%num

 

若是*次提交该项目,会弹出以下窗口,需填写项目名称以及描述,如下图:

%title插图%num

 

点击【Share】,如果下面没有问题,会弹出以下窗口

这里罗列出需要提交的类,以及各种资源配置文件等

%title插图%num

 

点击【OK】

在这一步出现了错误,错误如下:

%title插图%num

看了下提示信息,原来是Git没有配置全局用户的原因

解决方案:

(具体的解决方案可以参考我的另一篇博文:解决提交项目到GitHub,报错Please tell me who you are的问题)

找到Git安装目录下的Git Bash,运行后输入下面两行代码即可

git config --global user.email "you@example.com"  
git config --global user.name "Your Name"

%title插图%num

 

继续继续上传,若上传成功,在Android Studio右上角会有如下提示:

%title插图%num

 

5、此时,进入到你的GitHub网址,便可以看到提交成功的项目

%title插图%num

 

后续操作 之 项目更新:

当项目中需要新增类,或者修改类的时候,需要将其代码更新至GitHub中

1、新增类时:

在创建类时,就会提示你是否需要加入Git,如下图:

%title插图%num

 

点击【Yes】,该类就会加入到Git

%title插图%num

 

选择该类,右击,Git->Commit File,弹出下面窗口

%title插图%num

 

输入commit message,点击【Commit】,会弹出以下警告信息

忽略信息,继续点击【Commit】

%title插图%num

 

再次右击该类,Git->Repository->Push,如下图:

%title插图%num

 

在弹出的窗口中,继续点击【Push】

%title插图%num

 

此时,在GitHub中,便可以看到新增的类了

%title插图%num

 

2、修改类时:

右击需要修改的类,Git -> Add,如下图:

%title插图%num

 

后续的操作,便和新增类时的操作相同,不再赘述。

 

以上便是把一个Android Studio项目上传至GitHub的完整步骤。

XDM 有用 Mac M1 开发 Android 和 Flutter 的吗?

LZ 入手了 10 天左右,android 开发环境搭好了,跑 Android 项目没问题,比之前公司 i5+16G 内存的快。
之前 i5+16Gclean 后编译项目要 8 分钟,现在 Mac m1 16G 编译花费 2 分多。
Android 项目目前能正常开发。

Git 工具是用的是 SmartGit,也能正常使用。

Flutter 环境目前有点问题,使用的是 beta 分支的 SDK,flutter doctor 检测命令正常,AndroidStuio Flutter 和 Dart 插件也安装正常。但是连接真机跑代码时,却一直卡在“Running Gradle task ‘assembleDebug’…”,查看了下 Gradle 的目录,发现 Gradle-6.7-all.zip 下载正常。

XDM 有遇到 Flutter 卡在这一步的吗???

 

16 条回复    2021-02-04 22:42:42 +08:00
wuliaoshixia
    1

wuliaoshixia   63 天前

Android 开发的 JDK 使用的是 Zulu OpenJDK
leo7723
    2

leo7723   63 天前   ❤️ 1

单独打开安卓项目 RUN 一遍看下,至少报错会更详细一些。
有可能和机器没有关系。
zeropercenthappy
    3

zeropercenthappy   63 天前

这描述没法判断是机器的问题。
flutter-sdk/packages/flutter_tools/gradle/flutter.gradle 设置好镜像。
试试挂代理或者用手机热点再试试,可能是网络问题,其它依赖没下下来。
Carver9527
    4

Carver9527   63 天前 via iPhone

加个编译参数 v,把详细的日志打出来,看看卡在哪一步了
bullettrain1433
    5

bullettrain1433   63 天前

flutter 安装时候 termimal 用 rossetta 打开
HarryQu
    6

HarryQu   63 天前

M1 的编译速度可以提升这么多的吗?
hongch
    7

hongch   63 天前

“发现 Gradle-6.7-all.zip 下载正常“
那不就是正在下载 gradle 吗,你直接用 android studio 打开 Android 工程,在 android 工程下跑./gradlew assembleDebug 看进度
DGideas
    8

DGideas   63 天前

XDM 是啥,“兄弟们”?没别的意思哈,看着怎么这么奇怪呢 。。
janxin
    9

janxin   63 天前

之前看了一下 M1 下面这么折腾,我都是劝人还不如暂时不要用它做安卓相关开发。

Flutter 的支持目前也不好,开发的 M1 设备才拿到没几天,要完全兼容大概一段时间。而且现在要跑在 Rosetta 2 下面,不如 LZ 你看一下你的所有的环境是不是都跑在 Rosetta 2 下了?

unmois
    10

unmois   63 天前

宁是胡萝卜大佬吗
limerence12138
    11

limerence12138   63 天前

Android studio 还是没有原生适配啊,用起来卡卡的,还有一些页面会卡死
Richy
    12

Richy   63 天前

Android:目前 Android Studio 还没有适配 M1,可以用 IDEA 代替,不过目前 AGP 版本 4.1.1 无法直接安装 apk,需要新建 gradle 任务 [installDebug] ,每次安装后要手动打开 app,不知道有没有同学有解决方案

Flutter: 目前在用 VS Code Insiders 版本,暂时没有什么问题。gradle 运行慢大多是因为下载 gradle 太慢了。

wuliaoshixia
    13

wuliaoshixia   62 天前

@Carver9527
flutter doctor -v
[✓] Flutter (Channel beta, 1.25.0-9.0.pre.2, on macOS 11.2 20D64 darwin-arm,
locale zh-Hans-CN)
• Flutter version 1.25.0-9.0.pre.2 at /Users/sheep/Project/flutter_beta
• Framework revision 5d36f2e7f5 (3 weeks ago), 2021-01-14 15:57:49 -0800
• Engine revision 7a8f8ca02c
• Dart version 2.12.0 (build 2.12.0-133.7.beta)
• Pub download mirror https://pub.flutter-io.cn
• Flutter download mirror https://storage.flutter-io.cn

[✓] Android toolchain – develop for Android devices (Android SDK version 30.0.3)
• Android SDK at /Users/sheep/Library/Android/sdk
• Platform android-30, build-tools 30.0.3
• Java binary at: /Applications/Android
Studio.app/Contents/jre/jdk/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build
1.8.0_242-release-1644-b3-6915495)
• All Android licenses accepted.

[✓] Xcode – develop for iOS and macOS
• Xcode at /Applications/Xcode.app/Contents/Developer
• Xcode 12.4, Build version 12D4e
• CocoaPods version 1.10.1

[✓] Android Studio (version 4.1)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin can be installed from:
? https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
? https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build
1.8.0_242-release-1644-b3-6915495)

[✓] Connected device (1 available)
• SKW A0 (mobile) • 7692d5ee • android-arm64 • Android 10 (API 29)

wuliaoshixia
    14

wuliaoshixia   62 天前

@hongch gradle 应该是下载下来了,执行 flutter run -v,发现卡在下面这个步骤了
[ +914 ms] Welcome to Gradle 6.7!
[ ] Here are the highlights of this release:
[ ] – File system watching is ready for production use
[ ] – Declare the version of Java your build requires
[ ] – Java 15 support
[ ] For more details see https://docs.gradle.org/6.7/release-notes.html
[ +187 ms] Starting a Gradle Daemon, 3 busy Daemons could not be reused, use –status for details
wuliaoshixia
    15

wuliaoshixia   62 天前

找到原因了,原来是依赖没有下载下来?
[+378206 ms] FAILURE: Build failed with an exception.
[ +12 ms] * What went wrong:
[ ] A problem occurred configuring root project ‘android’.
[ ] > Could not resolve all artifacts for configuration ‘:classpath’.
[ ] > Could not download kotlin-compiler-embeddable-1.3.50.jar (org.jetbrains.kotlin:kotlin-compiler-embeddable:1.3.50)
[ ] > Could not get resource ‘https://jcenter.bintray.com/org/jetbrains/kotlin/kotlin-compiler-embeddable/1.3.50/kotlin-compiler-embeddable-1.3.50.jar’.
[ ] > Read timed out
[ ] > Could not download kotlin-daemon-embeddable-1.3.50.jar (org.jetbrains.kotlin:kotlin-daemon-embeddable:1.3.50)
[ ] > Could not get resource ‘https://jcenter.bintray.com/org/jetbrains/kotlin/kotlin-daemon-embeddable/1.3.50/kotlin-daemon-embeddable-1.3.50.jar’.
[ ] > Could not GET ‘https://jcenter.bintray.com/org/jetbrains/kotlin/kotlin-daemon-embeddable/1.3.50/kotlin-daemon-embeddable-1.3.50.jar’.
[ +17 ms] > Remote host closed connection during handshake
[ ] * Try:
[ ] Run with –stacktrace option to get the stack trace. Run with –info or –debug option to get more log output. Run with –scan to get full insights.
[ ] * Get more help at https://help.gradle.org
[ ] BUILD FAILED in 6m 19s
wuliaoshixia
    16

wuliaoshixia   62 天前

@zeropercenthappy 果然还是得用梯子先下一下依赖。。。

想阅读 Android 源码,请问该往哪个方向努力?

我是一年经验的 Android 开发,在某大厂工作。对 Android 的源码比较有兴趣。

尝试鼓捣过 AOSP,生成 ipr 文件后在 Android Studio 基本不报红。但是看源码抓瞎,不知道从何看起(如:①应用在安装过程中,Android 系统做了什么操作;②又或是状态栏下滑时,当前 TaskRecord 顶部的 Activity 会发生什么变化,等等),请问我该怎么做?

先感谢各位老哥老姐的回复!

12 条回复    2021-02-22 16:13:13 +08:00
MaxLi77
    1

MaxLi77   63 天前

半吊子先回答一下,Activity 相关 AMS,安装包管理 PMS,服务管理 Service Manager,进程通信 Binder 。懂的老哥可以多补充。
calloc
    2

calloc   63 天前 via iPhone

可以从上层 APP 入手,比如要分析安装 apk 时系统做了什么,我们知道系统在安装 apk 的时候 UI 界面是有所体现的,那么可以用`adb shell dumpsys activity top`命令查看当前界面对应的 Activity 。知道它的 Activity 就可以在源码中找到它对应的类,之后就可以用调试大法了。
ZSpirytus
    3

ZSpirytus   63 天前 via Android

@MaxLi77 感谢!我主要想搞懂某一个细节,比如说,点击安装后,系统做了什么之类的,直接看 AMS 和 PMS 的源码容易抓瞎。。
ZSpirytus
    4

ZSpirytus   63 天前 via Android

@calloc 感谢!这个思路巧妙,忘了还有 adb 这个工具,我试试看!
elonmask
    5

elonmask   63 天前 via Android   ❤️ 1

现在的安卓开发卷的厉害,想当年知道四大组件和生命周期就可以去 bat,现在又是 framework 又是 kernel 的,
12 年的时候我记得当时 QQ 安卓 app 的账号和密码都还放明文放 share preference 呢。如果
还年轻可以读个研或者自学换个方向,写界面是没有前途的。
MaxLi77
    6

MaxLi77   63 天前

@ZSpirytus 直接搜索 XXX 功能 + 源码分析,跟着别人的分析思路去看
m30102
    7

m30102   63 天前

@elonmask 以前 20 分的水平能一年一套房,现在 80 分的水平大概率睡桥洞
welkinshadow002
    8

welkinshadow002   62 天前

这里推荐 Gityuan 的相关博客,对整个流程的理解十分有帮助,http://gityuan.com/android/,我自己还买了一些书 android 系统内核设计思想啥的。

不过需要注意的是 android10 、android11 系统源码的改动比较大,自己还是要去跟一下

同样一年经验在学源码,共勉

ZSpirytus
    9

ZSpirytus   62 天前 via Android

@MaxLi77 哈哈,这个确实也是一种思路,我试试
ZSpirytus
    10

ZSpirytus   62 天前 via Android

@welkinshadow002 谢谢,这个我参考一下

ZSpirytus
    11

ZSpirytus   62 天前 via Android

@elonmask 我是纯兴趣的,只是好奇里面是怎么工作的
yolo0014
    12

yolo0014   44 天前

可以从 SystemUI 和 launcher 入手看看
友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速