天天看點

HybridApp 概念與自定義JsBridge 架構

前言:

HybridApp在過去的兩年中已經成為移動界的核心話題,但是作為一名Web開發者來說要如何站在移動網際網路的浪潮之巅呢?是選擇學習原生開發,研究Java、Object-C、C#等語言,還是選擇繼續使用網頁開發,容忍HTML5功能的局限性?就在開發者左右為難的情況下HybridApp作為一個折中的解決方案誕生了

概念:

HybridApp是同時采用網頁語言與程式語言進行開發,通過不同的應用商店進行打包與分發,應用的特性更接近原生應用而且又差別與Web應用。但是在開發過程中同時使用了網頁語言,是以開發成本與難度大大降低。也就是說HybridApp兼具了NativeApp與WebApp兩者的諸多優點。 對比:

   Web App(網頁應用) Hybrid App(混合應用) Native App(原生應用)
開發成本
維護更新 簡單 簡單 複雜
體驗
Store或market認可 不認可 認可 認可
安裝 不需要 需要 需要
跨平台
APK size 輕量

核心思路:

Hybrid App 融合 Web App的原理就是引入一個WebView元件,可以在這個元件中載入HTML頁面做UI呈現(部分的),通過JsBridge 實作業務層與界面層的資料通訊、邏輯調用。

技術實作:

自定義JsBridgeWebView 繼承自android.webkit.WebView 并内部定義一個BridgeHandler接口

@SuppressLint("SetJavaScriptEnabled")
public class JsBridgeWebView extends WebView {
    private List<String> mListName = new ArrayList<String>();

    private class BridgeHandlerHolder implements BridgeHandler {
        private BridgeHandler mBridgeHandler;

        public BridgeHandlerHolder(BridgeHandler in) {
            mBridgeHandler = in;
        }

        @Override
        @JavascriptInterface
        public String handler(String data0, String data1, String o) {
            return null != mBridgeHandler ? mBridgeHandler.handler(data0, data1, o) : null;
        }

    }

    public interface BridgeHandler {
        public String handler(String data0, String data1, String o);
    }


    public void callback(Object... args) {
        if (args.length > 0) {
            String jsFunctionName = (String) args[0];
            StringBuilder jsFunctionParam = new StringBuilder();
            if (!TextUtils.isEmpty(jsFunctionName)) {
                for (int i = 1; i < args.length; i++) {
                    if (i == 1) {
                        jsFunctionParam.append(String.format("'%s'", args[i]));
                    } else if (i > 1) {
                        jsFunctionParam.append(String.format(",'%s'", args[i]));
                    }
                }
                String js = String.format("javascript:%s(%s)", jsFunctionName, jsFunctionParam);
                this.loadUrl(js);
            }

        }
    }

    public JsBridgeWebView(Context context) {
        super(context);
        initSetting();
    }

    public JsBridgeWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initSetting();
    }

    public JsBridgeWebView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        initSetting();
    }

    public void initSetting() {
        WebSettings webSettings = this.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webSettings.setSupportZoom(true);
    }

    public void unRegisterHandlerAll() {
        if (null != mListName) {
            for (int i = 0; i < mListName.size(); i++) {
                this.removeJavascriptInterface(mListName.get(i));
            }
            mListName.clear();
        }
    }

    public void unRegisterHandler(String name) {
        if (null == mListName) {
            mListName = new ArrayList<String>();
        }
        if (null != mListName && mListName.contains(name)) {
            mListName.remove(name);
            this.removeJavascriptInterface(name);
        }
    }

    public boolean registerHandler(String name, BridgeHandler bridgeHandler) {
        boolean bReturn = true;
        if (null == mListName) {
            mListName = new ArrayList<String>();
        }
        if (null != mListName && mListName.contains(name)) {
            bReturn = false;
        }
        if (bReturn) {
            this.addJavascriptInterface(new BridgeHandlerHolder(bridgeHandler), name);
            if (null != mListName) {
                mListName.add(name);
            }
        }
        return bReturn;
    }
}      
Layout XML 使用:      
<RelativeLayout>      
<com.example.qinghua_liu.myapplication.customview.JsBridgeWebView
        android:id="@+id/JsBridgeWebView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>      
Activity Code:      
public class ActivityMain4Activity extends Activity {

