从零开始搞一个androidApp,实现h5自动更新、jsbridge

准备

window电脑

java jdk (包含了java jre)下载安装

android sdk下载安装

android studio下载安装

gradle下载

一台带sim卡的android手机

nodejs下载安装

 npm install -g cordava 安装

 

1、运行app,拉取版本控制文件 https://xxx.com/project/update.json

/* 格式如下 MD5_8标示8位数的md5值*/
/* version:全量更新的tag,如果本地与version不一样,就做全量更新*/
/* last_version:最新的版本*/
/* pres_version:有增量包的版本,不超过10个*/
{
  version:20230101
  last_version:"MD5_8"
}

需要app端实现 下载增量包或全量包,更新h5版本

1、如果version与本地不同,则下载全量包 ${down_path}${last_version}.zip

2、如果version与本地相同,则判断last_version

3、如果last_version与本地不同,首先下载增量包 ${down_path}${pre_version}-${last_version}.zip,

4、如果增量包存在,则完成更新下载

5、如果增量包不存在,则下载全量包${down_path}${last_version}.zip

6、如果全量包存在,则完成更新下载

7、如果全量包不存在,则更新下载失败,客户端继续使用旧版本

 碰到的问题

访问本地assets目录下的json文件,解析成json

String CACHEPATH = "update.json";//assets目录下的缓存文件
        String UPDATEPATH = "http://caoke90.gitee.io/suiplugin/update.json";//每次更新的文件
        String DOWNDIR = "https://example.com/project/dist/";//zip所在的远程目录

        String pre_res_version="";
        String pre_h5_version="";
        try {
            // 1. 读取Android应用程序资源JSON文件
            InputStreamReader isr = new InputStreamReader(getAssets().open(CACHEPATH), "UTF-8");
            BufferedReader br = new BufferedReader(isr);
            String line;
            StringBuilder builder = new StringBuilder();
            while ((line = br.readLine()) != null) {
                builder.append(line);
            }
            br.close();
            isr.close();
            JSONObject cacheJSON = new JSONObject(builder.toString());//builder读取了JSON中的数据。
            //直接传入JSONObject来构造一个实例
            pre_res_version = cacheJSON.getString("res_version");
            pre_h5_version = cacheJSON.getString("h5_version");
        }
        catch (Exception e) {
           Log.i("updateH5Assets","assets目录不存在"+CACHEPATH);
        }

 

https请求json报错:

1、使用okhttp3,src目录AndroidManifest.xml文件manifest中添加下一行,配置允许请求网络

<uses-permission android:name="android.permission.INTERNET"/>

2、src目录下build.gradle文件中添加依赖,用gradle下载

implementation 'com.squareup.okhttp3:okhttp:4.9.0'

3、请求https报错,设置信任所有的证书,定义了单例Ajax,Ajax.getText("https://www.cnblogs.com/caoke/p/17209909.html")

4、下载zip包,解压到cache目录下www目录异常,读取本地json文件,报错json文件,定义了工具类UtilsApi,本地h5文件的更新功能有了

 

 

5、在index.html页面点击a标签打开test.html,发现报错,添加webviewClient解决

 到目前为止:实现了h5的版本更新功能。

入口文件:MainActivity.java

package pers.example.demo1;

import android.os.StrictMode;
import java.io.*;

import android.os.Bundle;
import android.webkit.WebSettings;
import android.webkit.WebViewClient;


import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    BridgeWebView webView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //在Android 4.0以上,网络连接不能放在主线程上
        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
        StrictMode.setThreadPolicy(policy);

        //1、配置更新
        String cacheDir = this.getCacheDir().getAbsolutePath();
        String cacheUpdatePath = cacheDir+"/www/update.json";//本地的缓存文件
        String remoteUpdatePath = "http://192.168.3.213/update.json";//每次更新的文件
        String remotezipDir = "http://192.168.3.213/";//压缩包zip所在的远程目录
        String cacheWWWPath = cacheDir+"/www";//本地的www目录
        File cacheWWWFile=new File(cacheWWWPath);
        //2、更新本地h5资源
        UtilsApi.updateH5Assets(cacheUpdatePath,remoteUpdatePath,remotezipDir,cacheWWWFile);

        setContentView(R.layout.activity_main);
        //3、设置webview
        webView = findViewById(R.id.webView);
        //声明WebSettings子类
        WebSettings webSettings = webView.getSettings();

        webSettings.setAllowFileAccess(true);//不然本地html打不开
        webSettings.setUseWideViewPort(true);
        webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);

        webSettings.setJavaScriptEnabled(true);//才能让Webivew支持<meta>标签的viewport属性
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

        webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式

        webView.setWebViewClient(new WebViewClient());

        //4、加载手机本地的html页面
        webView.loadUrl(cacheWWWPath+"/index.html");
