使用okhttp3進行應用內的更新(Android)

背景:因爲手頭上做的這個app在Android上沒有進行大規模的團隊開發,但是想先發給玩家進行測試。所以就需要在app中添加一個更新的模塊。

效果:進入app的時候,就會檢測當前的版本是不是最新的版本,如果是的話就會進行彈窗提示,設置彈窗是否可以關閉來代表此次更新是否是強制的更新。

使用的框架是okhttp3

github的地址是:https://github.com/square/okhttp 

1、首先我們需要一個http服務器。因爲我們需要將我們的新版本的安裝包和一個說明文件放在這個服務器上,然後客戶端通過網絡去獲取這兩個東西。

因爲我們一旦檢測到有版本的更新的話,我們的彈窗裏面就需要去顯示此次更新的內容有哪些,所以這個彈窗裏面的內容我們不能寫死,只能從服務器去獲取

2、我們需要一個新的apk,和一個Json文件,新的apk作爲Demo中更新的新版本apk,我們只需要讓他界面不同就可以,還有一個Json文件,這個Json文件裏面我們需要存放一些信息,比如此次更新了哪些內容,然後我們應該去哪裏才能得到新的apk,還新的apk文件的md5碼,我們需要這個碼來做校驗。因爲項目的需要,我在裏面還存放了此次版本升級的等級,如果等級是:HIGH的話,彈窗就不能關閉,也就是強制性升級,如果是LOW的話,彈窗就可以關閉,也就是看玩家自己需求去更新。

3、我們的代碼結構比較簡單:

A.網絡部分:用來進行get,download等操作

B.ui,彈窗

C.Json解析:因爲我們熊服務器首先獲取的內容是一個json文本,用來填充彈窗的內容等,所以需要先解析一下

D.其他:因爲我們裏面需要進行apk文件的拷貝以及新生成安裝界面去安裝apk,所以這些雜七雜八的事情統一歸置到一個工具類

然後我們聲明一個AppUpdater.java,裏面歸納了上面寫部分的所有接口,這我們在MainActivity裏面只需要聲明一個AppUpddater的對象,這樣就可以幹所有的事情了。

下面我們來進行每一個部分的解析:

A部分:網絡部分,用來進行get,download等操作

用來進行get,download等操作

get操作其實也就是download操作,只不過download的是一個json文本,這文本里面存放的信息有這些:

{
    "title":"當前版本:1.4.5!",
    "content":"1. 優化了bug1;\n2. 上線了功能2 ;\n3. 修復了一些問題。",
    "url":"http://192.168.0.104:8083/MyApplication.apk",
    "md5":"bd90eda125f34bf2aed1876417cb3523",
    "versionCode":"450"
}

裏面的信息前面兩個是我們在彈窗裏面顯示內容,我們get到的信息,之後可以建一個類,然後將這些變量儲存在對象中的每一個成員當中,url是我們服務器的地址,訪問這個地址我們可以獲取到這個apk(這裏使用的是我自己搭建的http服務器,可能當你們看到這個文章的時候,我的服務器已經關掉了,所以不需要大驚小怪~~~)

KNetGetCallback類:

我們在使用get操作去get這個json文件之後,如果成功服務器會給我們返回一段字符,如果失敗,就會返回錯誤的信息,然後我們就需要調對應的回調函數

public interface KNetGetCallback {
    // get成功的話應該返回一個json字符串
    void getSuccess(String reponse);
    //get失敗拋出異常
    void getFailed(Throwable throeable);
}

初次之外,我們還需要一個DownLoad類,去Download我們的apk,這個類和上面的其實本質上是一樣的,都是從服務器去獲取文件,只不過物品,我們把上面的那個叫做Get操作,然後我們把這裏的叫做DownLoad操作,然後當我們DownLoad完成之後,成功:我們應該怎麼做,失敗,我們應該怎麼做。這裏我們還多餘了一個成員函數,就是獲取當前下載的文件的進度,用來給用戶顯示。

