Forwarding, please indicate the source: http://www.jianshu.com/p/b669940c9f3e
preface
Organize the function, take this piece out to do a separate demo, good to share and communicate with you. Version update this function is generally implemented in the APP, and users generally obtain the new version from two sources:
- One is new version alerts for various app markets
- One is to pull the version information when you open the app
- (There is also a push form, used more for hot fixes or patch packs)
The two differences lie in that the market cannot be forced to update, not timely enough, low viscosity, monotonous.
Abstract
Here are some of the techniques you will learn or review in this chapter:
- Dialog implementation key rewrite, after the popup window user can not click the virtual back button to close the window - ignore and will not be prompted, next version update popup again - custom service to host the download function - okhttp3 download file to sdcard, File authority judgment - binding service, callback download progress - simple MVP architecture - download automatically installedCopy the code
This is a screenshot of our company’s project with version update. Of course, the demo we’re going to implement isn’t going to write this complicated UI.
Function points (take a look at the final demo first)
dialog
Dialog. SetCanceledOnTouchOutside touch the window borders () whether to close the window, set the false is not close the dialog. The setOnKeyListener () sets the KeyEvent callback to monitor method. If the event is distributed to the Dialog, this event will be emitted. Normally, the event that touched the screen will be distributed to the Dialog first when the window is displayed, but by default it will return false without processing, i.e. continue to be distributed to the parent. If you’re just intercepting the return key, that’s all you need to do
mDialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
returnkeyCode == KeyEvent.KEYCODE_BACK && mDialog ! =null&& mDialog.isShowing(); }});Copy the code
ignore
Ignore this version update, do not pop up the next time there is a new version, and continue to pop up the reminder
In fact, this logic is easy to understand, there is no special code. The trick here is that it often takes every request to see if your app is up to date.
Here I didn’t do network request, merely as a simulation of the version number, and then do the normal logic, in our project, get the version number can get through request interface, that is to say, every time start request update interface, also it is very waste, I’m suggesting the version number in your home page and other interface information back together, Then write in SharedPreferences. Each time, check whether the version is the same as the ignored version and skip it. Otherwise, the interface will be updated during the next startup
public void checkUpdate(String local) {
// Assuming you get the latest version
// This is usually compared with the ignored version. It's not a drag here
String version = "2.0";
String ignore = SpUtils.getInstance().getString("ignore");
if(! ignore.equals(version) && ! ignore.equals(local)) { view.showUpdate(version); }}Copy the code
The custom service
Here we need to communicate with the service. We need to customize a bound service. We need to override several key methods. OnBind (return channel IBinder), unbindService(destroy resource when unbind), and write your own Binder to return retrievable Service objects when communicating. Perform other operations.
context.bindService(context,conn,flags)
- The context context
- Conn (ServiceConnnetion), Implementing this interface will let you implement two methods onServiceConnected(ComponentName, IBinder), which returns the IBinder object we will operate upon, onServiceDisconnected(ComponentName)
- Flags Service binding type. There are several types available, but the most common is service. BIND_AUTO_CREATE. If you bindService this parameter, your service will not respond.
You can manipulate the service by getting this object. This custom service is quite long. Download the demo and read it carefully.
public class DownloadService extends Service {
// Define the notify ID to avoid conflicts with other notification processing
private static final int NOTIFY_ID = 0;
private static final String CHANNEL = "update";
private DownloadBinder binder = new DownloadBinder();
private NotificationManager mNotificationManager;
private NotificationCompat.Builder mBuilder;
private DownloadCallback callback;
// Define an update rate to prevent the update notification bar from being jammed too frequently
private float rate = .0f;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public void unbindService(ServiceConnection conn) {
super.unbindService(conn);
mNotificationManager.cancelAll();
mNotificationManager = null;
mBuilder = null;
}
/** * Binder to communicate with activities */
public class DownloadBinder extends Binder{
public DownloadService getService(a){
return DownloadService.this; }}/** * Create notification bar */
private void setNotification(a) {
if (mNotificationManager == null)
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mBuilder = new NotificationCompat.Builder(this,CHANNEL);
mBuilder.setContentTitle("Start downloading")
.setContentText("Connecting to server")
.setSmallIcon(R.mipmap.ic_launcher_round)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setOngoing(true)
.setAutoCancel(true)
.setWhen(System.currentTimeMillis());
mNotificationManager.notify(NOTIFY_ID, mBuilder.build());
}
/** * Download complete */
private void complete(String msg) {
if(mBuilder ! =null) {
mBuilder.setContentTitle("New version").setContentText(msg);
Notification notification = mBuilder.build();
notification.flags = Notification.FLAG_AUTO_CANCEL;
mNotificationManager.notify(NOTIFY_ID, notification);
}
stopSelf();
}
/** * Start downloading apk */
public void downApk(String url,DownloadCallback callback) {
this.callback = callback;
if (TextUtils.isEmpty(url)) {
complete("Download path error");
return;
}
setNotification();
handler.sendEmptyMessage(0);
Request request = new Request.Builder().url(url).build();
new OkHttpClient().newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Message message = Message.obtain();
message.what = 1;
message.obj = e.getMessage();
handler.sendMessage(message);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.body() == null) {
Message message = Message.obtain();
message.what = 1;
message.obj = "Download error";
handler.sendMessage(message);
return;
}
InputStream is = null;
byte[] buff = new byte[2048];
int len;
FileOutputStream fos = null;
try {
is = response.body().byteStream();
long total = response.body().contentLength();
File file = createFile();
fos = new FileOutputStream(file);
long sum = 0;
while((len = is.read(buff)) ! = -1) {
fos.write(buff,0,len);
sum+=len;
int progress = (int) (sum * 1.0 f / total * 100);
if(rate ! = progress) { Message message = Message.obtain(); message.what =2;
message.obj = progress;
handler.sendMessage(message);
rate = progress;
}
}
fos.flush();
Message message = Message.obtain();
message.what = 3;
message.obj = file.getAbsoluteFile();
handler.sendMessage(message);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if(is ! =null)
is.close();
if(fos ! =null)
fos.close();
} catch(Exception e) { e.printStackTrace(); }}}}); }/** * the path is the root directory * the file name is called updateDemo.apk */
private File createFile(a) {
String root = Environment.getExternalStorageDirectory().getPath();
File file = new File(root,"updateDemo.apk");
if (file.exists())
file.delete();
try {
file.createNewFile();
return file;
} catch (IOException e) {
e.printStackTrace();
}
return null ;
}
/** * put the processing result back into the UI thread */
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 0:
callback.onPrepare();
break;
case 1:
mNotificationManager.cancel(NOTIFY_ID);
callback.onFail((String) msg.obj);
stopSelf();
break;
case 2: {int progress = (int) msg.obj;
callback.onProgress(progress);
mBuilder.setContentTitle("Downloading: New version...")
.setContentText(String.format(Locale.CHINESE,"%d%%",progress))
.setProgress(100,progress,false) .setWhen(System.currentTimeMillis()); Notification notification = mBuilder.build(); notification.flags = Notification.FLAG_AUTO_CANCEL; mNotificationManager.notify(NOTIFY_ID,notification); }break;
case 3:{
callback.onComplete((File) msg.obj);
// The app runs in the interface and is installed directly
// Otherwise run in the background to inform the form of completion
if (onFront()) {
mNotificationManager.cancel(NOTIFY_ID);
} else {
Intent intent = installIntent((String) msg.obj);
PendingIntent pIntent = PendingIntent.getActivity(getApplicationContext()
,0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
mBuilder.setContentIntent(pIntent)
.setContentTitle(getPackageName())
.setContentText("Download complete, click Install")
.setProgress(0.0.false) .setDefaults(Notification.DEFAULT_ALL); Notification notification = mBuilder.build(); notification.flags = Notification.FLAG_AUTO_CANCEL; mNotificationManager.notify(NOTIFY_ID,notification); } stopSelf(); }break;
}
return false; }});/** * whether to run before the user */
private boolean onFront(a) {
ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
if (appProcesses == null || appProcesses.isEmpty())
return false;
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.processName.equals(getPackageName()) &&
appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
return true; }}return false;
}
/** * Install * 7.0 remember to configure fileProvider */
private Intent installIntent(String path){
try {
File file = new File(path);
String authority = getApplicationContext().getPackageName() + ".fileProvider";
Uri fileUri = FileProvider.getUriForFile(getApplicationContext(), authority, file);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
} else {
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
}
return intent;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/** * Clear the hold */ for notify when it is destroyed
@Override
public void onDestroy(a) {
mNotificationManager = null;
super.onDestroy();
}
/** * define the callback method */
public interface DownloadCallback{
void onPrepare(a);
void onProgress(int progress);
void onComplete(File file);
void onFail(String msg); }}Copy the code
Okhttp3 downloads the file
See things for what they are, and you can do whatever you want. How to make the simplest request for OKHttp3, see below! It is simple and clear. Here, the most important thing is that everyone’s business, framework and requirements are not the same, so it is good to save time to understand the writing logic, so that the transplant to their own projects will not be unable to start. It’s a good idea to combine this with popular insertions like Retrofit and Volley. Avoid introducing too many third party libraries that will slow down the compilation, because the project is bloated.
If the downApk method is empty, the notification bar will tell the user that the download path is wrong. Create a request and execute the request. To download apK, we only need a URL (this URL is usually returned in the background when checking the version update interface). And then the first step is configured, and the next step is to figure out how to write the file stream to SDcard.
Write: means to read a file into your app (InputStream InputStreamReader FileInputStream BufferedInputStream) and write: It means that your app is pulling to sdcard (OutputStream OutputStreamWriter FileOutputStream BufferedOutputStream)
This is for those who have been confused about input and ouput
Request request = new Request.Builder().url(url).build();
new OkHttpClient().newCall(request).enqueue(new Callback() {});
Copy the code
Write a file
InputStream is = null;
byte[] buff = new byte[2048];
int len;
FileOutputStream fos = null;
try {
is = response.body().byteStream(); // Read the network file stream
long total = response.body().contentLength(); // Get the total number of bytes of the file stream
File file = createFile(); CreateFile () creates an empty file in the specified path and returns
fos = new FileOutputStream(file); // Digest the toilet ready
long sum = 0;
while((len = is.read(buff)) ! = -1) { // bang ~ bang ~ bit by bit towards sdcard &#$%@$%#%$
fos.write(buff,0,len);
sum+=len;
int progress = (int) (sum * 1.0 f / total * 100);
if(rate ! = progress) {// Use the handler callback to notify the download progress
rate = progress;
}
}
fos.flush();
// Use the handler callback to notify that the download is complete
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if(is ! =null)
is.close();
if(fos ! =null)
fos.close();
} catch(Exception e) { e.printStackTrace(); }}Copy the code
File download callback
In the okHTTP download above, I annotated the location of the callback, because the download thread is no longer in the UI thread, so it makes sense to use handler to put the data back into a thread that can manipulate the UI and then return it. Once the callback is implemented outside, we can directly process the data.
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 0:// Pre-download operations, such as checking whether the network is wifi
callback.onPrepare();
break;
case 1:// Download failed, empty the notification bar, and destroy the service itself
mNotificationManager.cancel(NOTIFY_ID);
callback.onFail((String) msg.obj);
stopSelf();
break;
case 2: {// Displays the real-time progress of the notification bar
int progress = (int) msg.obj;
callback.onProgress(progress);
mBuilder.setContentTitle("Downloading: New version...")
.setContentText(String.format(Locale.CHINESE,"%d%%",progress))
.setProgress(100,progress,false) .setWhen(System.currentTimeMillis()); Notification notification = mBuilder.build(); notification.flags = Notification.FLAG_AUTO_CANCEL; mNotificationManager.notify(NOTIFY_ID,notification); }break;
case 3: {// Download successfully, the user will directly install in the interface, otherwise ding a notification bar to remind, click the notification bar to jump to the installation interface
callback.onComplete((File) msg.obj);
if (onFront()) {
mNotificationManager.cancel(NOTIFY_ID);
} else {
Intent intent = installIntent((String) msg.obj);
PendingIntent pIntent = PendingIntent.getActivity(getApplicationContext()
,0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
mBuilder.setContentIntent(pIntent)
.setContentTitle(getPackageName())
.setContentText("Download complete, click Install")
.setProgress(0.0.false) .setDefaults(Notification.DEFAULT_ALL); Notification notification = mBuilder.build(); notification.flags = Notification.FLAG_AUTO_CANCEL; mNotificationManager.notify(NOTIFY_ID,notification); } stopSelf(); }break;
}
return false; }});Copy the code
Automatic installation
Android has been iterating faster and faster, and some apis have been abandoned or even nonexistent. File permissions in 7.0 are particularly strict, so some of the previous code may crash on higher systems, such as the following. If the version is not tested, phones in 7.0 will throw FileUriExposedException, stating that the app cannot access resources outside of your app. The official documentation recommends using FileProviders for file sharing. This means creating an XML folder in your project’s SRC /res folder and creating a custom file, and configuring this in the configuration manifest
file_paths.xml
<? The XML version = "1.0" encoding = "utf-8"? > <paths> <external-path name="external" path=""/> </paths>Copy the code
Install the apk
try {
String authority = getApplicationContext().getPackageName() + ".fileProvider";
Uri fileUri = FileProvider.getUriForFile(this, authority, file);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// Temporary read permission is required above 7.0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
} else {
Uri uri = Uri.fromFile(file);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
}
startActivity(intent);
// Pop up the installation window to close the original program.
// Avoid no reaction when clicking open after installation
killProcess(android.os.Process.myPid());
} catch (Exception e) {
e.printStackTrace();
}
Copy the code
I’ve put the Demo on Github and hopefully you’ll learn something from it and not get confused