Android|Android 进阶 Gradle讲解
1.MVC
MVC,Model View Controller,是软件架构中最常见的一种框架,简单来说就是通过controller的控制去操作model层的数据,并且返回给view层展示,具体见下图
文章图片
当用户触发事件的时候,view层会发送指令到controller层,接着controller去通知model层更新数据,model层更新完数据以后直接显示在view层上,这就是MVC的工作原理。
Android MVC
【Android|Android 进阶 Gradle讲解】一个Android工程有Java的class文件,有res文件夹,里面是各种资源,还有类似manifest文件等等。对于原生的Android项目来说。
layout.xml里面的xml文件就对应于MVC的view层,里面都是一些view的布局代码。
各种Java bean,还有一些类似repository类以及工具类就对应于model层。
各种activity就对应controller层。
举例:比如你的界面有一个按钮,按下这个按钮去网络上下载一个文件。这个按钮是view层的,是使用xml来写的,而那些和网络连接相关的代码写在其他类里,比如你可以写一个专门的networkHelper类,这个就是model层,那怎么连接这两层呢?是通过button.setOnClickListener()这个函数,这个函数就写在了activity中,对应于controller层。是不是很清晰。
大家想过这样会有什么问题吗?显然是有的,不然为什么会有MVP和MVVM的诞生呢,是吧。问题就在于xml作为view层,控制能力实在太弱了,你想去动态的改变一个页面的背景,或者动态的隐藏/显示一个按钮,这些都没办法在xml中做,只能把代码写在activity中,造成了activity既是controller层,又是view层的这样一个窘境。如果是一个逻辑很复杂的页面,activity或者fragment是不是动辄上千行呢?这样不仅写起来麻烦,维护起来更是噩梦。
MVC还有一个重要的缺陷,大家看上面那幅图,view层和model层是相互可知的,这意味着两层之间存在耦合,耦合对于一个大型程序来说是非常致命的,因为这表示开发,测试,维护都需要花大量的精力。
正因为MVC有这样那样的缺点,所以才演化出了MVP和MVVM这两种框架。
2.MVP
MVP作为MVC的演化,解决了MVC不少的缺点,对于Android来说,MVP的model层相对于MVC是一样的,而activity和fragment不再是controller层,而是纯粹的view层,所有关于用户事件的转发全部交由presenter层处理。下面还是让我们看图
文章图片
从图中就可以看出,最明显的差别就是view层和model层不再相互可知,完全的解耦,取而代之的presenter层充当了桥梁的作用,用于操作view层发出的事件传递到presenter层中,presenter层去操作model层,并且将数据返回给view层,整个过程中view层和model层完全没有联系。看到这里大家可能会问,虽然view层和model层解耦了,但是view层和presenter层不是耦合在一起了吗?其实不是的,对于view层和presenter层的通信,我们是可以通过接口实现的。
具体的意思就是说我们的activity,fragment可以去实现实现定义好的接口,而在对应的presenter中通过接口调用方法。不仅如此,我们还可以编写测试用的View,模拟用户的各种操作,从而实现对Presenter的测试。这就解决了MVC模式中测试,维护难的问题。
当然,其实最好的方式是使用fragment作为view层,而activity则是用于创建view层(fragment)和presenter层(presenter)的一个控制器。
和MVC最大的不同,MVP把activity作为了view层,通过代码也可以看到,整个activity没有任何和model层相关的逻辑代码,取而代之的是把代码放到了presenter层中,presenter获取了model层的数据之后,通过接口的形式将view层需要的数据返回给它就OK了。
这样的好处是。首先,activity的代码逻辑减少了,其次,view层和model层完全解耦,具体来说,如果你需要测试一个http请求是否顺利,你不需要写一个activity,只需要写一个java类,实现对应的接口,presenter获取了数据自然会调用相应的方法,相应的,你也可以自己在presenter中mock数据,分发给view层,用来测试布局是否正确。
3.MVVM
MVVM最早是由微软提出的
文章图片
从图中看出,它和MVP的区别貌似不大,只不过是presenter层换成了viewmodel层,还有一点就是view层和viewmodel层是相互绑定的关系,这意味着当你更新viewmodel层的数据的时候,view层会相应的变动ui。
我们很难去说MVP和MVVM这两个MVC的变种孰优孰劣,还是要具体情况具体分析。
MVC代码举例:
需求描述:在一个页面完成上传附件的功能。(上传手机本地多张图片,上传之前要检查手机存储卡问题,是否有赋予该APP相关权限问题...)。
View层 Xml文件(布局)
Text格式:
Design格式:
文章图片
很简单,一个ImageView点击上传图片,一个ListView用来显示选择的图片。
Controller层 activity类(窗口)
部分核心代码:
case R.id.activity_attachmentadd_chooselayout://上传图片
if(!BooleanUtils.isEmpty(resultid)&&!BooleanUtils.isEmpty(resultname)){
requestAndroidPermission();
}else{
toast.showToast(StringConstant.attachmentststatus10);
}
break;
private void requestAndroidPermission(){
if(FileHelper.isSdCardExist()){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){//6.0
boolean has= AndroidPermissionUtils.hasAndroidPermission(AttachmentAddActivity.this, DataConstant.permission);
if(!has){//6.0及以上 没有权限
AndroidPermissionUtils.requestAndroidPermission(AttachmentAddActivity.this,0,DataConstant.permission);
}else{//6.0及以上 有权限 请求相册图片
pickImage();
}
}else{//6.0以下 请求相册图片
pickImage();
}
}else{
toast.showToast(StringConstant.Filestatus2);
}
}
Model层 工具类
操作文件的工具类
public class FileHelper {private static FileHelper mInstance = new FileHelper();
/**
* 获取对象的静态方法
* */public static FileHelper getInstance() {
return mInstance;
}/**
* 判断当前设备是否有外部存储(SD卡)
* */public static boolean isSdCardExist() {
return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
}/**
* 获取sd卡根路径
* */public static String getTheRootDirectory(){
String result="";
if(isSdCardExist()){
result=Environment.getExternalStorageDirectory()+ "/";
}
return result;
}/**
* 判断某个文件是否已存在于文件夹内
* */public static boolean isExistFile(File file,String name){
boolean result=false;
if((null==file)||(name==null)){
return false;
}else{
List fileList = getFile(file);
if(fileList.size()>0){
for (int i = 0;
i < fileList.size();
i++) {
File filename = fileList.get(i);
if(name.equals(filename.getName())){
result=true;
break;
}
}
}else{
result=false;
}
}
returnresult;
}/**
* 获取某个文件夹下所有文件集合
* */public static List getFile(File file) {
List mFileList = new ArrayList();
File[] fileArray = file.listFiles();
for (File f : fileArray) {
if (f.isFile()) {
mFileList.add(f);
} else {
getFile(f);
}
}
return mFileList;
}public static String createAvatarPath(String userName) {
String dir = StringConstant.PICTURE_DIR;
File destDir = new File(dir);
if (!destDir.exists()) {
destDir.mkdirs();
}
File file;
if (userName != null) {
file = new File(dir, userName + ".png");
} else {
file = new File(dir, new DateFormat().format("yyyy_MMdd_hhmmss",
Calendar.getInstance(Locale.CHINA)) + ".png");
}
return file.getAbsolutePath();
}public static String createAvatarPathPicture(String userName) {
String dir = FileHelper.getTheRootDirectory() + StringConstant.PICTURTemporary_Path;
File destDir = new File(dir);
if (!destDir.exists()) {
destDir.mkdirs();
}
File file;
if (userName != null) {
file = new File(dir, userName + ".png");
} else {
file = new File(dir, new DateFormat().format("yyyy_MMdd_hhmmss",
Calendar.getInstance(Locale.CHINA)) + ".png");
}
return file.getAbsolutePath();
}public static String getUserAvatarPath(String userName) {
return StringConstant.PICTURE_DIR + userName + ".png";
}public interface CopyFileCallback {
public void copyCallback(Uri uri);
}/**
* 复制后裁剪文件 聊天发送图片
*
* @param file 要复制的文件
*/
public void copyAndCrop(final File file, final Activity context, final CopyFileCallback callback) {
if (isSdCardExist()) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
FileInputStream fis = new FileInputStream(file);
//String path = createAvatarPath(JMessageClient.getMyInfo().getUserName());
String path = "";
final File tempFile = new File(path);
FileOutputStream fos = new FileOutputStream(tempFile);
byte[] bt = new byte[1024];
int c;
while ((c = fis.read(bt)) > 0) {
fos.write(bt, 0, c);
}
//关闭输入、输出流
fis.close();
fos.close();
context.runOnUiThread(new Runnable() {
@Override
public void run() {
callback.copyCallback(Uri.fromFile(tempFile));
}
});
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
context.runOnUiThread(new Runnable() {
@Override
public void run() {}
});
}
}
});
thread.start();
} else {
Toast.makeText(context, StringConstant.Filestatus2, Toast.LENGTH_SHORT).show();
}
}/**
* 复制后裁剪文件 聊天发送图片
*
* @param file 要复制的文件
*/
public void copyAndCropPicture(final File file, final Activity context, final CopyFileCallback callback) {
if (isSdCardExist()) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
FileInputStream fis = new FileInputStream(file);
String name = String.valueOf(System.currentTimeMillis());
String path = createAvatarPathPicture(name);
final File tempFile = new File(path);
FileOutputStream fos = new FileOutputStream(tempFile);
byte[] bt = new byte[1024];
int c;
while ((c = fis.read(bt)) > 0) {
fos.write(bt, 0, c);
}
//关闭输入、输出流
fis.close();
fos.close();
context.runOnUiThread(new Runnable() {
@Override
public void run() {
callback.copyCallback(Uri.fromFile(tempFile));
}
});
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
context.runOnUiThread(new Runnable() {
@Override
public void run() {}
});
}
}
});
thread.start();
} else {
Toast.makeText(context, StringConstant.Filestatus2, Toast.LENGTH_SHORT).show();
}
}}
操作权限的工具类
public class AndroidPermissionUtils {/**
* Android 6.0及以上 检测是否具有某些权限
* */public static boolean hasAndroidPermission(Context context, String [] permission){
boolean has=true;
for(String per:permission){
if(ContextCompat.checkSelfPermission(context,per) != PackageManager.PERMISSION_GRANTED){
has=false;
break;
}
}
return has;
}/**
* Android 6.0及以上 申请某些权限
* */public static void requestAndroidPermission(Activity activity, int code, String []permission){
ActivityCompat.requestPermissions(activity,permission,code);
}}
图解:
文章图片
文章图片
文章图片
MVC在Android上的应用,一个具体的问题就是activity的责任过重,既是controller又是view。比如有一个progressDialog,在加载数据的时候显示,加载完了以后取消,逻辑其实是view层的逻辑,但是这个在xml里面操作比较困难,包括TextView.setTextView(),这个也一样。我们只能把这些逻辑写到activity中,这就造成了activity的臃肿。
MVP代码举例
需求描述:实现简单的登录功能
文章图片
View层 Xml文件(布局)以及Activity文件(或Fragment文件)
Activity类
public class LoginActivity extends AppCompatActivity implements ILoginActivity, View.OnClickListener{private EditText useredittext;
private EditText pwdedittext;
private TextView logintextview;
private TextView resettextview;
private ProgressDialog procDialog;
private ILoginInPresenter iLoginInPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
}/**
* 初始化各种View
* */public void initView(){
useredittext= (EditText) findViewById(R.id.activity_login_usernameedittext);
pwdedittext= (EditText) findViewById(R.id.activity_login_pwdedittext);
logintextview= (TextView) findViewById(R.id.activity_login_logintextview);
resettextview= (TextView) findViewById(R.id.activity_login_resettextview);
logintextview.setOnClickListener(this);
resettextview.setOnClickListener(this);
iLoginInPresenter = new LoginInPresenter(this);
}@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.activity_login_logintextview://登录
logintextview.setEnabled(false);
iLoginInPresenter.submitLogin(useredittext.getText().toString(), pwdedittext.getText().toString());
break;
case R.id.activity_login_resettextview://重置
iLoginInPresenter.clear();
break;
}
}@Override
public void clearEdittext() {
useredittext.setText("");
pwdedittext.setText("");
}@Override
public void loginStart() {
procDialog = new ProgressDialog(this);
procDialog.setMessage("登录中...");
procDialog.setCancelable(false);
procDialog.setIndeterminate(false);
procDialog.show();
}@Override
public void loginFinish(boolean result, int code) {
if(null!=procDialog){
if (procDialog.isShowing()) {
procDialog.dismiss();
}
}
if (result) {
Toast.makeText(this, "登录成功!", Toast.LENGTH_SHORT).show();
//模拟MVP的model层 所以创建一个Java Bean
Student student=new Student();
student.setName("张三");
student.setPwd("123");
String name=student.getName();
String pwd=student.getPwd();
Log.d("TAG","name----:"+name);
Log.d("TAG","pwd----:"+pwd);
} else {
Toast.makeText(this, "登录失败,错误码:" + code, Toast.LENGTH_SHORT).show();
}
}@Override
protected void onDestroy() {
super.onDestroy();
if(null!=procDialog){
if (procDialog.isShowing()) {
procDialog.dismiss();
}
}
}
}
xml布局文件
Text格式:
Design格式:
文章图片
Mode层 Java Bean
public class Student {private String name;
private String pwd;
public String getName() {
return name;
}public void setName(String name) {
this.name = name;
}public String getPwd() {
return pwd;
}public void setPwd(String pwd) {
this.pwd = pwd;
}
}
Presenter层 各种接口和其实现类
ILoginActivity接口
public interface ILoginActivity {void clearEdittext();
void loginStart();
void loginFinish(boolean result,int code);
}
ILoginInPresenter接口
public interface ILoginInPresenter {void clear();
void submitLogin(String name,String pwd);
}
ILoginInPresenter接口实现类
public class LoginInPresenter implements ILoginInPresenter{private boolean result=false;
private int code=-1;
private ILoginActivity iLoginActivity;
public LoginInPresenter(ILoginActivity iLoginActivity){
this.iLoginActivity=iLoginActivity;
}@Override
public void clear() {
iLoginActivity.clearEdittext();
}@Override
public void submitLogin(String name, String pwd) {
if(null!=name&&null!=pwd&&name.equals(pwd)){
result=true;
code=0;
}else{
result=false;
code=-1;
}/**
* 提交后直接给开始的回调
* */iLoginActivity.loginStart();
/**
* 模拟耗时操作 5秒后给完成的回调
* */Handler handler=new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
iLoginActivity.loginFinish(result,code);
}
}, 5000);
}}
结果:
name----:张三pwd----:123
MVVM代码举例
Android 依赖注入库之Data Binding Library(MVVM设计模式)详解
推荐阅读
- android第三方框架(五)ButterKnife
- Android中的AES加密-下
- 带有Hilt的Android上的依赖注入
- android|android studio中ndk的使用
- Android事件传递源码分析
- 推荐系统论文进阶|CTR预估 论文精读(十一)--Deep Interest Evolution Network(DIEN)
- RxJava|RxJava 在Android项目中的使用(一)
- Android7.0|Android7.0 第三方应用无法访问私有库
- 深入理解|深入理解 Android 9.0 Crash 机制(二)
- android防止连续点击的简单实现(kotlin)