Tip:之所以第一步的Get操作不用顯示下載進度,是因爲我們第一步獲取的只是一個簡單的文本文件,很小,所以沒必要去顯示,獲取的進度,對於用戶,這裏我們只需要Get  Json文件的內容就可以。

package com.appupdata.updata.net;

import java.io.File;

public interface KNetDownLoadCallback {
    // 下載成功應該返回一個文件,這個時候應該是一個apk文件
    void downloadSuccess(File apkFile);
    // 下載失敗拋出異常
    void downloadfailed(Throwable throowable);
    // 下載的過程返回進度
    void downloadProgress(int progress);
}

上面就是我們分別對應獲取Json文本以及apk文件的回調,下面就是我們對應的請求操作了:

package com.appupdata.updata.net;

import java.io.File;

public interface KNetManager {

    //需要讓服務器返回一段json數據,告訴我們要不要進行應用升級
    void get(String url,KNetGetCallback callback,Object tag);
    // 下載
    void  download(String url, File targetFile,KNetDownLoadCallback callback,Object tag);
    // 取消
    void cancel(Object tag);
}

第一個get函數就是我們對應去get Json文本的函數,第一個參數是url,第二個參數是獲取成功或者獲取失敗的回調,第三個是一個標誌位, 我們可以使用這個標誌位去進行對應的取消操作,就在第三個函數。

 

第二個函數是我們DownLoad APK文件的函數,第一個參數就是url,第二個是我們傳進來的一個空的文件,因爲這個時候,我們需要將apk文件從服務器上面讀下來,儲存在本地。所以我們需要一個空文件來寫,第三個參數使我們之前設置的DownLoad回調,第四個參數是我們操作的標誌位。

 

第三個成員函數:cancel是我們爲用戶的取消操作,而設置的,因爲有時候,當用戶在下載到一半的時候,可能因爲突發情況,而停止下載,這個時候,如果不設置沒有對應的處理措施,就可能有異常狀況發生。

以上就是我們準備的接口類,KNetManager裏面使用的是KNetGetCallback,KNetDownLoadCallback接口。後面的兩個接口,等我們具體使用的時候再去實現裏面的成員函數。

對於KNetManager我們創建一個OkHttpNetManager的類去實現裏面的三個成員函數:

我會這代碼裏面進行詳細的註釋,解釋怎麼創建一個請求,以及當你的服務器是https的時候我們應該怎麼設置證書之類的。

Talk is cheap,show me the code(裝個13)

package com.appupdata.updata.net;

import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.appupdata.MainActivity;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
//import java.util.logging.Handler;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;


public class OkHttpNetManager implements KNetManager{

    // 我們創建一個httpclient的對象,用來之後的時候進行一些關於http的設置
    private static OkHttpClient sOkHttpClient;
    // 我們這個時候需要獲取一下主線程,也就是我們的ui線程。因爲我們在出現錯誤的時候,可以使用彈窗,給用戶一些提示,這個時候的ui我們就需要在ui線程上展示
    private static Handler sHandler = new Handler(Looper.getMainLooper());

    static {
        
        // 使用http服務器
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(15, TimeUnit.SECONDS);
        sOkHttpClient = builder.build();
        
        
        // 下面的設置,是因爲有時候我們使用的是https的服務器,但是https的服務器是經過加密的
        // 所以我們就需要下面的設置,或者直接設置忽略https的證書,但是這樣做,https就又失去了意義,所以只是把這些操作列出來,但是我們不使用,如果有需要使用的同學,可以將下面的註釋去掉
        /*
        // 忽略https證書
        sOkHttpClient = builder.sslSocketFactory(sslContext.getSocketFactory())
                .hostnameVerifier(new TrustAllHostnameVerifier())
                .build();
        */

        // 使用https服務器
        /*
        * 需要設置https的證書*/
        //sOkHttpClient = getOkHttpClient_newInterface();

    }

