美文网首页Android 进阶之旅
Android 进阶学习(十) App内嵌的H5打开目标页面功能

Android 进阶学习(十) App内嵌的H5打开目标页面功能

作者: Tsm_2020 | 来源:发表于2020-11-20 17:36 被阅读0次

    现在主流的App基本上都会用H5,但是由于业务发展App肯定会出现H5打开app的目标页,或者调用app的方法,我们App最开始使用的是使用WebViewClient的shouldOverrideUrlLoading 这个方法,拦截h5需要跳转的url,然后根据url里面的信息跳转,这样做虽然能实现我所说的功能,但是非常不友好,每次需要都要h5新增跳转的url,然后app再判断这个url做跳转,只要功能增加,两端都要做代码修改,还不能兼容前面的版本,在探索的过程中我将原来拦截url的方法替换为JS调java的形式,修改之后每次h5需要打开app的页面都需要调用我们定义的方法,

       /**
        *
        * @param action 动作
        * @param extra  extra1 参数
        */
       @JavascriptInterface
       public void JsBridgeCall(String action,String extra,String extra1){
           if(callBack!=null){
               if(mPostHandler!=null){
                   mPostHandler.post(() -> callBack.onJsBridgeCall(action,extra,extra1));
               }
           }
       }
    

    注意这里我使用Handler post 了一下,返回来的时候在子线程,
    可以看到我们这么做之后的可以接收到h5传给我们的三个参数,虽然这个操作的写法相对于拦截url那种方式看起来高大上一些,但是并没有解决实际问题,那就是如何让h5打开app的任意页面呢,而且不同的页面可能还有不同的参数,参数的类型也多种多样,想来想去可以利用ClassLoader来解决这个问题,说一下解决这个问题的思路,

    JsBridgeCall(String action,String extra,String extra1)

    可以看到我定义这个方法有三个参数,第一个参数是action ,顾名思义就是动作的意思,比如 goback h5向后退一步,close 关闭当前页面,openPage 打开目标页
    第二个参数就是extra ,这个就是打开目标页的参数, 具体怎么打开我们在下面仔细分析,第三个参数extra1 作为额外参数的意思就是在执行完action 后,是否还伴随这个其他额外的动作,比如后退和关闭,这里我举一个简单的例子,可以看到在执行完openPage后,如果第三个参数不为空,那么将第三个参数作为第一个参数再执行一个遍这个方法

       @Override
       public void onJsBridgeCall(String action, String extra,String extra1) {
           switch (action){
               case "goBack":
                   onBackPressed();
                   break;
               case "close":
                   finish();
                   break;
               case "openPage":
                   try {
                       if(TextUtils.isEmpty(extra))
                           return;
                       WebInfoMode infoMode=JsonUtils.getTFromStr(extra,WebInfoMode.class);
                       infoMode.goIntent(this);
                       if(!TextUtils.isEmpty(extra1)){
                           onJsBridgeCall(extra1,"","");
                       }
                   }catch (Exception e){
    
                   }
                   break;
               case "hideTitle":
                   break;
           }
       }
    
     ///H5 后退一步,这样做会过滤掉重定向
       @Override
       public void onBackPressed() {
           if (null == webview_info) {finish();return;}
           WebBackForwardList webBackForwardList = webview_info.copyBackForwardList();
           if (null == webBackForwardList) {finish();return;}
           WebHistoryItem currentItem = webBackForwardList.getCurrentItem();
           int currentIndex = webBackForwardList.getCurrentIndex();
           if (null == currentItem) {
               finish();
           } else if (currentIndex == 0) {
              finish();
           } else {
               String url = currentItem.getUrl();
               if(!TextUtils.isEmpty(url) && url.contains("transfer=webviewCloseH5")){
                   finish();
               }
               if (webview_info.canGoBack()) {
                   webview_info.goBack();
               } else {
                   webview_info.goBackOrForward(-1);
               }
           }
       }
    

    下面我们来聊一聊具体如何通过ClassLoader来打开目标页,

    我们知道Context 类在创建的时候会加载一个ClassLoader,通过这个ClassLoader,我们只需要知道包名+类名就可以得到这个class,

    内部类是使用 $ 符号修饰的,避免踩坑,

    这个class就是我们在创建Intent的时候的目标页,代码如下

       public void  goIntent(Context context){
           Intent intent=new Intent();
           String packageName="你的包名";
           ClassLoader classLoader=context.getClassLoader();
           try {
               Class<?> intentCls = classLoader.loadClass(packageName+getIntentString());
               intent.setClass(context,intentCls);
               context.startActivity(intent);
           } catch (ClassNotFoundException e) {
               //TODO 提示更新最新版本,连这个类都没找到,相关功能肯定还没有
           }
       }
    

    在打开的页面没有参数的时候,我们只需要知道一个类的全路径+类名就可以了,比如我们的包名是 "com.tsm.myapplication",而我们目标页的路径是 com.tsm.myapplication.find.SecondActivtiy, 那么我们利用classLoader.loadClass(packageName+"find.SecondActivtiy"),,这样就解决了目标页的问题,我们再分析一下如何传递参数的问题,我现在的做法是兼容大部分参数 int string list map Serializable Parcelable,
    这种写法不能兼容所有的问题,但是绝对能解决大部分问题,而且再后面的修改的过程中还不用动前台代码,可以向前兼容,
    先来说一下我为参数的设计的类

       public static class WebIntentModel{
           /**
            *   intent 中 putExtra( kay,value) 的value ,我们将它定义为String ,根据不同的类型将这个String 解析成想要的参数
            */
           private String params_info;
           /**
            *   intent 中 putExtra( kay,value) 的key 这个肯定是String
            */
           private String key;
           /**
            * 为了区分 参数的类型  int  String  Map  List  Serializable   Parcelable
            */
           private String modelType;
           /**
            * 如果定义的是 List  Serializable  或者 Parcelable   我么就需要知道 是哪个类
            */
           private String modelCls;
       }
    

    通过这些参数,还有classLoader ,我们就可以将intent的参数拼接起来了,h5传递给我们打开目标也的格式就是


    image.png

    这个就是我们定义的第二个参数,也就是extra的 所有定义,
    下面我们贴一下所有的代码

    public  class WebInfoMode {
    
       private String intentString;
       private List<WebIntentModel> listMode;
    
       public String getIntentString() {
           return intentString;
       }
    
       public void setIntentString(String intentString) {
           this.intentString = intentString;
       }
    
       public List<WebIntentModel> getListMode() {
           return listMode;
       }
    
       public void setListMode(List<WebIntentModel> listMode) {
           this.listMode = listMode;
       }
    
    
    
       public void  goIntent(Context context){
           Intent intent=new Intent();
       String packageName="你的包名";
           ClassLoader classLoader=context.getClassLoader();
           try {
               Class<?> intentCls = classLoader.loadClass(packageName+getIntentString());
               intent.setClass(context,intentCls);
               if(listMode!=null&&listMode.size()>0){
                   for (int i=0;i<listMode.size();i++){
                       intent=listMode.get(i).initIntent(intent,classLoader,packageName);
                   }
               }
               context.startActivity(intent);
           } catch (ClassNotFoundException e) {
               //TODO 提示升级   找不到类就需要升级了
               e.printStackTrace();
           }
       }
    
       public static class WebIntentModel{
           /**
            *   intent 中 putExtra( kay,value) 的value ,我们将它定义为String ,根据不同的类型将这个String 解析成想要的参数
            */
           private String params_info;
           /**
            *   intent 中 putExtra( kay,value) 的key 这个肯定是String
            */
           private String key;
           /**
            * 为了区分 参数的类型  int  String  Map  List  Serializable   Parcelable
            */
           private String modelType;
           /**
            * 如果定义的是  Serializable  或者 Parcelable 我么就需要知道 是哪个类
            */
           private String modelCls;
    
    
           public String getModelType() {
               return modelType;
           }
    
           public void setModelType(String modelType) {
               this.modelType = modelType;
           }
    
           public String getParams_info() {
               return params_info;
           }
    
           public void setParams_info(String params_info) {
               this.params_info = params_info;
           }
    
           public String getKey() {
               return key;
           }
    
           public void setKey(String key) {
               this.key = key;
           }
    
           public String getModelCls() {
               return modelCls;
           }
    
           public void setModelCls(String modelCls) {
               this.modelCls = modelCls;
           }
    
           public Intent initIntent(Intent intent,ClassLoader classLoader,String packageName){
               if(TextUtils.isEmpty(modelType)){
                   return intent;
               }
               try {
                   switch (modelType.toLowerCase()){
                       case "str":
                           intent.putExtra(key,params_info);
                           break;
                       case "int":
                           intent.putExtra(key, TextUtils.parseInt(params_info));
                           break;
                       case "bool":
                           intent.putExtra(key,Boolean.parseBoolean(params_info));
                           break;
                       case "map":
                           intent.putExtra(key, (Serializable) JsonUtils.getStrMap(params_info));
                           break;
                       case "ser":
                           Class<?> cls = classLoader.loadClass(packageName+modelCls);
                           intent.putExtra(key,(Serializable) JsonUtils.getTFromStr(params_info,cls));
                           break;
                       case "par":
                           Class<?> cls1 = classLoader.loadClass(packageName+modelCls);
                           intent.putExtra(key,(Parcelable) JsonUtils.getTFromStr(params_info,cls1));
                           break;
                       case "list":
                           if(!TextUtils.isEmpty(listType)){
                               switch (listType){
                                   case "str":
                                       intent.putExtra(key,JsonUtils.getTsFromStr(params_info,String.class));
                                       break;
                                   case "int":
                                       intent.putExtra(key, JsonUtils.getTsFromStr(params_info,Integer.class));
                                       break;
                                   case "map":
                                       intent.putExtra(key, (Serializable) JsonUtils.getList(params_info));
                                       break;
                                   case "ser":
                                       Class<?> cls2 = classLoader.loadClass(packageName+modelCls);
                                       intent.putExtra(key,(Serializable) JsonUtils.getTsFromStr(params_info,cls2));
                                       break;
                                   case "par":
                                       Class<?> cls3 = classLoader.loadClass(packageName+modelCls);
                                       intent.putExtra(key,(Parcelable) JsonUtils.getTsFromStr(params_info,cls3));
                                       break;
                               }
                           }
                           break;
                   }
               }catch (ClassNotFoundException e){
                   //TODO 能找到目标页,但是找不到参数,证明参数类型有问题,
                   e.printStackTrace();
               }catch (Exception e){
                   e.printStackTrace();
               }
               return intent;
           }
       }
    }
    

    这里有一个地方比较坑,那就是params_info 如果作为Serializable或者Parcelable 的时候,内部的字符串前面有三个转义字符即 \"name\" 这种形式,原因是他被字符串处理了2次,
    贴一下我写的时候的案例

    
       ///这个是给H5  openPage 的时候的第二个参数
           String extra=    "{\"intentString\":\"tsm.activity.TsmActivity\"," +
                   "\"listMode\":[{\"key\":\"bean\",\"modelCls\":\"model.tsm.Model\",\"modelType\":\"ser\"," +
                   "\"params_info\":\"{\\\"airlineCode\\\":\\\"CA\\\",\\\"arrivalName\\\":\\\"DLC\\\",\\\"arriveTer\\\":\\\"--\\\",\\\"certId\\\":\\\"DHDHDHDH\\\",\\\"certType\\\":\\\"P\\\",\\\"copunStatus\\\":\\\"O\\\",\\\"couponNumber\\\":\\\"1\\\",\\\"departDate\\\":\\\"2020-11-21\\\",\\\"departName\\\":\\\"PEK\\\",\\\"departTer\\\":\\\"3\\\",\\\"flightNumber\\\":\\\"8908\\\",\\\"gate\\\":\\\"--\\\",\\\"pushFlag\\\":\\\"0\\\",\\\"seatNo\\\":\\\"--\\\",\\\"ticketNumber\\\":\\\"9992155241078\\\",\\\"userLabel\\\":\\\"0\\\",\\\"userLevel\\\":\\\"Silver\\\",\\\"version\\\":\\\"6\\\"}\"" +
                   "}]}";
    

    发现如果直接我给h5小姐姐字符串让她弄的话比较麻烦,每次还要我给她生成这个字符串,转义字符弄得比较麻烦,烦不胜烦,经过研究后直接使用JS 创建model,附上小姐姐的例子

               var model = new Object();
               model.intentString  = 'tsm.activity.TsmAcitivity';
               var innrList=new Array();
               var model1 = new Object();
               model1.type = 1;
               model1.org = 'PEK';
               model1.dst = 'SHA';
           
               var innr=new Object();
               innr.params_info = JSON.stringify(model1);
               innr.key='bean';
               innr.modelType='ser';
               innr.modelCls='model.TsmModel$TsmsBean';
               innrList.push(innr); //
               
               model.listMode = innrList;
               console.log(JSON.stringify(model));
               window.__native__.AirJsBridgeCall("openPage",JSON.stringify(model),"close");
    

    注意内部类的修饰字符 $

    可能有人还没有json的工具类,我也贴一下

    public class JsonUtils {
    
       private static Gson mGson = new GsonBuilder().registerTypeAdapterFactory(new GsonAdapterUtils.TypeAdapterFactory()).create();
    
       /**获取json字符串*/
       public static String getJsonStr(List<Map<String, Object>> list) {
           return mGson.toJson(list);
       }
    
       public static List<String> getJsonStr(String json) {
           return mGson.fromJson(json, new TypeToken<List<String>>() {}.getType());
       }
    
       public static String toJson(Object list) {
           return mGson.toJson(list);
       }
    
       /** 返回List */
       public static List<Map<String, Object>> getList(String jsonStr) {
           return mGson.fromJson(jsonStr, new TypeToken<List<Map<String, Object>>>() {}.getType());
       }
    
       public static List<List<Map<String, Object>>> getListWithInList(String jsonStr) {
           return mGson.fromJson(jsonStr, new TypeToken<List<List<Map<String, Object>>>>() {}.getType());
       }
    
       public static <T>  List<List<T>> getListWithInList(String jsonStr, Class<T> c) {
           List<List<T>> lst = new ArrayList<>();
           JsonArray array = new JsonParser().parse(jsonStr).getAsJsonArray();
           for(JsonElement elem : array){
               lst.add(getTsFromStr(elem.toString(),c));
           }
           return lst;
       }
    
    
       public static List<Map<String, String>> getStrList(String jsonStr) {
           return mGson.fromJson(jsonStr, new TypeToken<List<Map<String, String>>>() {}.getType());
       }
    
       /**返回List*/
       public static List<String> getStringList(String jsonStr) {
           return mGson.fromJson(jsonStr, new TypeToken<List<String>>() {}.getType());
       }
    
       /**返回Map */
       public static Map<String, Object> getMap(String jsonStr) {
           return mGson.fromJson(jsonStr, new TypeToken<Map<String, Object>>() {}.getType());
       }
       /**返回Map */
       public static Map<String, String> getStrMap(String jsonStr) {
           return mGson.fromJson(jsonStr, new TypeToken<Map<String, Object>>() {}.getType());
       }
       /**返回实体类*/
       public static <T> T fromJson(String json, Class<T> classOfT) {
           return mGson.fromJson(json, classOfT);
       }
       /**返回实体类*/
       public static <T> T fromJson(Reader json, Class<T> classOfT) {
           return mGson.fromJson(json, classOfT);
       }
       /**返回实体类*/
       public static <T> T fromJson(String json, Type type) {
           if (TextUtils.isEmpty(json)) {
               return null;
           }
           return mGson.fromJson(json, type);
       }
       /** 获取字符串from map*/
       public static String getJsonStrFromMap(Map<String, Object> map) {
           try {
               String res = mGson.toJson(map);
               return res;
           } catch (Exception e) {}
           return "";
       }
    
    
       /**字符串转实例*/
       public static <T> T getTFromStr(String json, Class<T> cls){
           return mGson.fromJson(json, cls);
       }
    
       /**字符串转list*/
       public static <T> ArrayList<T> getTsFromStr(String json, Class<T> clazz){
           ArrayList<T> lst = new ArrayList<>();
           if (!TextUtils.isEmpty(json)) {
               JsonArray array = new JsonParser().parse(json).getAsJsonArray();
               for (final JsonElement elem : array) {
                   lst.add(mGson.fromJson(elem, clazz));
               }
           }
           return lst;
       }
       /**字符串转list*/
       public static <T> LinkedList<T> getLListFromStr(String json, Class<T> clazz){
           LinkedList<T> lst = new LinkedList<>();
           if (!TextUtils.isEmpty(json)) {
               JsonArray array = new JsonParser().parse(json).getAsJsonArray();
               for (final JsonElement elem : array) {
                   lst.add(mGson.fromJson(elem, clazz));
               }
           }
           return lst;
       }
    }
    
    public class GsonAdapterUtils {
    
       public static class TypeAdapterFactory implements com.google.gson.TypeAdapterFactory {
           @SuppressWarnings("unchecked")
           public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
               Class<T> rawType = (Class<T>) type.getRawType();
               if (rawType == Float.class || rawType == float.class) {
                   return (TypeAdapter<T>) new FloatTypeAdapter();
               } else if (rawType == Integer.class || rawType == int.class) {
                   return (TypeAdapter<T>) new IntegerTypeAdapter();
               } else if (rawType == Long.class || rawType == long.class) {
                   return (TypeAdapter<T>) new LongTypeAdapter();
               } else if (rawType == Double.class || rawType == double.class) {
                   return (TypeAdapter<T>) new DoubleTypeAdapter();
               }
               return null;
           }
       }
    
       public static class LongTypeAdapter extends TypeAdapter<Long> {
           @Override
           public void write(JsonWriter out, Long value) {
               try {
                   if (value == null) {
                       value = 0L;
                   }
                   out.value(value);
               } catch (Exception e) {
                   e.printStackTrace();
               }
           }
    
           @Override
           public Long read(JsonReader in) {
               try {
                   Long value;
                   if (in.peek() == JsonToken.NULL) {
                       in.nextNull();
                       Log.e("TypeAdapter", "null is not a number");
                       return 0L;
                   }
                   if (in.peek() == JsonToken.BOOLEAN) {
                       boolean b = in.nextBoolean();
                       Log.e("TypeAdapter", b + " is not a number");
                       return 0L;
                   }
                   if (in.peek() == JsonToken.STRING) {
                       String str = in.nextString();
                       return Long.parseLong(str);
                   } else {
                       value = in.nextLong();
                       return value;
                   }
               } catch (Exception e) {
                   Log.e("TypeAdapter", "Not a number", e);
               }
               return 0L;
           }
       }
    
       public static class IntegerTypeAdapter extends TypeAdapter<Integer> {
           @Override
           public void write(JsonWriter out, Integer value) {
               try {
                   if (value == null) {
                       value = 0;
                   }
                   out.value(value);
               } catch (Exception e) {
                   e.printStackTrace();
               }
           }
    
           @Override
           public Integer read(JsonReader in) {
               try {
                   Integer value;
                   if (in.peek() == JsonToken.NULL) {
                       in.nextNull();
                       Log.e("TypeAdapter", "null is not a number");
                       return 0;
                   }
                   if (in.peek() == JsonToken.BOOLEAN) {
                       boolean b = in.nextBoolean();
                       Log.e("TypeAdapter", b + " is not a number");
                       return 0;
                   }
                   if (in.peek() == JsonToken.STRING) {
                       String str = in.nextString();
                       return Integer.parseInt(str);
                   } else {
                       value = in.nextInt();
                       return value;
                   }
               } catch (Exception e) {
                   //Log.e("TypeAdapter", "Not a number", e);
               }
               return 0;
           }
       }
    
       public static class DoubleTypeAdapter extends TypeAdapter<Double> {
           @Override
           public void write(JsonWriter out, Double value) {
               try {
                   if (value == null) {
                       value = 0D;
                   }
                   out.value(value);
               } catch (Exception e) {
                   e.printStackTrace();
               }
           }
    
           @Override
           public Double read(JsonReader in) {
               try {
                   if (in.peek() == JsonToken.NULL) {
                       in.nextNull();
                       Log.e("TypeAdapter", "null is not a number");
                       return 0D;
                   }
                   if (in.peek() == JsonToken.BOOLEAN) {
                       boolean b = in.nextBoolean();
                       Log.e("TypeAdapter", b + " is not a number");
                       return 0D;
                   }
                   if (in.peek() == JsonToken.STRING) {
                       String str = in.nextString();
                       return Double.parseDouble(str);
                   } else {
                       Double value = in.nextDouble();
                       return value == null ? 0D : value;
                   }
               } catch (NumberFormatException e) {
                   Log.e("TypeAdapter", e.getMessage(), e);
               } catch (Exception e) {
                   Log.e("TypeAdapter", e.getMessage(), e);
               }
               return 0D;
           }
       }
    
       public static class FloatTypeAdapter extends TypeAdapter<Float> {
           @Override
           public void write(JsonWriter out, Float value) {
               try {
                   if (value == null) {
                       value = 0F;
                   }
                   out.value(value.toString());
               } catch (Exception e) {
                   e.printStackTrace();
               }
           }
    
           @Override
           public Float read(JsonReader in) {
               try {
                   Float value;
                   if (in.peek() == JsonToken.NULL) {
                       in.nextNull();
                       Log.e("TypeAdapter", "null is not a number");
                       return 0F;
                   }
                   if (in.peek() == JsonToken.BOOLEAN) {
                       boolean b = in.nextBoolean();
                       Log.e("TypeAdapter", b + " is not a number");
                       return 0F;
                   }
                   if (in.peek() == JsonToken.STRING) {
                       String str = in.nextString();
                       return Float.parseFloat(str);
                   } else {
                       String str = in.nextString();
                       value = Float.valueOf(str);
                   }
                   return value;
               } catch (Exception e) {
                   Log.e("TypeAdapter", "Not a number", e);
               }
               return 0F;
           }
       }
    
    }
    
    
    
    

    相关文章

      网友评论

        本文标题:Android 进阶学习(十) App内嵌的H5打开目标页面功能

        本文链接:https://www.haomeiwen.com/subject/skadiktx.html