android js 互相調用


代碼地址如下:
http://www.demodashi.com/demo/13107.html

android js 互相調用 第二版

  • 支持js匿名函數接收
  • 支持js json對象接收
  • 支持js函數返回值獲取
  • 通過注解注入js方法
  • 優化第一版的反射注入方式,采用注解處理器編譯時生成注入代碼,提高運行效率
  • 加入簡單的 webview 預加載功能

實現原理

  • 通過注解處理器實現js代碼自動生成
  • 創建WebViewChromeClient重寫 onProgress方法當進度大於30%的時候執行js代碼注入,js代碼必須注入成功才能調用

js代碼生成邏輯

/**
 * 注解處理器
 */
@AutoService(Processor.class) public class InjectProcessor extends AbstractProcessor {

  /**
   * 文件相關的輔助類
   */
  private Filer mFiler;
  /**
   * 元素相關的輔助類
   */
  private Elements mElementUtils;
  /**
   * 日志相關的輔助類
   */
  private Messager mMessager;

  //返回注解處理器可處理的注解操作
  //@Override public Set<String> getSupportedOptions() {
  //  return getSupportedAnnotationTypes();
  //}

  @Override public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.RELEASE_7;
  }

  //得到注解處理器可以支持的注解類型
  @Override public Set<String> getSupportedAnnotationTypes() {
    HashSet<String> objects = new HashSet<>();
    objects.add(JsInject.class.getName());
    return objects;
  }

  //執行一些初始化邏輯
  @Override public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    mFiler = processingEnv.getFiler();
    mElementUtils = processingEnv.getElementUtils();
    mMessager = processingEnv.getMessager();
  }

  //核心方法,掃描,解析並處理自定義注解,生成***.java文件
  @Override public boolean process(Set<? extends TypeElement> annotations,
      RoundEnvironment roundEnv) {
    try {
      processImpl(roundEnv);
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
    return false;
  }

  private void processImpl(RoundEnvironment roundEnv) throws ClassNotFoundException, IOException {
  // 獲取被 JsInject 標記的元素
    Set<? extends Element> elementsAnnotatedWith =
        roundEnv.getElementsAnnotatedWith(JsInject.class);
    Iterator<? extends Element> iterator = elementsAnnotatedWith.iterator();
	//創建模板代碼
    String objectJs = "if(typeof(window.%s)=='undefined'){ window.%s = {";
    String methodJs = "%s:function(){"
        + " return EasyJS.call('%s', '%s', Array.prototype.slice.call(arguments));},";
    final StringBuilder objectSb = new StringBuilder();
    Map<String, String> map = new HashMap<>();
    while (iterator.hasNext()) {
      Element next = iterator.next();
      JsInject jsInject = next.getAnnotation(JsInject.class);
      //String classType = next.asType().toString();
      //error(next.getEnclosingElement() + "-----" + next.getKind() + "-----" + next.getSimpleName()
      //        +"----------"+next.getEnclosedElements()+"------"+next.asType().toString()+"--------",
      //    next);
	  // 獲取 被JsInject 標記的 class ,並判斷 當前類是否繼承於 com.liwg.jsbridge.library.JsPlugin
      TypeElement typeElement = (TypeElement) next;
      if (!typeElement.getSuperclass().toString().equals("com.liwg.jsbridge.library.JsPlugin")) {
        error("cover JsInject note class must extends JsPlugin", next);
        return;
      }
      String objectName = jsInject.value();
      if (objectName == null || objectName.length() < 1) {
        objectName = typeElement.getSimpleName().toString();
      }
      map.put(objectName, String.format("new %s()", typeElement.getQualifiedName().toString()));
      objectSb.append(String.format(objectJs, objectName, objectName));
      final StringBuilder methodSb = new StringBuilder();
	  // 獲取方法列表
	  List<? extends Element> methodElements = next.getEnclosedElements();
      for (int i = 0; i < methodElements.size(); i++) {
        Element element = methodElements.get(i);
        if(!(element instanceof ExecutableElement))
          continue;
        ExecutableElement method = (ExecutableElement) element;
        String methodName = method.getSimpleName().toString();
        // 存在一個 <init>方法,過濾掉
        if (methodName.contains("<")) continue;
        if (!method.getModifiers().contains(Modifier.PUBLIC)) {
          //必須是public 修飾的方法
          continue;
        }
		// 過濾掉  JsInject 注解 聲明需要過濾的方法
        if (!filterMethod(jsInject.filter(), methodName)) {
          //不需要過濾此方法
          methodSb.append(String.format(methodJs, methodName, objectName, methodName));
        }
      }
      if (methodSb.length() > 0) methodSb.deleteCharAt(methodSb.length() - 1);
      objectSb.append(methodSb);
      objectSb.append("}}");
    }
    objectSb.append("if(window.EasyJS&&window.EasyJS.injectFlag==0){if(JSBridgeReady){JSBridgeReady();window.EasyJS.injectFlag=1}}");
    String jsCode = objectSb.toString();
	// 創建 java 源文件
    JavaFileObject sourceFile = mFiler.createSourceFile("com.liwg.jsbridge.library.JSBridge");
    Writer writer = null;
    try {
      writer = sourceFile.openWriter();
      writer.write("package com.liwg.jsbridge.library;\n\n");
      writer.write("final class JSBridge implements com.liwg.jsbridge.library.IJSBridge{\n");
      writer.write("    public static final JSBridge INSTANCE = new JSBridge();\n");
      writer.write("    public java.util.Map<String,Object> map = new java.util.HashMap<>();\n");
      writer.write("    private JSBridge(){\n");
      for (Map.Entry<String, String> entry : map.entrySet()) {
        writer.write(String.format("     map.put(\"%s\",%s); \n", entry.getKey(), entry.getValue()));
      }
      writer.write("    }\n");
      writer.write("    public static final JSBridge get(){\n");
      writer.write("      return INSTANCE;\n");
      writer.write("    }\n");
      writer.write("    public String getJsCode(){\n");
      writer.write("      return \"" + jsCode + "\";\n");
      writer.write("    }\n");
      writer.write(
          "    /**注冊對象, 被@JsPlugin注解標記的對象會自動注入,並調用空參的構造函數,\n  如果需要重寫構造,需保留空參的構造,並調用此方法注冊\n  */\n");
      writer.write("    public void register(String name,Object obj){\n");
      writer.write("      map.put(name,obj);\n");
      writer.write("    }\n");
      writer.write("    public Object queryJavaObject(String name){\n");
      writer.write("     return map.get(name);\n");
      writer.write("    }\n");
/*      writer.write("    public void callJsReady(com.liwg.jsbridge.library.BridgeWebView webview){\n");
      writer.write("     webview.callJsMethod(\"JSBridgeReady()\");\n");
      writer.write("    }\n");*/
      writer.write("  }\n");
    } catch (Exception e) {
      error(e.getLocalizedMessage(),roundEnv.getRootElements().iterator().next());
    } finally {
      writer.close();
    }
  }

  private boolean filterMethod(String[] filter, String methodName) {
    int length = filter == null ? 0 : filter.length;
    for (int i = 0; i < length; i++) {
      if (filter[i].equals(methodName)) {
        return true;
      }
    }
    return false;
  }

  void error(CharSequence msg, Element element) {
    mMessager.printMessage(Diagnostic.Kind.WARNING, msg, element);
  }
}