    // 重載我們的get函數 
    @Override
    public void get(String url,final KNetGetCallback callback,Object tag)
    {
        // 下面是通過url創建一個okhttp請求
        // 通過request builder 獲得一個request對象
        // 通過request獲得一個call對象
        // 通過call對象去執行execute/enqueue
        Request.Builder builder =  new Request.Builder();
        // 返回一個request對象
        Request request = builder.url(url).get().tag(tag).build();
        Call call = sOkHttpClient.newCall(request);

        // 該接口會直接返回一個結果,類似與一個同步的操作
        //call.execute();

        // 類似於使用一個隊列進行異步的操作
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, final IOException e) {
                //不一定是UI線程,但是我們希望是UI線程,因爲可能牽扯一些UI的更新
                sHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        callback.getFailed(e);
                    }
                });
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                try {
                    final String string = response.body().string();
                    sHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            callback.getSuccess(string);
                        }
                    });
                }catch (Throwable e)
                {
                    e.printStackTrace();
                    callback.getFailed(e);
                }
            }
        });
    }

    // 重載download函數
    @Override
    public void  download(String url, final File targetFile, final KNetDownLoadCallback callback,Object tag)
    {
        if (!targetFile.exists())
        {
            //創建文件夾
            targetFile.getParentFile().mkdirs();
        }

        Request.Builder builder = new Request.Builder();
        final Request request = builder.url(url).get().tag(tag).build();
        Call call = sOkHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, final IOException e) {
                sHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        callback.downloadfailed(e);
                    }
                });
            }

            @Override
            public void onResponse(final Call call, Response response) throws IOException {
                InputStream is = null;
                OutputStream os = null;
                try {

                    final long totalLength= response.body().contentLength();

                    is = response.body().byteStream();
                    os = new FileOutputStream(targetFile);

                    // 寫文件的buffer
                    byte[] buffer = new byte[8 *1024];
                    // 當前寫了多少個字節
                    long curLenngth = 0;
                    //
                    int bufferLength = 0;

                    while (!(call.isCanceled())  && (bufferLength = is.read(buffer)) !=-1)
                    {
                        os.write(buffer,0,bufferLength);
                        os.flush();
                        // 這個累加就是爲了回調progress
                        curLenngth +=bufferLength;

                        final long finalCurLenngth = curLenngth;
                        sHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                callback.downloadProgress((int)(finalCurLenngth * 1.0f / totalLength *100));
                            }
                        });
                    }

                    // 設置文件的可讀可寫可執行
                    try {
                        targetFile.setExecutable(true,false);
                        targetFile.setReadable(true,false);
                        targetFile.setWritable(true,false);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    // 在這個時候判斷一下請求是不是被取消了,防止出現意外
                    if (call.isCanceled())
                    {
                        return;
                    }
                    // 到了這裏,說明文件已經下載完成,然後我們就是調用下載完成的回調了
                    sHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            callback.downloadSuccess(targetFile);
                        }
                    });
                } catch (final IOException e) {
                    if(call.isCanceled())
                    {
                        return;
                    }
                    e.printStackTrace();
                    sHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            callback.downloadfailed(e);
                        }
                    });
                }finally {
                    if (is != null)
                    {
                        is.close();
                    }

                    if (os != null)
                    {
                        os.close();
                    }
                }
            }
        });
    }

    // 這裏是重載cancel的回調
    @Override
    public void cancel(Object tag)
    {
        // 下面是使用okhttp裏面提供的接口,當用戶取消的時候,我們應該進行的操作
        // 獲取等待的call
        List<Call> queuedCallList = sOkHttpClient.dispatcher().queuedCalls();
        if (queuedCallList != null)
        {
            for (Call call:queuedCallList)
            {
                if (tag.equals(call.request().tag()))
                {
                    call.cancel();
                }
            }
        }

        // 獲取正在運行的call
        List<Call> runningCdallList = sOkHttpClient.dispatcher().runningCalls();
        if (runningCdallList != null)
        {
            for (Call call:runningCdallList)
            {
                if (tag.equals(call.request().tag()))
                {
                    call.cancel();
                }
            }
        }
    }

    
    // 下面的類是進行https的設置的類,這個時候首先我們得有一個https的服務器,或者直接使用阿里雲或者騰訊雲之類的,設置一下證書,然後我們把證書下載下來,下面的GoodCloud.cer就是我下載的https服務器的證書
    // 怎麼獲取證書以及怎麼搭建一個https的服務器,大家可以在網上查找,這裏就不做詳細的闡述了
    // 因爲我也是從網上找的方法,自己改了一下,就不在大神面前班門弄斧了
    private static OkHttpClient getOkHttpClient_newInterface()
    {
        try{
            SSLContext sslContext = null;
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            InputStream certificate = MainActivity.getAppContext().getAssets().open("GoodCloud.cer");
            String certificateAlias = Integer.toString(0);
            keyStore.load(null);
            keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
            final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
            if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
                throw new IllegalStateException("Unexpected default trust managers:"
                        + Arrays.toString(trustManagers));
            }
            X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
            sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[] { trustManager }, new SecureRandom());
            SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
            OkHttpClient client = new OkHttpClient.Builder()
                    .sslSocketFactory(sslSocketFactory, trustManager)
                    .build();
            return client;
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }

        return null;
    }

    public static void handleSSLHandshake() {
        try {
            TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }

                @Override
                public void checkClientTrusted(X509Certificate[] certs, String authType) {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] certs, String authType) {
                }
            }};

            SSLContext sc = SSLContext.getInstance("TLS");
            // trustAllCerts信任所有的證書
            sc.init(null, trustAllCerts, new SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
            HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });
        } catch (Exception ignored)
        {
            ignored.printStackTrace();
        }
    }

    private static class TrustAllHostnameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;//,無視hostName,直接返回true,表示信任所有主機,可跳過證書的驗證
        }

    }



}

