基于MVP+AsyncTask实现的安卓自动升级实现

效果图

7248113-dff91efd5ad106e5.gif
Video_20190131_110052_221 (1).gif

MVP设计模式以及注意事项

在传统的MVC模式中,View层接受用户的输入,然后通过Controller修改对应的Model实例,但是在Android中,Activity不仅承担了View的角色,还承担了一部分的Controller角色,为了将activity的view层和controller层分离出来,出现了MVP模式,在MVP模式中,activity被分离成View和Presenter,其中

  • M(model)主要负责实体类的数据。
  • V(View)指在android上的可视化组件,可以是activity,fragment,主要用于处理android上的视图处理和更新。
  • P(presenter)是View和Model交互的桥梁。主要处理activity的业务逻辑。


    7248113-fdb2356c522d1010.png
    mvp

注意:由于Activity中需要持有view的引用,如果在退出activity的时候,presenter的异步操作保持着Activity的引用,那么activity将得不到释放,从而引起内存泄漏。
处理方法是以弱引用的方式持有view,并且在activity的onDestroy方法上释放view,具体可以看后面UpdatePresenter

  1. 根据app升级所需要的参数,构建相应的实体类
public class UpdateMessage {
    private String id ;
    private String name ;
    private String version;
    private String message;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
  1. 根据面向对象的思想,先设计后实现,添加一个presnter的接口IPresenter
public interface IUpdatePresenter {
    // 检查升级所需要的权限
    void checkPermission();
    // 检查更新
    public void checkUpdate();
    // 更新操作
    public void update();
    // 安装操作
    public void install();
    // 保存用户的配置
    void saveConfig(String key ,boolean isChecked);
    // 加载配置
    void loadConfig();
    // 解绑presenter
    void detach();
    
}
  1. 为View层设计回调接口ISetActivity
public interface ISetActivity {
   public Context getContext();
   public void Success(boolean result);
   public void setProgressValue(int value);
   public void hasUpdate(boolean result, UpdateMessage message);
   void LoadedData(boolean index1,boolean index2);

}

4.实现IPresenter,在构造函数中传入ISetActivity,让presenter持有view,并在activity上实现ISetActivity,最后在Activity的onCreate方法上初始化presenter,在onDestroy中销毁presenter。

最后的类图如下:


7248113-3a686631c6eb4148.png
simpleUML.png

Model中的UpdateMessage是在Presenter进行了初始化,但uml没显示出来。

Presenter业务实现

AsyncTask下载实现
public class DownUtil extends AsyncTask<String,Integer,Integer> {

    private static final int DOWNLOAD_SUCCESS = 0;
    private static final int DOWNLOAD_FAILED = 1;
    private static final int DOWNLOAD_PAUSED = 2;
    private static final int DOWNLOAD_CANCELED = 3;
    private final String TAG = "DownUtil";

    private DownloadListener listener;

    private boolean isCanceled = false;
    private boolean isPaused = false;
    private int lastProgress;

    public DownUtil(DownloadListener listener) {
        this.listener = listener;
    }