//        webView.loadUrl("http://www.baidu.com");

        // 5. 展示所有文件
        showList(cacheWWWFile);
    }
    private static void showList(File file) {
        if (file.isDirectory()) {//如果是目录
            System.out.println("文件夹:" + file.getPath());
            File[] listFiles = file.listFiles();//获取当前路径下的所有文件和目录,返回File对象数组
            for (File f : listFiles) {//将目录内的内容对象化并遍历
                showList(f);
            }
        } else if (file.isFile()) {//如果是文件
            System.out.println("文件:" + file.getPath());
        }
    }

}
View Code

h5更新功能:UtilsApi.java

package pers.example.demo1;


import android.util.Log;

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

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;



public class UtilsApi {
    //    构造方法设置为私有,避免在Singleton类外部创建Singleton对象
    private UtilsApi() {}

    public static void updateH5Assets(String cacheUpdatePath,String remoteUpdatePath,String remotezipDir,File cacheWWWFile) {
        String TAG="updateH5Assets";

        String pre_res_version="";
        String pre_h5_version="";
        try {
            fileProber(new File(cacheUpdatePath));
            // 1. 读取Android应用程序资源JSON文件
            JSONObject cacheJSON = UtilsApi.readFileToJSON(cacheUpdatePath);//builder读取了JSON中的数据。
            //直接传入JSONObject来构造一个实例
            pre_res_version = cacheJSON.getString("res_version");
            pre_h5_version = cacheJSON.getString("h5_version");
        }
        catch (Exception e) {
            Log.i(TAG,"assets目录不存在"+cacheUpdatePath);
        }
        Log.i(TAG,pre_res_version);
        Log.i(TAG,pre_h5_version);

        int tag=0;
        try {
            Log.i(TAG,remoteUpdatePath);
            // 2. 下载JSON文件
            JSONObject nJSON=Ajax.getJSON(remoteUpdatePath);
            // 3. 解析JSON
            String res_version=nJSON.getString("res_version");
            String h5_version=nJSON.getString("h5_version");

            String zipPath="";
            if(!res_version.equals(pre_res_version)){
                deleteAllFiles(cacheWWWFile);
                //下载全量包
                tag=1;
                zipPath=remotezipDir+h5_version+".zip";
            }else if(!h5_version.equals(pre_h5_version)){
                //下载增量包
                tag=2;

                deleteAllFiles(cacheWWWFile);
                zipPath=remotezipDir+h5_version+".zip";
//                zipPath=remotezipDir+pre_h5_version+"-"+h5_version+".zip";
            }
            Log.i(TAG,zipPath);
            if(tag>0){
                InputStream zipStream =Ajax.getInputStream(zipPath);

                unzipWithStream(zipStream,cacheWWWFile);
                zipStream.close();
                // 4. 存储文件到Android应用程序资源目录
                writeFileByJSON(cacheUpdatePath, nJSON);
            }
        }catch (Exception e) {
            e.printStackTrace();
        }

    }
    // 删除文件夹以及子文件夹、子文件
    public static void deleteAllFiles(File root) {
        File files[] = root.listFiles();
        if (files != null)
            for (File f : files) {
                if (f.isDirectory()) { // 判断是否为文件夹
                    deleteAllFiles(f);
                    try {
                        f.delete();
                    } catch (Exception e) {
                    }
                } else {
                    if (f.exists()) { // 判断是否存在
                        deleteAllFiles(f);
                        try {
                            f.delete();
                        } catch (Exception e) {
                        }
                    }
                }
            }
    }
    /**
     * 解压
     *
     * @param zipStream
     * @param destFile
     */
    public static void unzipWithStream(InputStream zipStream, File destFile)
    {

        try {
            ZipInputStream zis = new ZipInputStream(zipStream);
            ZipEntry zipEntry = null;
            while ((zipEntry = zis.getNextEntry()) != null) {
                String dir = destFile.getPath() + File.separator + zipEntry.getName();
                File dirFile = new File(dir);
                //创建上级文件夹
                fileProber(dirFile);
                if (zipEntry.isDirectory()) {
                    dirFile.mkdirs();
                } else {
                    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dir));
                    int len;
                    byte[] buff = new byte[1024];
                    while ((len = zis.read(buff, 0 ,1024)) != -1) {
                        bos.write(buff, 0, len);
                    }
                    bos.close();
                }
                zis.closeEntry();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }
    // 确保上级目录存在
    private static void fileProber(File dirFile) {
        File parentFile = dirFile.getParentFile();
        if (!parentFile.exists()) {
// 递归寻找上级目录
            fileProber(parentFile);
            parentFile.mkdir();
        }
    }
    public static JSONObject readFileToJSON(String filePath) throws IOException, JSONException {
        InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "UTF-8");
        BufferedReader br = new BufferedReader(isr);
        String line;
        StringBuilder builder = new StringBuilder();
        while ((line = br.readLine()) != null) {
            builder.append(line);
        }
        br.close();
        isr.close();
        return new JSONObject(builder.toString());//builder读取了JSON中的数据。
    }
    public static void writeFileByJSON(String filePath, JSONObject jsonObject) throws IOException {

        FileOutputStream fos= new FileOutputStream(filePath);
        OutputStreamWriter os= new OutputStreamWriter(fos);
        BufferedWriter w= new BufferedWriter(os);
        w.write(jsonObject.toString());
        w.close();
    }
}
View Code