B部分:ui彈窗

裝個部分就是我們的UI部分,其實說是ui,也就是一個簡單的彈窗。然後從我們的第一次Get下來的json文件裏面獲取的字符串,來填充裏面的信息,這裏貼上代碼,因爲項目的實際需求不同,可能有些功能不需要展示彈窗,所以就不進行詳細的講解了

package com.appupdata.updata.ui;

import android.app.Dialog;
import android.content.DialogInterface;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;

import com.appupdata.updata.AppUpdater;
import com.appupdata.updata.DownLoadBean.DownLoadBean;
import com.appupdata.updata.net.KNetDownLoadCallback;
import com.appupdata.updata.utils.AppUtils;
import com.kingsoft.appupdata.R;

import java.io.File;
import java.io.FileOutputStream;

public class UpdateVersionShowDialog extends DialogFragment {

    private static final String KEY_DOWN_LOAD_BEAN = "download_bean";
    // 聲明一個解析字符的類,我們會在第一次get的時候,將返回的json信息進行序列化,序列化的過程會在DownLoadBean裏面體現,然後在這裏直接使用
    private DownLoadBean mDownLoadBean;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        Bundle arguments = getArguments();
        if (arguments != null)
        {
            mDownLoadBean = (DownLoadBean)arguments.getSerializable(KEY_DOWN_LOAD_BEAN);
        }
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.dialog_updater,container,false);
        // 當這個彈窗創建的時候,使用這個函數進行信息的展示
        bindEvents(view);
        return view;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
        getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
    }

    private void bindEvents(View view)
    {
        TextView tvTitle = view.findViewById(R.id.text_title);
        TextView tvContent = view.findViewById(R.id.text_content);
        final TextView tvUpdate = view.findViewById(R.id.text_update);

        tvTitle.setText(mDownLoadBean.title);
        tvContent.setText(mDownLoadBean.content);
        // 彈窗裏面會有一個按鈕,這個按鈕,點擊之後就會進行下載,然後顯示下載的進度,這裏就會使用我們之前聲明的下載的成員函數,然後還會傳入我們下載成功的回調類:KNetDownLoadCallback
        tvUpdate.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(final View view)
            {
                view.setEnabled(false);
                final File targetFile = new File(getActivity().getCacheDir(),"updateinstall.apk");

// 這裏就是使用download函數進行下載,第一個參數是我們獲取的json文本里面apk文件的url
// 第二個參數是我們創建的空的文件,用來寫入服務器上下載的apk文件的內容
// 第三個參數是我們使用的回調函數,在下載成功或者下載失敗的時候需要做的回調                AppUpdater.getInstance().getNetManager().download(mDownLoadBean.url, targetFile, new KNetDownLoadCallback() {
                    @Override
                    public void downloadSuccess(File apkFile) {
                        view.setEnabled(true);
                        Log.d("DY",apkFile.getAbsolutePath());

                        // 下載成功之後開始隱藏彈窗,然後安裝
                        dismiss();

                        // 檢查文件的md5,判斷文件的完整性,然後安裝
                        String fileMD5 = AppUtils.getFileMD5(targetFile);
                        Log.d("md5 = ",fileMD5);

                        if (fileMD5 != null && fileMD5.equals(mDownLoadBean.md5))
                        {
                            // 安裝
                            AppUtils.installApk(getActivity(),apkFile);
                        }
                        else
                        {
                            Toast.makeText(getActivity(),"MD5檢測失敗,文件被損壞",Toast.LENGTH_SHORT).show();
                        }


                    }

                    @Override
                    public void downloadfailed(Throwable throowable) {
                        view.setEnabled(true);
                        Toast.makeText(getActivity(),"下載失敗",Toast.LENGTH_SHORT).show();
                    }
                    // 下載的時候,要隨時的更新當前下載的apk文件寫入的進度
                    @Override
                    public void downloadProgress(int progress) {
                        Log.d("DY","progress" + progress);
                        tvUpdate.setText(progress + "%");
                    }
                },UpdateVersionShowDialog.this);
            }
        });
    }

    @Override
    public void onDismiss(@NonNull DialogInterface dialog) {
        super.onDismiss(dialog);
        // 窗口隱藏的時候,我們也需要對okhttp進行對應的取消操作
        AppUpdater.getInstance().getNetManager().cancel(this);
    }
    
    // 展示我們的窗口
    public static void show(FragmentActivity activity, DownLoadBean bean)
    {
        Bundle bundle = new Bundle();
        bundle.putSerializable(KEY_DOWN_LOAD_BEAN,bean);
        UpdateVersionShowDialog dialog = new UpdateVersionShowDialog();
        dialog.setArguments(bundle);
        if (bean.updateLevel.equals("HIGH"))
        {
            dialog.setCancelable(false);
        }

        dialog.show(activity.getSupportFragmentManager(),"UpdateShowVersionDialog");
    }
}

