Android 进阶之旅

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

2020-11-20  本文已影响0人  Tsm_2020

现在主流的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;
       }
   }

}



上一篇下一篇

猜你喜欢

热点阅读