依赖
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' implementation 'io.reactivex.rxjava2:rxjava:2.1.12' implementation 'com.squareup.retrofit2:retrofit:2.4.0' compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0' compile 'com.squareup.retrofit2:converter-gson:2.4.0' compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
布局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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" android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent" tools:context="com.example.com.demo513.MainActivity"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="检测更新" android:id="@+id/tv" android:gravity="center_horizontal" android:layout_marginTop="30dp" /> </LinearLayout>
api
package com.example.com.demo513.api; import com.example.com.demo513.bean.ResultBean; import io.reactivex.Flowable; import retrofit2.http.GET; /** * Created by linmeihui on 2018/5/13. */ public interface ServiceApi { @GET("api/checkversion.php") Flowable<ResultBean> getBean() ; }
model
package com.example.com.demo513.model; import com.example.com.demo513.bean.ResultBean; import com.example.com.demo513.presenter.DataPresenter; import com.example.com.demo513.presenter.MyDataPresenter; import com.example.com.demo513.utils.OkHttpUtils; import io.reactivex.Flowable; /** * Created by linmeihui on 2018/5/13. */ public class MyDataModel { public void toUrl(String url, DataPresenter dataPresenter) { Flowable<ResultBean> bean = OkHttpUtils.getInstance(url).getApi().getBean(); dataPresenter.onSuccess(bean); } }
presenter
package com.example.com.demo513.presenter; import io.reactivex.Flowable; /** * Created by linmeihui on 2018/5/13. */ public interface DataPresenter<T> { //成功方法 void onSuccess(Flowable<T> t); }
package com.example.com.demo513.presenter; import com.example.com.demo513.bean.ResultBean; import com.example.com.demo513.model.MyDataModel; import com.example.com.demo513.view.DataView; import io.reactivex.Flowable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.DefaultSubscriber; /** * Created by linmeihui on 2018/5/13. */ public class MyDataPresenter implements DataPresenter<ResultBean> { private DataView dataView; private final MyDataModel myDataModel; public MyDataPresenter (DataView dataView){ this.dataView=dataView; myDataModel = new MyDataModel(); } @Override public void onSuccess(Flowable<ResultBean> t) { t.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new DefaultSubscriber<ResultBean>() { @Override public void onNext(ResultBean resultBean) { dataView.success(resultBean); } @Override public void onError(Throwable t) { dataView.faile(t.getMessage()); } @Override public void onComplete() { } }); } public void toGetUrl(String url){ myDataModel.toUrl(url,this); } }
utils
package com.example.com.demo513.utils; import java.io.File; import java.io.FileInputStream; import java.math.BigInteger; import java.security.MessageDigest; /** * Created by linmeihui on 2018/5/12. */ public class FileMd5Utils { public static String getFileMD5(File file) { if (!file.isFile()) { return null; } MessageDigest digest = null; FileInputStream in = null; byte buffer[] = new byte[1024]; int len; try { digest = MessageDigest.getInstance("MD5"); in = new FileInputStream(file); while ((len = in.read(buffer, 0, 1024)) != -1) { digest.update(buffer, 0, len); } in.close(); } catch (Exception e) { e.printStackTrace(); return null; } BigInteger bigInt = new BigInteger(1, digest.digest()); return bigInt.toString(16); } }
package com.example.com.demo513.utils; import com.example.com.demo513.api.ServiceApi; import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; /** * Created by linmeihui on 2018/5/13. */ public class OkHttpUtils { private static OkHttpUtils instance; private final Retrofit retrofit; private OkHttpUtils(String url){ //拦截器 HttpLoggingInterceptor httpLoggingInterceptor=new HttpLoggingInterceptor(); httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC); OkHttpClient okHttpClient=new OkHttpClient.Builder() .addInterceptor(httpLoggingInterceptor) .build(); retrofit = new Retrofit.Builder() .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .client(okHttpClient) .baseUrl(url) .build(); } public static OkHttpUtils getInstance(String url){ if (instance==null){ synchronized (OkHttpUtils.class){ if (null==instance){ instance=new OkHttpUtils(url); } } } return instance; } public ServiceApi getApi(){ return retrofit.create(ServiceApi.class); } }
package com.example.com.demo513.view; import com.example.com.demo513.bean.ResultBean; /** * Created by linmeihui on 2018/5/13. */ public interface DataView<T>{ void success(ResultBean resultBean); void faile(String message); }
activity
package com.example.com.demo513; import android.app.AlertDialog; import android.app.Dialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Environment; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.TextView; import android.widget.Toast; import com.example.com.demo513.bean.ResultBean; import com.example.com.demo513.presenter.MyDataPresenter; import com.example.com.demo513.utils.FileMd5Utils; import com.example.com.demo513.view.DataView; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import javax.security.auth.login.LoginException; import okhttp3.Call; import okhttp3.Callback; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; public class MainActivity extends AppCompatActivity implements DataView<ResultBean>{ private TextView tv; private MyDataPresenter myDataPresenter; private File file; private String url="http://www.xieast.com/";; private ProgressDialog dialog; private SharedPreferences sp; private long fileLength; private boolean isflag=false; private ResultBean.DataBean data; private boolean isMust=true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); initListener(); } private void init() { //判断外置存储是否挂载 if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { //若挂载 则创建存放位置(SDK中) File externalStorageDirectory = Environment.getExternalStorageDirectory(); String path = externalStorageDirectory.getAbsolutePath() + File.separator + "new.apk"; file = new File(path); //判断文件是否存在 不存在则创建 if (!file.exists()) { try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } } tv = findViewById(R.id.tv); myDataPresenter = new MyDataPresenter(this); myDataPresenter.toGetUrl(url); //创建进度条 dialog = new ProgressDialog(this); dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); //dialog.setCanceledOnTouchOutside(false); sp = getSharedPreferences("version",MODE_PRIVATE); fileLength = sp.getLong("length", 0); if (fileLength!=0){ isflag=true; }else { isflag=false; } } //监听 private void initListener() { tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int last_version = data.getLast_version(); //获取本地name PackageManager packageManager = getPackageManager(); try { PackageInfo packageInfo = packageManager.getPackageInfo(getPackageName(), 0); int versionCode = packageInfo.versionCode; //判断是否小宇最新版本 if (versionCode<last_version){ AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setTitle("更新提示"); builder.setMessage("检测到最新版本"); //判断是否小于强制更新版本 if (versionCode<data.getLast_must_update()){ isMust=true; Toast.makeText(MainActivity.this,"版本太低,需要强制更新",Toast.LENGTH_SHORT).show(); builder.setPositiveButton("立即更新", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { //调用下载APK的方法 setUpdateVersion(data.getUrl(),data.getMd5()); } }).setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { dialog.dismiss(); finish(); } }); }else { isMust=false; builder.setNegativeButton("取消",null); builder.setPositiveButton("立即更新", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { setUpdateVersion(data.getUrl(),data.getMd5()); } }); } AlertDialog alertDialog=builder.create(); alertDialog.setCanceledOnTouchOutside(!isMust); alertDialog.show(); }else { Toast.makeText(MainActivity.this, "已经是最新版本", Toast.LENGTH_SHORT).show(); } } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } } }); } //下载apk方法 private void setUpdateVersion(String url, final String md5) { String range=""; //如果getSharedPreferences 存入果值 //range的头部拼接 file.length() 获取文件的长度 //当存入又数据的话 range 的 bytes的拼接就等于 文件的长度 并且文件的总长度从getSharedPreferences中取出 if (isflag){ range="bytes="+file.length()+"-"+fileLength; }else { range="bytes="+file.length()+"-"; } dialog.show(); //实例化OKhttp对象 OkHttpClient okHttpClient=new OkHttpClient(); //实例化Request对象 下载文件有一个问题就是 断点, 为了避免就需要加入一个Header头部字段 Request request=new Request.Builder() .addHeader("range",range) .url(url) .get() .build(); Call call = okHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { //定义一个文件总长度变量 long contentLength = 0; //判断 如果getSharedPreferencest // 里存入的数据等于0的时候 获取网络请求过来数据总长度 并存入 SharedPreferencest if (fileLength==0){ contentLength = response.body().contentLength(); //存入sharedPreferencest中 sp.edit().putLong("length",contentLength).commit(); }else { //如果存入的数据不等于0 则吧SharedPreferencest中的数据 赋值给 contentLength; contentLength=fileLength; } InputStream inputStream = response.body().byteStream(); toInputString(contentLength, inputStream, md5); } }); } private void toInputString(long l, InputStream inputStream, String md5){ try { Log.d("--", "文件总长度: "+l); //随机储存的类 RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); //定义一个byte数据 来决定一次读取多少字节 byte[] bytes=new byte[2048]; int len=0; //获取文件的长度 long sum = file.length(); //将获取过文件长度的变量存入 随机储存类中, //当sd卡中文件长度是0是,随机储存类储存的位置也是在0 randomAccessFile.seek(sum); while ((len=inputStream.read(bytes,0,bytes.length))!=-1){ sum += len; //进行写入数据 randomAccessFile.write(bytes,0,len); //移动到当前已经下载的文件大小的位置 randomAccessFile.seek(sum); //算出下载进度 int i = (int) (sum * 100 / l); Log.d("--", "toInputString: "+i); //为进度条赋值 dialog.setProgress(i); //当进度条数大于99时进行关闭 if (i > 99) { //读取完了 需要进行关闭流 inputStream.close(); //并关闭进度条的弹框 dialog.dismiss(); //调用检测 本地文件是否还和网络文件一致 checkAPK(file, md5); //跳出循环 break; } } } catch ( Exception e) { e.printStackTrace(); } } private void checkAPK(File file, String md5) { //调用工具类 算出文件的md5值 String fileMD5 = FileMd5Utils.getFileMD5(file); Log.d("--", "网络md5:---" + md5 + "--本地文件的MD5:--" + fileMD5); //equalsIgnoreCase 实现可以忽略大小写, 由于md5是又大小写 //判断 文件的md5值 和网络下载的md5值 是否一致 if (fileMD5.equalsIgnoreCase(md5)) { insertAPK(file); } else { Toast.makeText(this, "文件出现故障 md5值不一致", Toast.LENGTH_SHORT).show(); } } private void insertAPK(File file) { Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); //最好添加上这个Flag intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivityForResult(intent, 1000); System.exit(0); } @Override public void success(ResultBean resultBean) { Log.i("----", "success: "+resultBean.getData().getLast_version()); data = resultBean.getData(); } @Override public void faile(String message) { } }