C部分:Json解析

當我們第一次從服務器get到json文本的時候,就需要將json文本里面的內容進行解析,也就是序列化,以供以後的使用

package com.appupdata.updata.DownLoadBean;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.Serializable;

public class DownLoadBean implements Serializable {
    public String title;
    public String content;
    public String url;
    public String md5;
    public String versionCode;
    public String updateLevel;
    
    public static String getJson(String jsonStr) {
        if (jsonStr != null && jsonStr.startsWith("\ufeff")) {
            jsonStr = jsonStr.substring(jsonStr.indexOf("{"),
                    jsonStr.lastIndexOf("}") + 1);
        }
        return jsonStr;
    }
    // 析構函數,我們使用okhttp進行get操作之後,服務器就會給我們返回一段文本,這個文本里面就會
    // 有我們需要的信息包含:title、content(前面兩個用來填充彈窗裏面的信息)、apk的url(這        
    // 個url用來第二次download的時候,下載apk文件)、apk的md5(檢測下載下來的apk文件的完整
    // 性)、版本號(用來判斷是否需要更新)
    public static DownLoadBean parse(String respone)
    {
        try {
            // 將文件裏面的字段信息進行序列化,拆開然後賦值給新的對象
            respone = getJson(respone);
            JSONObject repJson = new JSONObject(respone);
            String title = repJson.optString("title");
            String content = repJson.optString("content");
            String url = repJson.optString("url");
            String md5 = repJson.optString("md5");
            String versionCode = repJson.optString("versionCode");
            // HIGH MEDIUM LOW
            String updateLevel = repJson.optString("updateLevel");

            DownLoadBean bean = new DownLoadBean();
            bean.title = title;
            bean.content = content;
            bean.url = url;
            bean.md5 = md5;
            bean.versionCode = versionCode;
            bean.updateLevel = updateLevel;

            return bean;

        } catch (JSONException e) {
            e.printStackTrace();
        }

        return null;
    }
}