    private RelativeLayout mainLayout;
    private JsBridgeWebView jsBridgeWebView;
    private Context mContext;
    private WebViewHandler mWebViewHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main4);
        mContext = this;
        jsBridgeWebView = (JsBridgeWebView) findViewById(R.id.JsBridgeWebView);
        mWebViewHandler = new WebViewHandler(jsBridgeWebView);
        //jsBridgeWebView.initSetting();
        mainLayout = (RelativeLayout) findViewById(R.id.main_layout);
        jsBridgeWebView.registerHandler("Register", new JsBridgeWebView.BridgeHandler() {
            @Override
            public String handler(String data0, String data1, String o) {
                Toast toast = Toast.makeText(mContext, String.format("register data. Name:%s ,Psw:%s", data0, data1),
                        Toast.LENGTH_SHORT);
                toast.show();
                final Object jscallbackFuncName = o;
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = mWebViewHandler.obtainMessage();
                        message.obj = jscallbackFuncName;
                        mWebViewHandler.sendMessageDelayed(message, 3000);
                    }
                }).start();
                return "";
            }
        });

        jsBridgeWebView.loadData("", "text/html", null);
        jsBridgeWebView.loadUrl("file:///android_asset/www/register.html");

        jsBridgeWebView.setWebViewClient(new WebViewClient() {

        });
    }

    private static class WebViewHandler extends Handler {
        WeakReference<JsBridgeWebView> mJsBridgeWebView;

        public WebViewHandler(JsBridgeWebView view) {
            mJsBridgeWebView = new WeakReference<>(view);
        }

        public void handleMessage(Message msg) {
            final JsBridgeWebView webview = mJsBridgeWebView.get();
            if (null != webview) {
                String jscallbackFuncName = msg.obj.toString();
                webview.callback(jscallbackFuncName, "1001");
            }
            //handle you message here!
        }

    }

}      
register.html:      
<!DOCTYPE html>
<html>
<head>
<base target="_blank"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
   *{margin:0;padding: 0;}

   .lanren{width: 221px;border: solid #E5E5E5; border-width: 1px 0 0 1px;margin:50px auto;}
   .lanren ul{overflow: hidden;}
   .lanren ul li{float:left;width:200px;height:100px;padding:15px 10px 10px 10px;border-right:1px solid #EDEDED;border-bottom:1px solid #EDEDED;
   overflow: hidden;position: relative;list-style: none;}
   .lanren ul li img{float: left;margin-right:10px;}
   .lanren ul li p{text-align: left;color: #666;font-size: 12px;margin-bottom: 5px;line-height: 140%;max-height: 32px;overflow: hidden;}
   .lanren ul li b{color: #e12228;font-size: 16px;font-weight: 700;}
   </style>
</head> 
<body>

<br/>
<div style="margin-left:15px;">
使用者名:<br/>
<input type="text" id ="namec"/>
<br/>
<br/>
密碼:
<br/>
<input type="password" id ="psw"/>
<br/><br/><br/>
<input type="button" id="play" value="注冊" οnclick="callAndroidRe();">
   <br/><br/><div  id="res">

</div><br/><br/>廣告推廣</div>


<div class="lanren">
   </div>
   <script src="jquery.min.js"></script>
   <script>
   $(function(){
      $('.lanren li').hover(function(){
         $(this).find('img').stop().animate({
            'margin-left':'-7px',
            'margin-right':'17px'
         })
      },function(){
         $(this).find('img').stop().animate({
            'margin-left':'0',
            'margin-right':'10px'
         })
      })
   })
   </script>
   <!--代碼部分end-->
   
</body>
</html>
<script language="JavaScript" type="text/javascript">
var res = document.getElementById('res');
var namec = document.getElementById('namec');
var psw = document.getElementById('psw');
function jsCallback (userid){
   res.innerHTML= '注冊成功! 您的ID是:'+userid;
}


var Person = function (userid) {
 this.userid = userid;
}.method('getName', function () {
   res.innerHTML= '注冊成功! 您的ID是:'+this.userid;
 }).method('setName', function (userid) {
   this.userid = userid;
     return this;
  });

function callAndroidRe(){
//alert('qh');
res.innerHTML= '注冊中';
    var id = window.Register.handler(namec.value,psw.value,'jsCallback');
    //res.innerHTML= '注冊成功! 您的ID是:'+id;
}


</script>      
總結:      
兩個注意點:      
1.如果你的程式目标平台是17或者是更高,你必須要在暴露給網頁可調用的方法(這個方法必須是公開的)加上      
@JavascriptInterface注釋。如果你不這樣做的話,在4.2以以後的平台上,網頁無法通路到你的方法。      
2. webView 的方法調用必須要在同一線程(例子中是主線程)是以用WorkThread 消息給主線程的Handler 進行
    
    
     webView 的方法調用。Handler 使用過程中注意記憶體洩露的問題。
    
    
     

    延伸:      
上面例子也可認為是個人實作的一個JSbridge 的微型架構。回到Hybrid APP架構話題上來,都說混合開發把UI的開發
交給H5,業務部分給移動開發去做,曾經有牛人問我一個這樣的問題,說有沒有考慮過,讓移動開發去做UI(流暢),而把      
業務邏輯交給html(ajax)去做,仔細想想,雖然技術上也可實作,但是現實意義在哪裡,我還真沒想透。如果您有見解,歡迎留言。      

繼續閱讀