    @Override
    //apk下载实现,支持断点续传
    protected Integer doInBackground(String... strings) {
        Log.d(TAG, "doInBackground: " + "begin");
        InputStream is = null;
        RandomAccessFile savaFile = null;
        File file = null;
        long downloadLength = 0;
        String downloadUrl = "http://193.112.***.***/static/dingding/update.apk";
        String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
        String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
        file = new File(directory + fileName);
        if (file.exists()){
            downloadLength = file.length();
        }
        try {
            long contentLength = getContentLength(downloadUrl);
            if (contentLength ==0){
                return DOWNLOAD_FAILED;
            }else if (contentLength == downloadLength){
                return DOWNLOAD_SUCCESS;
            }
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder()
                    .addHeader("RANGE","byte=" + downloadLength + "-")
                    .url(downloadUrl)
                    .build();
            Response response = client.newCall(request).execute();
            if (response != null){
                assert response.body() != null;
                is = response.body().byteStream();
                savaFile = new RandomAccessFile(file,"rw");
                savaFile.seek(downloadLength);
                byte[] b = new byte[1024];
                int total =0;
                int len;
                while ((len=is.read(b))!=-1){
                    if (isCanceled){
                        return DOWNLOAD_CANCELED;
                    }else if(isPaused){
                        return DOWNLOAD_PAUSED;
                    }else {
                        total += len;
                        savaFile.write(b,0,len);
                        int progress = (int) ((total + downloadLength)*100/contentLength);
                        publishProgress(progress);
                    }
                }
                response.body().close();
                return DOWNLOAD_SUCCESS;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (is!=null){
                    is.close();
                }
                if (savaFile != null) {
                    savaFile.close();
                }
                if (isCanceled && file!=null){
                    file.delete();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        return DOWNLOAD_FAILED;
    }

    private long getContentLength(String downloadUrl) throws IOException {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url(downloadUrl).build();
        Response response = client.newCall(request).execute();
        if (response!=null && response.isSuccessful()){
            assert response.body() != null;
            long contentLength = response.body().contentLength();
            response.close();
            Log.d(TAG, "doInBackground: " + contentLength + "1");
            return contentLength;
        }
        return 0;
    }

    @Override
    protected void onPostExecute(Integer integer) {
        switch (integer){
            case DOWNLOAD_SUCCESS:
                listener.onSuccess();
                break;
            case DOWNLOAD_FAILED:
                listener.onFailed();
                break;
            case DOWNLOAD_PAUSED:
                listener.onPause();
                break;
            case DOWNLOAD_CANCELED:
                listener.onCanceled();
                break;
                default:break;
        }
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        int progress = values[0];
        if (progress > lastProgress){
            listener.onProgress(progress);
            lastProgress = progress;
        }
    }

    public void pauseDownload(){
        isPaused = true;
    }

    public void cancelDownload(){
        isCanceled = true;
    }
}

UpdatePresenter 实现
public class UpdatePresenter implements IUpdatePresenter, DownloadListener {

    private static final String TAG = "UpdatePresenter";
    private WeakReference<ISetActivity> weakActivity;
    public UpdatePresenter(ISetActivity setActivity) {
        this.weakActivity = new WeakReference<>(setActivity);
    }

    @Override
    public void checkPermission() {
        if (ContextCompat.checkSelfPermission(weakActivity.get().getContext(),Manifest.permission.WRITE_EXTERNAL_STORAGE)!=PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions((Activity) weakActivity.get().getContext(),new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
        }
    }

    @Override
    public void checkUpdate() {
        final OkHttpClient client = new OkHttpClient();
        final Request request = new Request.Builder()
                .url("http://193.112.***.***/static/dingding/update.xml")
                .build();
        PackageManager manager = weakActivity.get().getContext().getPackageManager();
        PackageInfo info = null;
        try {
            info = manager.getPackageInfo(weakActivity.get().getContext().getPackageName(), 0);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        assert info != null;
        final Float version = Float.valueOf(info.versionName);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    final List<UpdateMessage> updateMessageList = parseXMLWithPull(client.newCall(request).execute().body().string());
                    Log.d(TAG, "run: " + updateMessageList.get(0).getVersion() + "  new" + version);
                    if (Float.valueOf(updateMessageList.get(0).getVersion()) > version ){
                        weakActivity.get().hasUpdate(true,updateMessageList.get(0));
                    }else {
                        weakActivity.get().hasUpdate(false,null);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    @Override
    public void update() {
        DownUtil downUtil = new DownUtil(this);
        downUtil.execute();
    }

    //apk自动安装实现
    @Override
    public void install() {
        String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
        File apkFile = new File(directory + "/update.apk");
        Log.d(TAG, "install: " + apkFile.getPath());
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            Log.w(TAG, "版本大于 N ,开始使用 fileProvider 进行安装");
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Uri contentUri = FileProvider.getUriForFile(
                    weakActivity.get().getContext()
                    , "com.example.yami.graduationdesign.fileprovider"
                    , apkFile);
            intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
        } else {
            Log.w(TAG, "正常进行安装");
            intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
        }
        weakActivity.get().getContext().startActivity(intent);
    }

    @Override
    public void saveConfig(String key,boolean isChecked) {
        SharedPreferences sp = weakActivity.get().getContext().getSharedPreferences("dingding",Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sp.edit();
        editor.putBoolean(key,isChecked);
        editor.apply();

    }

    @Override
    public void loadConfig() {
        SharedPreferences sharedPreferences = weakActivity.get().getContext().getSharedPreferences("dingding",Context.MODE_PRIVATE);
        boolean index = sharedPreferences.getBoolean("first",false);
        boolean index1 = sharedPreferences.getBoolean("second",false);
        weakActivity.get().LoadedData(index,index1);
    }

    //解析XML数据,本来应该写在UpdateMessages里面的,我为了方便写在这里。
    private List<UpdateMessage> parseXMLWithPull(String responseData) {
        List<UpdateMessage> updateMessages = new ArrayList<>();
        try{
            XmlPullParserFactory factory =  XmlPullParserFactory.newInstance();
            XmlPullParser xmlPullParser = factory.newPullParser();
            xmlPullParser.setInput(new StringReader(responseData));
            int eventType = xmlPullParser.getEventType();
            String id = "";
            String name = "";
            String version = "";
            String message = "";
            while (eventType != XmlPullParser.END_DOCUMENT){
                String nodeName = xmlPullParser.getName();
                switch (eventType){
                    case XmlPullParser.START_TAG :
                        if ("id".equals(nodeName)){
                            id = xmlPullParser.nextText();
                        }else if ("name".equals(nodeName)){
                            name = xmlPullParser.nextText();
                        }else if ("version".equals(nodeName)){
                            version = xmlPullParser.nextText();
                        }else if ("message".equals(nodeName)){
                            message = xmlPullParser.nextText();
                        }
                        break;
                    case XmlPullParser.END_TAG:
                        if ("app".equals(nodeName)){
                            UpdateMessage updateMessage = new UpdateMessage();
                            updateMessage.setId(id);
                            updateMessage.setName(name);
                            updateMessage.setMessage(message);
                            updateMessage.setVersion(version);
                            updateMessages.add(updateMessage);
                        }
                        break;
                    default:
                        break;
                }
                eventType = xmlPullParser.next();
            }
            return updateMessages;
        } catch (XmlPullParserException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            return updateMessages;
        }
    }

    @Override
    public void onProgress(int progress) {
        Log.d(TAG, "onProgress: " + " " + progress);
        weakActivity.get().setProgressValue(progress);
    }

    @Override
    public void onSuccess() {
        Log.d(TAG, "onProgress: " + " " + "SUCCESS");
        weakActivity.get().Success(true);
    }

    @Override
    public void onFailed() {
        Log.d(TAG, "onProgress: " + " " + "fail");
        weakActivity.get().Success(false);
    }

    @Override
    public void onPause() {

    }

    @Override
    public void onCanceled() {

    }

    @Override
    public void detach(){
        if(weakActivity !=null){
            weakActivity.clear();
            weakActivity = null;
        }
    }
}

View的实现

public class MainActivity extends AppCompatActivity implements ISetActivity{

    private CheckBox autoCheck;
    private CheckBox fourceUpdate;
    private TextView checkUpdate;
    private IUpdatePresenter presenter;
    ProgressDialog dialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initUI();
        presenter = new UpdatePresenter(this);
        presenter.loadConfig();
        presenter.checkPermission();
        registerListen();
    }

    private void registerListen() {
        autoCheck.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                presenter.saveConfig("first",isChecked);
            }
        });
        if (autoCheck.isChecked()){
            presenter.checkUpdate();
        }
        fourceUpdate.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                presenter.saveConfig("second",isChecked);
            }
        });
        checkUpdate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                presenter.checkUpdate();
            }
        });
    }

    private void initUI() {
        checkUpdate = findViewById(R.id.textView3);
        autoCheck = findViewById(R.id.checkbox1);
        fourceUpdate = findViewById(R.id.checkbox2);
        dialog = new ProgressDialog(this);
        dialog.setMax(100);
        dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        dialog.setCancelable(false);
        dialog.setTitle("发现新版本");
        dialog.setMessage("正在下载");
    }


    @Override
    public Context getContext() {
        return this;
    }

    @Override
    public void Success(final boolean result) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (result){
                    Toast.makeText(MainActivity.this,"软件下载完成",Toast.LENGTH_SHORT).show();
                    presenter.install();
                }else {
                    Toast.makeText(MainActivity.this,"软件下载失败,请检查网络连接",Toast.LENGTH_SHORT).show();
                }
                dialog.dismiss();
            }
        });
    }

    @Override
    public void setProgressValue(int value) {
        dialog.setProgress(value);
    }

    @Override
    public void hasUpdate(boolean result, UpdateMessage message) {
        if (result){
            if (fourceUpdate.isChecked()){
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        dialog.show();
                    }
                });
                presenter.update();
            }else {
                final AlertDialog.Builder builder =new AlertDialog.Builder(this);
                builder.setTitle("有新版本" + message.getVersion() + "可用,是否下载?");
                builder.setMessage("更新到" + message.getVersion() + "版本" + "\n修复若干个BUG");
                builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        MainActivity.this.dialog.show();
                        presenter.update();
                        dialog.dismiss();
                    }
                });
                builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                });
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        builder.show();
                    }
                });
            }
        }else {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(MainActivity.this,"当前已经是最新版本",Toast.LENGTH_SHORT).show();
                }
            });
        }
    }

    @Override
    public void LoadedData(boolean index1, boolean index2) {
        autoCheck.setChecked(index1);
        fourceUpdate.setChecked(index2);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        presenter.detach();
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_33720956/article/details/87574833
今日推荐