D部分:其他

這一部分主要是我們在其中使用到的一些功能性函數的類,包含有:文件md5的檢測,apk的安裝等。

這些函數有一個共同的特點:就是不僅僅可以在更新的這個模塊中使用,也可以在其他的項目中使用,複用率比較高。

相當於一個工具類(這樣說我感覺更準確一些)

package com.appupdata.updata.utils;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;

import androidx.core.content.FileProvider;

import com.kingsoft.appupdata.BuildConfig;
import com.meituan.android.walle.WalleChannelReader;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.zip.ZipFile;

public class AppUtils {
    // 獲取當前apk的版本號,用來和服務器上的版本號進行對比,來確定是否需要進行更新
    public static long getVersionCode(Context context)
    {
        PackageManager packageManager = context.getPackageManager();
        try {
            PackageInfo packageInfo = packageManager.getPackageArchiveInfo(context.getPackageCodePath(),PackageManager.GET_ACTIVITIES);

            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
            {
                return packageInfo.getLongVersionCode();
            }
            else
            {
                return packageInfo.versionCode;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return  -1;
    }
    // 安裝apk
    public static void installApk(Activity activity, File apkFile)
    {
        Intent intent = new Intent();
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction(Intent.ACTION_VIEW);
        Uri uri = null;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
        {
            uri = FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID+".provider",apkFile);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        }
        else
        {
            uri = Uri.fromFile(apkFile);
        }
        intent.setDataAndType(uri,"application/vnd.android.package-archive");
        activity.startActivity(intent);
    }

    // 獲取文件的md5
    public static String getFileMD5(File targetFile) {
        if (targetFile == null || !targetFile.isFile())
        {
            return null;
        }

        MessageDigest digest = null;
        FileInputStream in = null;
        byte[] buffer = new byte[1024];
        int length = 0;

        try {
            digest = MessageDigest.getInstance("md5");
            in = new FileInputStream(targetFile);
            while ((length = in.read(buffer))!=-1)
            {
                digest.update(buffer,0,length);
            }

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }finally {
            if (in != null)
            {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        byte[] result = digest.digest();
        BigInteger bigInt = new BigInteger(1,result);
        return bigInt.toString(16);
    }
    // 這個是我根據項目需求,在裏面加的檢測渠道號的函數,和okhttp無關,這裏使用的是
    // 美團提供的命令行打包工具Walle,如果需要統計渠道號的話,可以在項目裏面直接配置,然後使用
    // 美團的打包工具Walle進行渠道號的打包,這裏使用提供的接口進行獲取渠道號。
    // 如果只是單純的使用okhttp的話,可以直接忽略這個函數
    // walle的GitHub地址:https://github.com/Meituan-Dianping/walle
    public static String getChannelData(Context context)
    {
        String channel = WalleChannelReader.getChannel(context);
        return channel;
    }

github地址:https://github.com/YuDang1024/AppUpdateForAndroid

歷史一個月,斷斷續續,終於搞完這篇文章,難免有疏忽和錯誤的地方,歡迎指正~!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章