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