生成的代碼

final class JSBridge implements com.liwg.jsbridge.library.IJSBridge{
    public static final JSBridge INSTANCE = new JSBridge();
    public java.util.Map<String,Object> map = new java.util.HashMap<>();
    private JSBridge(){
	//生成 注入 js對象名和java對象的映射
     map.put("AB",new com.src.wugang.jsbridge.MainActivity.AB()); 
     map.put("Plugin",new com.src.wugang.jsbridge.A()); 
    }
    public static final JSBridge get(){
      return INSTANCE;
    }
    public String getJsCode(){
      return "if(typeof(window.Plugin)=='undefined'){ window.Plugin = {test:function(){ return EasyJS.call('Plugin', 'test', Array.prototype.slice.call(arguments));},test1:function(){ return EasyJS.call('Plugin', 'test1', Array.prototype.slice.call(arguments));}}}if(typeof(window.AB)=='undefined'){ window.AB = {test:function(){ return EasyJS.call('AB', 'test', Array.prototype.slice.call(arguments));},test1:function(){ return EasyJS.call('AB', 'test1', Array.prototype.slice.call(arguments));},test2:function(){ return EasyJS.call('AB', 'test2', Array.prototype.slice.call(arguments));}}}if(window.EasyJS&&window.EasyJS.injectFlag==0){if(JSBridgeReady){JSBridgeReady();window.EasyJS.injectFlag=1}}";
    }
    /**注冊對象, 被@JsPlugin注解標記的對象會自動注入,並調用空參的構造函數,
  如果需要重寫構造,需保留空參的構造,並調用此方法注冊
  */
    public void register(String name,Object obj){
      map.put(name,obj);
    }
    public Object queryJavaObject(String name){
     return map.get(name);
    }
  }

