效果图
Video_20190131_110052_221 (1).gifMVP设计模式以及注意事项
在传统的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的业务逻辑。
mvp
注意:由于Activity中需要持有view的引用,如果在退出activity的时候,presenter的异步操作保持着Activity的引用,那么activity将得不到释放,从而引起内存泄漏。
处理方法是以弱引用的方式持有view,并且在activity的onDestroy方法上释放view,具体可以看后面UpdatePresenter
- 根据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;
}
}
- 根据面向对象的思想,先设计后实现,添加一个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();
}
- 为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。
最后的类图如下:
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();
}
}
网友评论