接口请求:Ajax.java

package pers.example.demo2;

import android.annotation.SuppressLint;
import android.util.Log;

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

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/**
 * Created by ck on 23-3-15
 * okhttp3的再封装
 */


public class Ajax {
    private static OkHttpClient client = null;
    //    构造方法设置为私有,避免在Singleton类外部创建Singleton对象
    private Ajax() {}
    // 证书工厂
    @SuppressLint("TrulyRandom")
    private static SSLSocketFactory createSSLSocketFactory() {
        SSLSocketFactory sSLSocketFactory = null;
        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, new TrustManager[]{new TrustAllManager()},
                    new SecureRandom());
            sSLSocketFactory = sc.getSocketFactory();
        } catch (Exception ignored) {
        }
        return sSLSocketFactory;
    }
    // 证书管理器
    private static class TrustAllManager implements X509TrustManager {
        @SuppressLint("TrustAllX509TrustManager")
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
        }

        @SuppressLint("TrustAllX509TrustManager")
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }
    // 证书端口
    private static class TrustAllHostnameVerifier implements HostnameVerifier {
        @SuppressLint("BadHostnameVerifier")
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }
    private static OkHttpClient getClient() throws UnsupportedEncodingException {
        if(client==null){
            synchronized (OkHttpClient.class)
            {
                if (client == null)
                {
                    client= new OkHttpClient.Builder()
                            .connectTimeout(10, TimeUnit.SECONDS)
                            .readTimeout(10, TimeUnit.SECONDS)
                            .writeTimeout(10, TimeUnit.SECONDS)
                            .sslSocketFactory(createSSLSocketFactory(),new TrustAllManager())
                            .hostnameVerifier(new TrustAllHostnameVerifier())
                            .retryOnConnectionFailure(true).build();

                }
            }
        }
        return client;
    }

    public static String getText(String url) throws IOException {

        Request request = new Request.Builder().header("User-Agent","AndroidApp-AndroidApp/1.1.0").url(url).build();

        Response response = getClient().newCall(request).execute();
        if(response.isSuccessful()){
            return response.body().string();
        }
        throw new IOException();
    }
    public static JSONObject getJSON(String url) throws IOException, JSONException {

        Request request = new Request.Builder()
                .header("User-Agent","AndroidApp-AndroidApp/1.1.0")
                .url(url).get().build();

        Response response = getClient().newCall(request).execute();
        if(response.isSuccessful()){
            return new JSONObject(response.body().string());
        }
        throw new IOException();
    }
    public static InputStream getInputStream(String url) throws IOException {

        Request request = new Request.Builder().header("User-Agent","AndroidApp-AndroidApp/1.1.0").url(url).build();

        Response response = getClient().newCall(request).execute();
        if(response.isSuccessful()){
            return response.body().byteStream();
        }
        throw new IOException();
    }
}
View Code

 

