Android 进阶 Gradle讲解

1.MVC

MVC,Model View Controller,是软件架构中最常见的一种框架,简单来说就是通过controller的控制去操作model层的数据,并且返回给view层展示,具体见下图

Android 进阶 Gradle讲解_第1张图片

当用户触发事件的时候,view层会发送指令到controller层,接着controller去通知model层更新数据,model层更新完数据以后直接显示在view层上,这就是MVC的工作原理。

 

 

Android MVC

一个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层处理。下面还是让我们看图

Android 进阶 Gradle讲解_第2张图片

从图中就可以看出,最明显的差别就是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最早是由微软提出的

Android 进阶 Gradle讲解_第3张图片

从图中看出,它和MVP的区别貌似不大,只不过是presenter层换成了viewmodel层,还有一点就是view层和viewmodel层是相互绑定的关系,这意味着当你更新viewmodel层的数据的时候,view层会相应的变动ui。

我们很难去说MVP和MVVM这两个MVC的变种孰优孰劣,还是要具体情况具体分析。

 

 

 

 

 

MVC代码举例:

 

需求描述:在一个页面完成上传附件的功能。(上传手机本地多张图片,上传之前要检查手机存储卡问题,是否有赋予该APP相关权限问题...)。

 

View层 Xml文件(布局)

Text格式:




    

    

        

        

        
    


    

        

            

            

            

            

                

                

            

            

            

            

            

        

    

 

Design格式:

Android 进阶 Gradle讲解_第4张图片

 

很简单,一个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;
            }
        }
        return  result;
    }

    /**
     * 获取某个文件夹下所有文件集合
     * */

    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);
    }

}

 

 

图解:

Android 进阶 Gradle讲解_第5张图片

 

 

Android 进阶 Gradle讲解_第6张图片

 

 

 

Android 进阶 Gradle讲解_第7张图片

 

 

MVC在Android上的应用,一个具体的问题就是activity的责任过重,既是controller又是view。比如有一个progressDialog,在加载数据的时候显示,加载完了以后取消,逻辑其实是view层的逻辑,但是这个在xml里面操作比较困难,包括TextView.setTextView(),这个也一样。我们只能把这些逻辑写到activity中,这就造成了activity的臃肿。

 

 

 

MVP代码举例

 

 

需求描述:实现简单的登录功能

Android 进阶 Gradle讲解_第8张图片

 

 

 

 

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格式:

Android 进阶 Gradle讲解_第9张图片

 

 

 

 

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,进阶)