App应用 一般更新
-
意义
- 能及时告知用户有新的版本
-
途径
- 各大应用市场:google play 等
实践
- 下载apk
- 安装apk
所需的类图
所需的类
/**
* @author zl
* 版本 1.0
* 创建时间 2021/6/6
* 描述:
* 事件的监听回调
*/
public interface UpdateDownLoadListener {
//下载请求开始回调
void onStarted();
//下载进度回调
void onProgressChanged(int progress,String downLoadUrl);
//下载完成
void onFinished(long completeSize,String downLoadUrl);
//下载失败
void onFailure();
}
/**
* @author zl
* 版本 1.0
* 创建时间 2021/6/6
* 描述:
* 负责处理文件 和 线程切换
*/
public class UpdateDownLoadRequest implements Runnable {
private static final String TAG = "UpdateDownLoadRequest";
private String downLoadUrl;
private String localFilePath;
private UpdateDownLoadListener mUpdateDownLoadListener;
private boolean isDownloading = false;//正在下载中
private long currentLength;//当前文件的长度
private DownLoadResponseHandler mDownLoadResponseHandler;
//构造函数
public UpdateDownLoadRequest(String downLoadUrl, String localFilePath, UpdateDownLoadListener updateDownLoadListener) {
isDownloading = true;
this.downLoadUrl = downLoadUrl;
this.localFilePath = localFilePath;
this.mUpdateDownLoadListener = updateDownLoadListener;
this.mDownLoadResponseHandler = new DownLoadResponseHandler();
}
//建立连接
private void makeRequest() throws IOException, InterruptedException {
if (!Thread.currentThread().isInterrupted()) {
URL url = new URL(downLoadUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
connection.setRequestProperty("Connection", "Keep-Alive");
connection.connect();//阻塞我们当前线程
currentLength = connection.getContentLength();
if (!Thread.currentThread().isInterrupted()) {
//真正的完成文件的下载
mDownLoadResponseHandler.sendResponseMessage(connection.getInputStream());
}
}
}
@Override
public void run() {
try {
makeRequest();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public enum FailureCode {
UnknownHost, Socket, SocketTimeOut, ConnectionTimeOut,
IO, JSON, Interrupted, HttpResponse
}
//真正下载文件,发送消息 和 接口回调
public class DownLoadResponseHandler {
protected static final int FAILURE_MESSAGE = 1;
protected static final int FINISH_MESSAGE = 3;
protected static final int PROGRESS_CHANGED = 5;
private int mCompleteSize;
private int progress;
private int oldProgress; //之前的进度
private Handler mHandler;//真正完成线程间通信
public DownLoadResponseHandler() {
mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
handleSlefMessage(msg);
}
};
}
//消息处理
private void handleSlefMessage(Message msg) {
Object[] response;
switch (msg.what) {
case FINISH_MESSAGE:
onFinish();
break;
case PROGRESS_CHANGED:
response = (Object[]) msg.obj;
handleProgressChangedMessage(((Integer) response[0]).intValue());
break;
case FAILURE_MESSAGE:
response = (Object[]) msg.obj;
handleFailureMessage((FailureCode) response[0]);
break;
default:
}
}
private void sendMessage(Message msg) {
if (mHandler != null) {
mHandler.sendMessage(msg);
} else {
handleSlefMessage(msg);
}
}
//消息发送
protected void sendFinishMessage() {
sendMessage(obtainMessage(FINISH_MESSAGE, null));
}
protected void sendProgressChangedMessage(int progress) {
sendMessage(obtainMessage(PROGRESS_CHANGED, new Object[]{progress}));
}
protected void sendFailureMessage(FailureCode failureCode) {
sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{failureCode}));
}
//获取一个消息
private Message obtainMessage(int responseMessage, Object response) {
Message msg;
if (mHandler != null) {
msg = mHandler.obtainMessage(responseMessage, response);
} else {
msg = Message.obtain();
msg.what = responseMessage;
msg.obj = response;
}
return msg;
}
//消息接口回调处理
private void onFinish() {
if (mUpdateDownLoadListener != null) {
mUpdateDownLoadListener.onFinished(mCompleteSize, "");
}
}
private void onFailure(FailureCode failureCode) {
if (mUpdateDownLoadListener != null) {
mUpdateDownLoadListener.onFailure();
}
}
private void handleFailureMessage(FailureCode failureCode) {
onFailure(failureCode);
}
private void handleProgressChangedMessage(int progress) {
if (mUpdateDownLoadListener != null) {
mUpdateDownLoadListener.onProgressChanged(progress, "");
}
}
//真正下载方法,发送各种类型的事件
void sendResponseMessage(InputStream is) {
RandomAccessFile randomAccessFile = null;
mCompleteSize = 0;
try {
byte[] buffer = new byte[1024];
int length;
//用指针指向这个文件,并标记为read + write(double)
randomAccessFile = new RandomAccessFile(localFilePath, "rwd");
while ((length = is.read(buffer)) != -1) {
if (isDownloading) {
randomAccessFile.write(buffer, 0, length);
mCompleteSize += length;
if (mCompleteSize <= currentLength) {
progress = (int) (mCompleteSize * 100L / currentLength);
Log.d(TAG, "sendResponseMessage: 当前的下载进度是 " + progress);
if (oldProgress != progress) {
sendProgressChangedMessage(progress);
}
oldProgress = progress;
}
}
}
sendFinishMessage();
} catch (IOException e) {
sendFailureMessage(FailureCode.IO);
} finally {
try {
if (is != null) {
is.close();
}
if (randomAccessFile != null) {
randomAccessFile.close();
}
} catch (IOException e) {
sendFailureMessage(FailureCode.IO);
}
}
}
}
}
/**
* @author zl
* 版本 1.0
* 创建时间 2021/6/6
* 描述:
* 调用UpdateDownloadRequest
*/
public class UpdateManager {
private static UpdateManager manager;
private ThreadPoolExecutor mThreadPoolExecutor;
private UpdateDownLoadRequest mUpdateDownLoadRequest;
private UpdateManager(){
mThreadPoolExecutor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
}
static {
Log.d("tag","对象创建 声明周期");
manager = new UpdateManager();
}
public static UpdateManager getInstance(){
return manager;
}
public void startDownLoad(String downloadUrl,String localPath,UpdateDownLoadListener loadListener){
//如果任务已存在,不做任何操作
if(mUpdateDownLoadRequest != null){
return;
}
//文件名不能为空
if(TextUtils.isEmpty(localPath)){
return;
}
//检查路径是否存在 或 合法
checkLocalFilePath(localPath);
//开始真正的去下载任务
mUpdateDownLoadRequest = new UpdateDownLoadRequest(downloadUrl,localPath,loadListener);
Future<?> future = mThreadPoolExecutor.submit(mUpdateDownLoadRequest);
}
private void checkLocalFilePath(String localPath) {
//判断文件夹是否存在
File dir = new File(localPath.substring(0,localPath.lastIndexOf("/") + 1));
if(!dir.exists()){
dir.mkdirs();
}
//判断文件是否存在
File file = new File(localPath);
if(!file.exists()){
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* @author zl
* 版本 1.0
* 创建时间 2021/6/6
* 描述:
* app更新下载后台任务
*/
public class UpdateService extends Service {
//默认测试的apk
private String apkUrl = "http://xyapk.xy599.com/xiaoyu_351_77_gw.apk";
private String filePath;
private NotificationManager mNotificationManagerCompat;
private Notification mNotificationCompat;
@SuppressLint("ServiceCast")
@Override
public void onCreate() {
super.onCreate();
Log.d("tag", "onCreate ");
mNotificationManagerCompat = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
filePath = Environment.getExternalStorageDirectory() + "/xyzq.apk";
Log.d("tag", "filePath = " + filePath);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("tag", "onStartCommand");
if (intent == null) {
notityUser("下载失败", "参数为空", 0);
stopSelf();
}
notityUser("下载开始", "下载开始", 0);
startDownLoad();
return super.onStartCommand(intent, flags, startId);
}
private void startDownLoad() {
UpdateManager.getInstance().startDownLoad(apkUrl, filePath, new UpdateDownLoadListener() {
@Override
public void onStarted() {
Toast.makeText(UpdateService.this, "开始下载", Toast.LENGTH_SHORT).show();
}
@Override
public void onProgressChanged(int progress, String downLoadUrl) {
notityUser("正在下载", "正在下载", progress);
}
@Override
public void onFinished(long completeSize, String downLoadUrl) {
notityUser("下载完成", "下载完成", 100);
stopSelf();
}
@Override
public void onFailure() {
notityUser("下载失败", "下载失败", 0);
stopSelf();
}
});
}
//更新我们的Notification ,告知用户进度
private void notityUser(String result, String reason, int progress) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setContentTitle(getString(R.string.app_name));
if (progress > 0 && progress < 100) {
builder.setProgress(100, progress, false);
} else {
//设置0 可以隐藏progress
builder.setProgress(100, 100, false);
}
builder.setAutoCancel(true);
builder.setWhen(System.currentTimeMillis());
//显示的文本
builder.setTicker(result);
builder.setContentIntent(progress >= 100 ? getContentIntent()
: PendingIntent.getActivity(this, 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT));
builder.setChannelId(getApplication().getPackageName()); //必须添加(Android 8.0) 【唯一标识】
builder.setPriority(Notification.PRIORITY_MIN).setSmallIcon(R.mipmap.ic_launcher)
// .setColor(0xFF000000)
//.setContentIntent(PendingIntent.getService(this, 0, actionIntent, 0))
.setOngoing(false)
.setShowWhen(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setupNotificationChannel();
builder.setChannelId("test");
}
mNotificationCompat = builder.build();
mNotificationManagerCompat.notify(0, mNotificationCompat);
}
@RequiresApi(api = Build.VERSION_CODES.O)
private void setupNotificationChannel() {
String channelName = "Termux_Backer";
String channelDescription = "Notifications from Termux_Backer";
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel channel = new NotificationChannel("test", channelName, importance);
channel.setDescription(channelDescription);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
manager.createNotificationChannel(channel);
}
//点击notification的操作
private PendingIntent getContentIntent() {
//安装APk
Toast.makeText(this, "安装APk", Toast.LENGTH_SHORT).show();
// install();
return null;
}
public void install() {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri uri;
File file = new File(filePath);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
String packageName = getApplication().getPackageName();
String authority = new StringBuilder(packageName).append(".fileprovider").toString();
uri = FileProvider.getUriForFile(getApplicationContext(), authority, file);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
} else {
uri = Uri.fromFile(file);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
}
getApplicationContext().startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
/**
* @author zl
* 版本 1.0
* 创建时间 2021/6/6
* 描述:
*/
public class UpdateActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 101);
} else {
Intent intent = new Intent(this, UpdateService.class);
startService(intent);
boolean en = NotificationManagerCompat.from(this).areNotificationsEnabled();
Log.e("tag", en + "");
}
}
}
其他配置
- AndroidManifest.xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/>
<!-- 安装apk 权限 -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<provider
android:exported="false"
android:grantUriPermissions="true"
android:authorities="com.liangzai.fulisehapplication.fileprovider"
android:name="androidx.core.content.FileProvider">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"
/>
</provider>
-
file_paths配置
<?xml version="1.0" encoding="utf-8"?> <paths> <!-- 注意 name 是不能一样的,不然会覆盖 --> <!-- files-path /data/data/包名/files 目录 --> <!-- external-path /storage/emulate/0 目录 --> <!-- external-files-path /storage/emulate/0/Android/data/包名/files 目录 --> <!-- Environment.getExternalStorageDirectory() 和 external-path 对应 --> <external-path name="external_files" path="."/> <files-path name="apk" path="apk/"/> </paths>
关于遗留的部分
- 不能重复下载,应该在网络下载之前判断本地是否已存在或本地版本是不是最新的,后序版本优化
快捷键配置
- option + command + t try catch 快捷键
- logt 快速打印 TAG
- logd 快速打印 Log