JS调用Android基本有下面三种方式,主要用到第2个

webView.addJavascriptInterface()
WebViewClient.shouldOverrideUrlLoading()
WebChromeClient.onJsAlert()/onJsConfirm()/onJsPrompt() 方法分别回调拦截JS对话框alert()、confirm()、prompt()消息

Android调用JS,主要用到第1个

webView.loadUrl();
webView.evaluateJavascript()

通过数据格式消息体,实现两端的听、说通信
public class Message {
public String responseId;
public String responseData;
public String callbackId;
public String data;
public String handlerName;
}
callbackId等于responseId
根据jsbrige1.0.4框架版本,实现了通信方案
前端js调用java
1、js端带着请求格式Message{handlerName,callbackId,data},用iframe.src通知java开始接受数据
2、java端loadUrl调用javascript:WebViewJavascriptBridge._fetchQueue();
3、js端拼接数据,
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
3、java端在shouldOverrideUrlLoading中接受数据集合,flushMessageQueue函数中处理请求或者回复

java调用前端js

1、页面不存在时记录要说的,页面在就直接发送数据

2、函数将数据转成请求格式Message{handlerName,callbackId,data}的数据,然后发送出去
2、java端loadUrl调用javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');
3、js端在_dispatchMessageFromNative函数中处理请求或者回复,回复格式Message{responseId,responseData}

package pers.example.demo2;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.StrictMode;
import android.util.Log;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;

import androidx.appcompat.app.AppCompatActivity;

import com.github.lzyzsd.jsbridge.BridgeHandler;
import com.github.lzyzsd.jsbridge.BridgeWebView;
import com.github.lzyzsd.jsbridge.CallBackFunction;
import com.github.lzyzsd.jsbridge.DefaultHandler;
import com.google.gson.Gson;

import java.io.File;


public class MainActivity extends AppCompatActivity {
    private final String TAG = "MainActivity";
    BridgeWebView webView;
    int RESULT_CODE = 0;
    ValueCallback<Uri> mUploadMessage;
    static class Location {
        String address;
    }

    static class User {
        String name;
        Location location;
        String testStr;
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //在Android 4.0以上,网络连接不能放在主线程上
        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
        StrictMode.setThreadPolicy(policy);

        //1、配置更新
        String cacheDir = this.getCacheDir().getAbsolutePath();
        String cacheUpdatePath = cacheDir+"/www/update.json";//本地的缓存文件
        String remoteUpdatePath = "http://192.168.1.217/update.json";//每次更新的文件
        String remotezipDir = "http://192.168.1.217/";//压缩包zip所在的远程目录
        String cacheWWWPath = cacheDir+"/www";//本地的www目录
        File cacheWWWFile=new File(cacheWWWPath);
        //2、更新本地h5资源
        UtilsApi.updateH5Assets(cacheUpdatePath,remoteUpdatePath,remotezipDir,cacheWWWFile);

        setContentView(R.layout.activity_main);
        //3、设置webview
        webView = (BridgeWebView) findViewById(R.id.webView);
//声明WebSettings子类
        WebSettings webSettings = webView.getSettings();

        webSettings.setAllowFileAccess(true);//不然本地html打不开
        webSettings.setUseWideViewPort(true);
        webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);

        webSettings.setJavaScriptEnabled(true);//才能让Webivew支持<meta>标签的viewport属性
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

        webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式

        webView.setDefaultHandler(new DefaultHandler());

        //4、加载手机本地的html页面
        webView.loadUrl(cacheWWWPath+"/index.html");
        Log.i("tag",cacheWWWPath+"/index.html");
        webView.registerHandler("submitFromWeb", new BridgeHandler() {

            @Override
            public void handler(String data, CallBackFunction function) {
                Log.i(TAG, "handler = submitFromWeb, data from web = " + data);
                function.onCallBack("submitFromWeb exe, response data 中文 from Java");
            }

        });
        User user = new User();
        Location location = new Location();
        location.address = "SDU";
        user.location = location;
        user.name = "大头鬼";

        webView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {
            @Override
            public void onCallBack(String data) {

            }
        });

        webView.send("hello");

    }
}
View Code

 

 
posted @ 2023-03-13 00:33  巅峰蜗牛  阅读(267)  评论(0编辑  收藏  举报