Android MVVM框架搭建(二)OKHttp + Retrofit + RxJava(1),2024Android高级面试题总结

methodName = methodName.substring(0, 1).toUpperCase() + methodName.substring(1);

StringBuilder stringBuilder = new StringBuilder();

stringBuilder.append(“[ (”).append(className).append(“:”).append(lineNumber).append(“)#”).append(methodName).append(" ] ");

if (msg != null && type != JSON) {

stringBuilder.append(msg);

}

String logStr = stringBuilder.toString();

switch (type) {

case V:

Log.v(tag, logStr);

break;

case D:

Log.d(tag, logStr);

break;

case I:

Log.i(tag, logStr);

break;

case W:

Log.w(tag, logStr);

break;

case E:

Log.e(tag, logStr);

break;

case A:

Log.wtf(tag, logStr);

break;

case JSON: {

if (TextUtils.isEmpty(msg)) {

Log.d(tag, “Empty or Null json content”);

return;

}

String message = null;

try {

if (msg.startsWith(“{”)) {

JSONObject jsonObject = new JSONObject(msg);

message = jsonObject.toString(JSON_INDENT);

} else if (msg.startsWith(“[”)) {

JSONArray jsonArray = new JSONArray(msg);

message = jsonArray.toString(JSON_INDENT);

}

} catch (JSONException e) {

e(tag, e.getCause().getMessage() + “\n” + msg);

return;

}

printLine(tag, true);

message = logStr + LINE_SEPARATOR + message;

String[] lines = message.split(LINE_SEPARATOR);

StringBuilder jsonContent = new StringBuilder();

for (String line : lines) {

jsonContent.append("║ ").append(line).append(LINE_SEPARATOR);

}

Log.d(tag, jsonContent.toString());

printLine(tag, false);

}

break;

default:

break;

}

}

private static void printLine(String tag, boolean isTop) {

if (isTop) {

Log.d(tag, “╔═══════════════════════════════════════════════════════════════════════════════════════”);

} else {

Log.d(tag, “╚═══════════════════════════════════════════════════════════════════════════════════════”);

}

}

}

三、构建网络框架


1. Base

在通过网络请求返回数据时,先进行一个数据解析,得到结果码和错误信息,在network包下新建一个BaseResponse类,代码如下:

/**

  • 基础返回类

  • @author llw

*/

public class BaseResponse {

//返回码

@SerializedName(“res_code”)

@Expose

public Integer responseCode;

//返回的错误信息

@SerializedName(“res_error”)

@Expose

public String responseError;

}

然后再自定义一个BaseObserver类,继承自rxjava的Observer。依然在network包下创建,代码如下:

/**

  • 自定义Observer

  • @author llw

*/

public abstract class BaseObserver implements Observer {

//开始

@Override

public void onSubscribe(Disposable disposable) {

}

//继续

@Override

public void onNext(T t) {

onSuccess(t);

}

//异常

@Override

public void onError(Throwable e) {

onFailure(e);

}

//完成

@Override

public void onComplete() {

}

//成功

public abstract void onSuccess(T t);

//失败

public abstract void onFailure(Throwable e);

}

2. 异常处理

在实际的网络请求中有很多的异常信息和错误码,需要对这些信息要处理,在network包下新建一个errorhandler包,包下新建一个HttpErrorHandler类,代码如下:

/**

  • 网络错误处理

  • @author llw

*/

public class HttpErrorHandler implements Function<Throwable, Observable> {

/**

  • 处理以下两类网络错误:

  • 1、http请求相关的错误,例如:404,403,socket timeout等等;

  • 2、应用数据的错误会抛RuntimeException,最后也会走到这个函数来统一处理;

*/

@Override

public Observable apply(Throwable throwable) throws Exception {

//通过这个异常处理,得到用户可以知道的原因

return Observable.error(ExceptionHandle.handleException(throwable));

}

}

然后再在network包下创建一个ExceptionHandle类,代码如下:

/**

  • 异常处理

  • @author llw

*/

public class ExceptionHandle {

//未授权

private static final int UNAUTHORIZED = 401;

//禁止的

private static final int FORBIDDEN = 403;

//未找到

private static final int NOT_FOUND = 404;

//请求超时

private static final int REQUEST_TIMEOUT = 408;

//内部服务器错误

private static final int INTERNAL_SERVER_ERROR = 500;

//错误网关

private static final int BAD_GATEWAY = 502;

//暂停服务

private static final int SERVICE_UNAVAILABLE = 503;

//网关超时

private static final int GATEWAY_TIMEOUT = 504;

/**

  • 处理异常

  • @param throwable

  • @return

*/

public static ResponseThrowable handleException(Throwable throwable) {

//返回时抛出异常

ResponseThrowable responseThrowable;

if (throwable instanceof HttpException) {

HttpException httpException = (HttpException) throwable;

responseThrowable = new ResponseThrowable(throwable, ERROR.HTTP_ERROR);

switch (httpException.code()) {

case UNAUTHORIZED:

responseThrowable.message = “未授权”;

break;

case FORBIDDEN:

responseThrowable.message = “禁止访问”;

break;

case NOT_FOUND:

responseThrowable.message = “未找到”;

break;

case REQUEST_TIMEOUT:

responseThrowable.message = “请求超时”;

break;

case GATEWAY_TIMEOUT:

responseThrowable.message = “网关超时”;

break;

case INTERNAL_SERVER_ERROR:

responseThrowable.message = “内部服务器错误”;

break;

case BAD_GATEWAY:

responseThrowable.message = “错误网关”;

break;

case SERVICE_UNAVAILABLE:

responseThrowable.message = “暂停服务”;

break;

default:

responseThrowable.message = “网络错误”;

break;

}

return responseThrowable;

} else if (throwable instanceof ServerException) {

//服务器异常

ServerException resultException = (ServerException) throwable;

responseThrowable = new ResponseThrowable(resultException, resultException.code);

responseThrowable.message = resultException.message;

return responseThrowable;

} else if (throwable instanceof JsonParseException

|| throwable instanceof JSONException

|| throwable instanceof ParseException) {

responseThrowable = new ResponseThrowable(throwable, ERROR.PARSE_ERROR);

responseThrowable.message = “解析错误”;

return responseThrowable;

} else if (throwable instanceof ConnectException) {

responseThrowable = new ResponseThrowable(throwable, ERROR.NETWORK_ERROR);

responseThrowable.message = “连接失败”;

return responseThrowable;

} else if (throwable instanceof javax.net.ssl.SSLHandshakeException) {

responseThrowable = new ResponseThrowable(throwable, ERROR.SSL_ERROR);

responseThrowable.message = “证书验证失败”;

return responseThrowable;

} else if (throwable instanceof ConnectTimeoutException){

responseThrowable = new ResponseThrowable(throwable, ERROR.TIMEOUT_ERROR);

responseThrowable.message = “连接超时”;

return responseThrowable;

} else if (throwable instanceof java.net.SocketTimeoutException) {

responseThrowable = new ResponseThrowable(throwable, ERROR.TIMEOUT_ERROR);

responseThrowable.message = “连接超时”;

return responseThrowable;

}

else {

responseThrowable = new ResponseThrowable(throwable, ERROR.UNKNOWN);

responseThrowable.message = “未知错误”;

return responseThrowable;

}

}

/**

  • 约定异常

*/

public class ERROR {

/**

  • 未知错误

*/

public static final int UNKNOWN = 1000;

/**

  • 解析错误

*/

public static final int PARSE_ERROR = 1001;

/**

  • 网络错误

*/

public static final int NETWORK_ERROR = 1002;

/**

  • 协议出错

*/

public static final int HTTP_ERROR = 1003;

/**

  • 证书出错

*/

public static final int SSL_ERROR = 1005;

/**

  • 连接超时

*/

public static final int TIMEOUT_ERROR = 1006;

}

public static class ResponseThrowable extends Exception {

public int code;

public String message;

public ResponseThrowable(Throwable throwable, int code) {

super(throwable);

this.code = code;

}

}

public static class ServerException extends RuntimeException {

public int code;

public String message;

}

}

3. 拦截器

网络请求中拦截器的作用是比较大的,这里我们只做日志的打印。网络访问分为请求和返回两个部分,那么就对应两个拦截器。在network包下新建一个interceptor包,包下新建一个RequestInterceptor类,代码如下:

/**

  • 请求拦截器

  • @author llw

*/

public class RequestInterceptor implements Interceptor {

/**

  • 网络请求信息

*/

private INetworkRequiredInfo iNetworkRequiredInfo;

public RequestInterceptor(INetworkRequiredInfo iNetworkRequiredInfo){

this.iNetworkRequiredInfo = iNetworkRequiredInfo;

}

/**

  • 拦截

*/

@Override

public Response intercept(Chain chain) throws IOException {

String nowDateTime = DateUtil.getDateTime();

//构建器

Request.Builder builder = chain.request().newBuilder();

//添加使用环境

builder.addHeader(“os”,“android”);

//添加版本号

builder.addHeader(“appVersionCode”,this.iNetworkRequiredInfo.getAppVersionCode());

//添加版本名

builder.addHeader(“appVersionName”,this.iNetworkRequiredInfo.getAppVersionName());

//添加日期时间

builder.addHeader(“datetime”,nowDateTime);

//返回

return chain.proceed(builder.build());

}

}

这里是简单的打印了一下,app的版本号和版本名,因为实际开发中,可能有多个版本在进行测试,这样可以帮助快速区分。

下面是返回拦截器,在interceptor包下新建一个ResponseInterceptor类,代码如下:

/**

  • 返回拦截器(响应拦截器)

  • @author llw

*/

public class ResponseInterceptor implements Interceptor {

private static final String TAG = “ResponseInterceptor”;

/**

  • 拦截

*/

@Override

public Response intercept(Chain chain) throws IOException {

long requestTime = System.currentTimeMillis();

Response response = chain.proceed(chain.request());

KLog.i(TAG, “requestSpendTime=” + (System.currentTimeMillis() - requestTime) + “ms”);

return response;

}

}

4. 网络请求服务

前面的3步操作都属于准备环节,核心的地方在这里,也就是创建网络服务,这里会将OKHttp、Retrofit、RxJava串起来,在network包下新建一个NetworkApi类,里面的代码如下:

/**

  • 网络Api

  • @author llw

  • @description NetworkApi

*/

public class NetworkApi {

/**

  • 获取APP运行状态及版本信息,用于日志打印

*/

private static INetworkRequiredInfo iNetworkRequiredInfo;

/**

  • API访问地址

*/

private static final String BASE_URL = “https://cn.bing.com”;

private static OkHttpClient okHttpClient;

private static final HashMap<String, Retrofit> retrofitHashMap = new HashMap<>();

/**

  • 初始化

*/

public static void init(INetworkRequiredInfo networkRequiredInfo) {

iNetworkRequiredInfo = networkRequiredInfo;

}

/**

  • 创建serviceClass的实例

*/

public static T createService(Class serviceClass) {

return getRetrofit(serviceClass).create(serviceClass);

}

/**

  • 配置OkHttp

  • @return OkHttpClient

*/

private static OkHttpClient getOkHttpClient() {

//不为空则说明已经配置过了,直接返回即可。

if (okHttpClient == null) {

//OkHttp构建器

OkHttpClient.Builder builder = new OkHttpClient.Builder();

//设置缓存大小

int cacheSize = 100 * 1024 * 1024;

//设置网络请求超时时长,这里设置为6s

builder.connectTimeout(6, TimeUnit.SECONDS);

//添加请求拦截器,如果接口有请求头的话,可以放在这个拦截器里面

builder.addInterceptor(new RequestInterceptor(iNetworkRequiredInfo));

//添加返回拦截器,可用于查看接口的请求耗时,对于网络优化有帮助

builder.addInterceptor(new ResponseInterceptor());

//当程序在debug过程中则打印数据日志,方便调试用。

if (iNetworkRequiredInfo != null && iNetworkRequiredInfo.isDebug()) {

//iNetworkRequiredInfo不为空且处于debug状态下则初始化日志拦截器

HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();

//设置要打印日志的内容等级,BODY为主要内容,还有BASIC、HEADERS、NONE。

httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

//将拦截器添加到OkHttp构建器中

builder.addInterceptor(httpLoggingInterceptor);

}

//OkHttp配置完成

okHttpClient = builder.build();

}

return okHttpClient;

}

/**

  • 配置Retrofit

  • @param serviceClass 服务类

  • @return Retrofit

*/

private static Retrofit getRetrofit(Class serviceClass) {

if (retrofitHashMap.get(BASE_URL + serviceClass.getName()) != null) {

//刚才上面定义的Map中键是String,值是Retrofit,当键不为空时,必然有值,有值则直接返回。

return retrofitHashMap.get(BASE_URL + serviceClass.getName());

}

//初始化Retrofit Retrofit是对OKHttp的封装,通常是对网络请求做处理,也可以处理返回数据。

//Retrofit构建器

Retrofit.Builder builder = new Retrofit.Builder();

//设置访问地址

builder.baseUrl(BASE_URL);

//设置OkHttp客户端,传入上面写好的方法即可获得配置后的OkHttp客户端。

builder.client(getOkHttpClient());

//设置数据解析器 会自动把请求返回的结果(json字符串)通过Gson转化工厂自动转化成与其结构相符的实体Bean

builder.addConverterFactory(GsonConverterFactory.create());

//设置请求回调,使用RxJava 对网络返回进行处理

builder.addCallAdapterFactory(RxJava2CallAdapterFactory.create());

//retrofit配置完成

Retrofit retrofit = builder.build();

//放入Map中

retrofitHashMap.put(BASE_URL + serviceClass.getName(), retrofit);

//最后返回即可

return retrofit;

}

/**

  • 配置RxJava 完成线程的切换

  • @param observer 这个observer要注意不要使用lifecycle中的Observer

  • @param 泛型

  • @return Observable

*/

public static ObservableTransformer<T, T> applySchedulers(final Observer observer) {

return upstream -> {

Observable observable = upstream

.subscribeOn(Schedulers.io())//线程订阅

.observeOn(AndroidSchedulers.mainThread())//观察Android主线程

.map(NetworkApi.getAppErrorHandler())//判断有没有500的错误,有则进入getAppErrorHandler

.onErrorResumeNext(new HttpErrorHandler<>());//判断有没有400的错误

//订阅观察者

observable.subscribe(observer);

return observable;

};

}

/**

  • 错误码处理

*/

protected static Function<T, T> getAppErrorHandler() {

return response -> {

//当response返回出现500之类的错误时

if (response instanceof BaseResponse && ((BaseResponse) response).responseCode >= 500) {

//通过这个异常处理,得到用户可以知道的原因

ExceptionHandle.ServerException exception = new ExceptionHandle.ServerException();

exception.code = ((BaseResponse) response).responseCode;

exception.message = ((BaseResponse) response).responseError != null ? ((BaseResponse) response).responseError : “”;

throw exception;

}

return response;

};

}

}

网络框架就构建完成了,network包内容如下图所示:

在这里插入图片描述

这个网络框架在使用前需要先进行初始化,后面有使用的实例,代码中的注释应该是很明白了,总的来说就是一个思路,OkHttp做底层的网络访问,Retrofit做上层网络请求接口的封装,同时将需要的数据解析成实体,同时Retrofit还有对RxJava的支持,这样就可以在请求的时候做线程切换,切换到子线程,在数据返回的时候切换到主线程。避免了在主线程中进行耗时操作的问题。因此那么多人说Retrofit强大是有原因的。因为你不会看到有人直接拿OKHttp + Rxjava进行使用而跳过Retrofit的。所以这个组合使用是有其道理在里面的。对于任何不了解的事情,都不要急着下结论。

四、使用网络框架


网络框架搭建好了,下面也要能够使用才行对吧,这里我通过访问必应的每日一图来作为演示,必应每日一图的访问地址如下所示:

“https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1”

不管拿到任何API接口都要先进行一次测试,这是对自己负责,不过过于相信别人,否则你会吃亏的。

在这里插入图片描述

通过浏览器访问得到返回结果,然后我们通过返回的数据构建一个实体Bean。

1. 创建返回实体

在model包下新建一个BiYingResponse类,代码如下:

/**

  • 必应访问接口返回数据实体

  • @author llw

  • @description BiYingImgResponse

*/

public class BiYingResponse {

private TooltipsBean tooltips;

private List images;

public TooltipsBean getTooltips() {

return tooltips;

}

public void setTooltips(TooltipsBean tooltips) {

this.tooltips = tooltips;

}

public List getImages() {

return images;

}

public void setImages(List images) {

this.images = images;

}

public static class TooltipsBean {

private String loading;

private String previous;

private String next;

private String walle;

private String walls;

public String getLoading() {

return loading;

}

public void setLoading(String loading) {

this.loading = loading;

}

public String getPrevious() {

return previous;

}

public void setPrevious(String previous) {

this.previous = previous;

}

public String getNext() {

return next;

}

public void setNext(String next) {

this.next = next;

}

public String getWalle() {

return walle;

}

public void setWalle(String walle) {

this.walle = walle;

}

public String getWalls() {

return walls;

}

public void setWalls(String walls) {

this.walls = walls;

}

}

public static class ImagesBean {

private String startdate;

private String fullstartdate;

private String enddate;

private String url;

private String urlbase;

private String copyright;

private String copyrightlink;

private String title;

private String quiz;

private boolean wp;

private String hsh;

private int drk;

private int top;

private int bot;

private List<?> hs;

public String getStartdate() {

return startdate;

}

public void setStartdate(String startdate) {

this.startdate = startdate;

}

public String getFullstartdate() {

return fullstartdate;

}

public void setFullstartdate(String fullstartdate) {

this.fullstartdate = fullstartdate;

}

public String getEnddate() {

return enddate;

}

public void setEnddate(String enddate) {

this.enddate = enddate;

}

public String getUrl() {

return url;

}

public void setUrl(String url) {

this.url = url;

}

public String getUrlbase() {

return urlbase;

}

public void setUrlbase(String urlbase) {

this.urlbase = urlbase;

}

public String getCopyright() {

return copyright;

}

public void setCopyright(String copyright) {

this.copyright = copyright;

}

public String getCopyrightlink() {

return copyrightlink;

}

public void setCopyrightlink(String copyrightlink) {

this.copyrightlink = copyrightlink;

}

public String getTitle() {

return title;

}

public void setTitle(String title) {

this.title = title;

}

public String getQuiz() {

return quiz;

}

public void setQuiz(String quiz) {

this.quiz = quiz;

}

public boolean isWp() {

return wp;

}

public void setWp(boolean wp) {

this.wp = wp;

}

public String getHsh() {

return hsh;

}

public void setHsh(String hsh) {

this.hsh = hsh;

}

public int getDrk() {

return drk;

}

public void setDrk(int drk) {

this.drk = drk;

}

public int getTop() {

return top;

}

public void setTop(int top) {

this.top = top;

}

public int getBot() {

return bot;

}

public void setBot(int bot) {

this.bot = bot;

}

public List<?> getHs() {

return hs;

}

public void setHs(List<?> hs) {

this.hs = hs;

}

}

}

2. 创建ApiService

在com.llw.mvvm包下新建一个api包,api包下新建一个ApiService类,代码如下:

/**

  • 所有的Api网络接口

  • @author llw

*/

public interface ApiService {

/**

  • 必应每日一图

*/

@GET(“/HPImageArchive.aspx?format=js&idx=0&n=1”)

Observable biying();

}

这里的意思很明白就是,把一个完整的网络连接进行一个拆分,一部分是不变的,一部分是变化的,这也符合实际开发中的需求,一个服务器上有多个接口,这样做在更改服务器的时候就只要更改不变的一处就可以了。这里的Observable依然是RxJava中的,不要导错了。

3. 创建数据存储

首先在com.llw.mvvm包下面创建一个repository包,repository包下新建一个MainRepository类,里面的代码如下:

/**

  • Main存储库 用于对数据进行处理

  • @author llw

*/

public class MainRepository {

@SuppressLint(“CheckResult”)

public MutableLiveData getBiYing() {

final MutableLiveData biyingImage = new MutableLiveData<>();

ApiService apiService = NetworkApi.createService(ApiService.class);

apiService.biying().compose(NetworkApi.applySchedulers(new BaseObserver() {

@Override

public void onSuccess(BiYingResponse biYingImgResponse) {

KLog.d(new Gson().toJson(biYingImgResponse));

biyingImage.setValue(biYingImgResponse);

}

@Override

public void onFailure(Throwable e) {

KLog.e("BiYing Error: " + e.toString());

}

}));

return biyingImage;

}

}

这里就是对刚才的网络接口进行请求,然后返回LiveData。这里为什么要单独建一个包来管理页面的数据获取,其实你可以将这里的代码写到MainViewModel中,但是你得保证唯一性,因为假如你一个接口在多个地方会使用,你每一个都写到对应的ViewModel中,是不是就会有很多的重复代码?这样就不是很好。现在这样做虽然会麻烦一些,但是好处是很多的,因为我们现在也只是获取网络数据,实际中App的数据还有多个来源,本地数据库、本地缓存。都是可以拿数据的。这些环节如果要写的话,都是要写在这个Repository中的,如果你放到ViewModel中,会导致里面的代码量很大,因为你一个ViewModel中可能有多个网络请求,这很正常。

本来下一步就是应该要去MainViewModel中调用刚才MainRepository中的方法了,但是由于之前MainViewModel中有上一篇文章的代码,因此我们需要做一个转移。说白了,就是新建一个LoginActivity去把MainActivity的内容都移过去,这一步我就只贴代码了,不做说明了,因为上一篇已经说过了。

在com.llw.mvvm包下新建一个LoginActivity,对应的布局是activity_login.xml,下面在viewmodels包下新建一个LoginViewModel类,代码如下:

/**

  • 登录页面ViewModel

  • @author llw

*/

public class LoginViewModel extends ViewModel {

public MutableLiveData user;

public MutableLiveData getUser(){

if(user == null){

user = new MutableLiveData<>();

}

return user;

}

}

然后修改activity_login.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android=“http://schemas.android.com/apk/res/android”

xmlns:app=“http://schemas.android.com/apk/res-auto”

xmlns:tools=“http://schemas.android.com/tools”>

<variable

name=“viewModel”

type=“com.llw.mvvm.viewmodels.LoginViewModel” />

<LinearLayout

android:layout_width=“match_parent”

android:layout_height=“match_parent”

android:gravity=“center”

android:orientation=“vertical”

android:padding=“32dp”>

<TextView

android:id=“@+id/tv_account”

android:text=“@{viewModel.user.account}”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”/>

<TextView

android:layout_marginBottom=“24dp”

android:id=“@+id/tv_pwd”

android:text=“@{viewModel.user.pwd}”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”/>

<com.google.android.material.textfield.TextInputLayout

android:layout_width=“match_parent”

android:layout_height=“wrap_content”>

<com.google.android.material.textfield.TextInputEditText

android:id=“@+id/et_account”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:background=“@color/white”

android:text=“@={viewModel.user.account}”

android:hint=“账号” />

</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:layout_marginTop=“12dp”>

<com.google.android.material.textfield.TextInputEditText

android:id=“@+id/et_pwd”

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:background=“@color/white”

android:text=“@={viewModel.user.pwd}”

android:hint=“密码”

android:inputType=“textPassword” />

</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.button.MaterialButton

android:id=“@+id/btn_login”

android:layout_width=“match_parent”

android:layout_height=“48dp”

android:layout_margin=“24dp”

android:insetTop=“0dp”

android:insetBottom=“0dp”

android:text=“登 录”

app:cornerRadius=“12dp” />

下面修改LoginActivity中的代码

public class LoginActivity extends AppCompatActivity {

private ActivityLoginBinding dataBinding;

private LoginViewModel loginViewModel;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

//数据绑定视图

dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_login);

loginViewModel = new LoginViewModel();

//Model → View

User user = new User(“admin”, “123456”);

loginViewModel.getUser().setValue(user);

//获取观察对象

MutableLiveData user1 = loginViewModel.getUser();

user1.observe(this, user2 -> dataBinding.setViewModel(loginViewModel));

dataBinding.btnLogin.setOnClickListener(v -> {

if (loginViewModel.user.getValue().getAccount().isEmpty()) {

Toast.makeText(LoginActivity.this, “请输入账号”, Toast.LENGTH_SHORT).show();

return;

}

if (loginViewModel.user.getValue().getPwd().isEmpty()) {

Toast.makeText(LoginActivity.this, “请输入密码”, Toast.LENGTH_SHORT).show();

return;

}

Toast.makeText(LoginActivity.this, “登录成功”, Toast.LENGTH_SHORT).show();

startActivity(new Intent(LoginActivity.this,MainActivity.class));

});

}

}

好了,进入下一步,这里需要对项目进行配置了

4. 项目环境配置

涉及到网络时,需要注意一点,就是在Android8.0之上的版本都默认使用Https访问了,需要要允许Http访问的话,需要进行一次配置。

首先在res下新建一个xml文件夹,文件夹下新建一个network_config.xml,里面的代码如下:

<?xml version="1.0" encoding="utf-8"?>

下面要创建一个实现类,实现network包中的INetworkRequiredInfo接口,在com.llw.mvvm包下新建一个NetworkRequiredInfo

/**

  • 网络访问信息

  • @author llw

*/

public class NetworkRequiredInfo implements INetworkRequiredInfo {

private final Application application;

public NetworkRequiredInfo(Application application){

this.application = application;

}

/**

  • 版本名

*/

@Override

public String getAppVersionName() {

return BuildConfig.VERSION_NAME;

}

/**

  • 版本号

*/

@Override

public String getAppVersionCode() {

return String.valueOf(BuildConfig.VERSION_CODE);

}

/**

  • 是否为debug

*/

@Override

public boolean isDebug() {

return BuildConfig.DEBUG;

}

/**

  • 应用全局上下文

*/

@Override

public Application getApplicationContext() {

return application;

}

}

这个要在AndroidManifest.xml中做配置,不过先不着急,先在com.llw.mvvm包下创建一个BaseApplication类,里面的代码如下:

/**

  • 自定义 Application

  • @author llw

*/

public class BaseApplication extends Application {

@SuppressLint(“StaticFieldLeak”)

public static Context context;

@Override

public void onCreate() {

super.onCreate();

//初始化

NetworkApi.init(new NetworkRequiredInfo(this));

context = getApplicationContext();

}

public static Context getContext() {

return context;

}

}

然后我们去AndroidManifest.xml中进行配置,配置如下图所示:

在这里插入图片描述

第一个:网络请求是需要静态权限的。

第二个:配置我们刚才自定义的BaseApplication,在onCreate中对网络框架进行了初始化,如果不配置,使用的就是系统的Application。

第三个:配置HTTP网络访问许可。

第四个:就是修改LoginActivity作为第一个启动的Activity,当点击登录按钮是就会进入到MainActivity。

5. 必应图片显示

下面就是需要MainViewModel的代码,如下:

/**

  • 主页面ViewModel

  • @author llw

  • @description MainViewModel

*/

public class MainViewModel extends ViewModel {

public LiveData biying;

public void getBiying(){

biying = new MainRepository().getBiYing();

}

}

由于是加载网络图片,这里使用Glide框架进行加载,在app的build.gradle中中dependencies{}闭包下增加如下依赖:

//图片加载框架

implementation ‘com.github.bumptech.glide:glide:4.11.0’

annotationProcessor ‘com.github.bumptech.glide:compiler:4.11.0’

在这里插入图片描述

然后Sync Now同步一下即可。

下面就是显示图片了,这里要思考一个问题,那就是图片能不能通过DataBinding的方式进行数据绑定,是可以的,不过需要我们自定义一个ImageView,用于绑定网络地址,很简单的一个View,在com.llw.mvvm下新建一个view包,包下新建一个CustomImageView,代码如下:

/**

  • 自定义View

  • @author llw

  • @description CustomImageVIew

*/

public class CustomImageView extends AppCompatImageView {

public CustomImageView(Context context, @Nullable AttributeSet attrs) {

super(context, attrs);

}

/**

  • 必应壁纸 因为拿到的url不完整,因此需要做一次地址拼接

  • @param imageView 图片视图

  • @param url 网络url

*/

@BindingAdapter(value = {“biyingUrl”}, requireAll = false)

public static void setBiyingUrl(ImageView imageView, String url) {

String assembleUrl = “http://cn.bing.com” + url;

KLog.d(assembleUrl);

Glide.with(BaseApplication.getContext()).load(assembleUrl).into(imageView);

}

/**

  • 普通网络地址图片

  • @param imageView 图片视图

  • @param url 网络url

*/

@BindingAdapter(value = {“networkUrl”}, requireAll = false)

public static void setNetworkUrl(ImageView imageView, String url) {

Glide.with(BaseApplication.getContext()).load(url).into(imageView);

}

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

给大家分享一份移动架构大纲,包含了移动架构师需要掌握的所有的技术体系,大家可以对比一下自己不足或者欠缺的地方有方向的去学习提升;

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

这里插入图片描述](https://img-blog.csdnimg.cn/a2751bc5cf3341a58e1315d1052731e5.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5Yid5a2m6ICFLVN0dWR5,size_20,color_FFFFFF,t_70,g_se,x_16)

然后Sync Now同步一下即可。

下面就是显示图片了,这里要思考一个问题,那就是图片能不能通过DataBinding的方式进行数据绑定,是可以的,不过需要我们自定义一个ImageView,用于绑定网络地址,很简单的一个View,在com.llw.mvvm下新建一个view包,包下新建一个CustomImageView,代码如下:

/**

  • 自定义View

  • @author llw

  • @description CustomImageVIew

*/

public class CustomImageView extends AppCompatImageView {

public CustomImageView(Context context, @Nullable AttributeSet attrs) {

super(context, attrs);

}

/**

  • 必应壁纸 因为拿到的url不完整,因此需要做一次地址拼接

  • @param imageView 图片视图

  • @param url 网络url

*/

@BindingAdapter(value = {“biyingUrl”}, requireAll = false)

public static void setBiyingUrl(ImageView imageView, String url) {

String assembleUrl = “http://cn.bing.com” + url;

KLog.d(assembleUrl);

Glide.with(BaseApplication.getContext()).load(assembleUrl).into(imageView);

}

/**

  • 普通网络地址图片

  • @param imageView 图片视图

  • @param url 网络url

*/

@BindingAdapter(value = {“networkUrl”}, requireAll = false)

public static void setNetworkUrl(ImageView imageView, String url) {

Glide.with(BaseApplication.getContext()).load(url).into(imageView);

}

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-lWlltAgv-1712340322363)]

[外链图片转存中…(img-Bo85s5OY-1712340322364)]

[外链图片转存中…(img-V1oX9Rfu-1712340322364)]

[外链图片转存中…(img-wXCOA87P-1712340322364)]

[外链图片转存中…(img-v1RAIcL6-1712340322364)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

给大家分享一份移动架构大纲,包含了移动架构师需要掌握的所有的技术体系,大家可以对比一下自己不足或者欠缺的地方有方向的去学习提升;

[外链图片转存中…(img-xmlLuHab-1712340322365)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值