使用方式

	<com.wugang.jsbridge.library.BridgeWebView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/web_view"/>

Activity

  • 注入的插件對象必須實現JsPlugin接口,所有需要注入的對象必須繼承JsPlugin這個類並且 加上 @JsInject 注解標記
  • 被 @JsInject 標記的類會被自動注入,並調用空參的構造創建對象,如果有自定義構造 可以使用 webView.getJsBridge().register()
  • 如果該類中的方法不希望被注入可以 使用 @JsInject 注解上的 filter參數過濾掉
    public class MainActivity extends AppCompatActivity {

       @Override protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
          PreLoadManager.get(this).preload("http://www.baidu.com", "http://www.youku.com");
          BridgeWebView webView = (BridgeWebView) findViewById(R.id.web_view);
          webView.getJsBridge().register("AB",new AB(this));
          webView.loadUrl("file:///android_asset/test.html");
          WebView.setWebContentsDebuggingEnabled(true);
       }

      @JsInject public static class AB extends JsPlugin {
          private Context context;

          public AB() {
          }

          public AB(Context context) {
            this.context = context;
          }

          public void test(String s, JSFunction jsFunction) {
            Toast.makeText(context, "js調用我", 1).show();
            jsFunction.execute("test execute   " + s);
          }

          public void test1() {
            Log.e("-------", "test1: ");
          }

          public void test2() {
            Log.e("-------", "test2: ");
          }
      }
    }

HTML&JS代碼

	<html>
	<script>
		  // 推薦使用方式,否則直接調用將無法調用到 原生方法
		  window.JSBridgeReady=function(){
			  console.log("---window EasyJSReady---")
			  AB.test2();
			  AB.test("call test",function(ret){
				  console.log(ret)
			  })
		  }
	</script>
	<script src="test.js"></script>

	<body>
	  <button onclick="javascript:location.reload()">refresh</button>
	</body>
	</html>

網頁預加載

   //預加載,推薦在Application中調用
   PreLoadManager.get(this).preload("http://www.baidu.com", "http://www.youku.com");
Activity 中使用
public class PreLoadActivity extends AppCompatActivity {
  @Override protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    BridgeWebView webView = PreLoadManager.get(this).getWebView();
    setContentView(webView);
    webView.setWebViewClient(new WebViewClient(){
      @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
        if(request.hasGesture()){
          Intent intent = new Intent(PreLoadActivity.this,PreLoadActivity.class);
          intent.putExtra("url",request.getUrl());
          startActivity(intent);
          return true;
        }
        return super.shouldOverrideUrlLoading(view, request);
      }
    });
    webView.loadUrl(getIntent().getStringExtra("url"));
  }
}

項目結構圖

參考項目https://github.com/lwugang/safe-java-js-webview-bridge

參考項目https://github.com/dukeland/EasyJSWebView

android js 互相調用

代碼地址如下:
http://www.demodashi.com/demo/13107.html

注:本文著作權歸作者,由demo大師代發,拒絕轉載,轉載需要作者授權


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM