HyBrid APP簡單入門之JS與Java互調以及JSBridge的實現

前言

HyBrid俗稱混合開發(fā)敞峭。使用Android提供的組件——WebView背桐,去加載放在本地或者服務器用h5編寫的UI界面系宜;js與java之間的互調骑丸,使得HyBrid APP的體驗更加趨向原生APP。本章內容主要講述在h5頁面調用Java方法恭取、在Android里調用h5里的js方法和jsbridge的簡單實現泰偿。因此,擁有h5蜈垮、css和js更容易上手甜奄。

三種APP開發(fā)方式比較

三種app比較.png

安全漏洞

在Android 4.2以下的WebView有個安全漏洞,外部網頁通過得到Runtime對象窃款,然后執(zhí)行系統(tǒng)命令得到信息,原因出在addJavascriptInterface()方法牍氛。下面是漏洞的簡單描述:
1晨继、向WebView注冊了一個叫“InterfaceName”的對象
2、js中可以訪問到“InterfaceName”對象
3搬俊、js中通過“getClass”方法獲取該對象的類型類
4紊扬、通過反射機制,得到該類的Runtime對象
5唉擂、調用靜態(tài)方法執(zhí)行系統(tǒng)命令
核心代碼示例:

<script type="text/javascript">
            function execute(cmd) {
                return demo.getClass().forName('java.lang.Runtime').getMethod('getRuntime', null).invoke(null, null).exec(cmd);
            }
            
            execute(["ls", "/mnt/sdcard"]);
        </script>

解決方案:

  • Android 4.2以上:@JavascriptInterface
  • Android 4.2以下:自定義js和Android交互方式

因此餐屎,在講述js與java互調時基于Android 4.2以上。這個了解了解就好玩祟。

項目結構

js與java互調項目結構.png

JS調用Java方法

main目錄腹缩,選擇new->Folder->Assets Folder,完成assets目錄創(chuàng)建。然后新建一個文件夾,命名為jscalljava藏鹊。接著新建一個空白的html文件命名為index润讥。

新建一個空Activity,命名為JSAndJavaActivity盘寡,且設置為啟動Activity楚殿,代碼如下:

public class JSAndJavaActivity extends AppCompatActivity {

    private WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_jsand_java);

        webView = findViewById(R.id.web_view);

        WebSettings settings = webView.getSettings();
        settings.setJavaScriptEnabled(true);

        // 第二個參數可以簡單理解為android表示AndroidAndrJsInterface的對象
        // 在js,通過它調用AndroidAndrJsInterface類下的方法
        // 名字可以自定義
        webView.addJavascriptInterface(new AndroidAndrJsInterface(), "android");
        webView.setWebViewClient(new WebViewClient());
        webView.loadUrl("file:///android_asset/jscalljava/index.html");
    }

    class AndroidAndrJsInterface {

        // 該注解可以解決Android 4.2以上的安全漏洞竿痰,4.2以下沒有這個注解
        @JavascriptInterface
        public void showToast() {
            Toast.makeText(JSAndJavaActivity.this, "我被js調用了", Toast.LENGTH_LONG).show();
        }

        @JavascriptInterface
        public void showToast(String info) {
            Toast.makeText(JSAndJavaActivity.this, "來自js的消息:" + info, Toast.LENGTH_LONG).show();
        }
    }
}

布局文件activity_jsand_java.xml的代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".JSAndJavaActivity">

    <WebView
        android:id="@+id/web_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    
</android.support.constraint.ConstraintLayout>

index.html添加如下代碼:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style type="text/css">
            div {
                margin: 0 auto;
                width: 200px;
            }
            button {
                width: 150px;
                font-weight: bolder;
                font-family: "微軟雅黑";
            }
        </style>
    </head>
    <body>
        <div>
            <p>
                <button onclick="android.showToast()">調用Java無參方法</button>
            </p>
            <p>
                <button onclick="android.showToast('I am come from js.')">調用Java有參方法</button>
            </p>
        </div>
    </body>
</html>

Java調用JS方法

示例代碼主要演示以下內容:

  • Android調用js的無參函數
  • Android調用js的有參函數
  • Android調用js的函數并獲取返回值

在assets目錄下新建文件夾脆粥,命名為javacalljs。然后新建一個空的html影涉,命名為index变隔。

新建一個空Activity,命名為JavaAndJSActivity常潮,且設置為啟動Activity弟胀,代碼如下:

public class JavaAndJSActivity extends AppCompatActivity implements View.OnClickListener {

    private Button btnNoParamter;
    private Button btnYesParamter;
    private Button btnNoParamterAndReturn;

    // 加載網頁或者說H5頁面
    private WebView webView;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_java_and_js);

        btnNoParamter = findViewById(R.id.btn_no_parameter);
        btnYesParamter = findViewById(R.id.btn_yes_parmater);
        btnNoParamterAndReturn = findViewById(R.id.btn_no_parmater_return);

        btnNoParamter.setOnClickListener(this);
        btnYesParamter.setOnClickListener(this);
        btnNoParamterAndReturn.setOnClickListener(this);

        webView = new WebView(this);

        WebSettings settings = webView.getSettings();
        settings.setJavaScriptEnabled(true); // 設置支持js腳本語言
        settings.setUseWideViewPort(true); // 支持雙擊-前提是頁面要支持才顯示
        settings.setBuiltInZoomControls(true); // 支持縮放按鈕-前提是頁面要支持才顯示

        webView.setWebViewClient(new WebViewClient()); // 不跳轉到默認瀏覽器
        webView.setWebChromeClient(new WebChromeClient()); // 支持js彈窗

        webView.addJavascriptInterface(new GetJsResult(), "Result");

        // 加載本地文件:file:///android_asset/文件具體路徑
        // 網絡資源,如:http://www.baidu.com
        // 此處asset后面是沒有s的
        webView.loadUrl("file:///android_asset/javacalljs/index.html"); // 加載網絡資源(需要網絡權限)喊式,也可以時assets目錄下的資源

        // 加載h5寫的頁面孵户,會替換當前原生頁面,在這里不需要
//        setContentView(webView);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            // 格式:WebView.loadUrl("javascript:js方法")
            case R.id.btn_no_parameter:
                // 調用js無參函數
                webView.loadUrl("javascript:noParamter()");
                break;
            case R.id.btn_yes_parmater:
                String info = "Hello,I am come from java.";
                // 調用js有參參數
                // 傳遞字符串要加個單引號,數字可以不加岔留;傳遞數組可以傳遞json格式的字符串
                webView.loadUrl("javascript:yesParamter('" + info + "')");
                break;
            case R.id.btn_no_parmater_return:
                webView.loadUrl("javascript:returnResult()");
                break;
            default:
                break;
        }
    }

    class GetJsResult {
        @JavascriptInterface
        public void getResult(String res) {
            Toast.makeText(JavaAndJSActivity.this, "js返回的結果:" + res, Toast.LENGTH_SHORT).show();
        }
    }

}

布局文件activity_java_and_js.xml的代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">


    <Button
        android:id="@+id/btn_no_parameter"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:text="調用無參方法"
        app:layout_constraintBottom_toTopOf="@+id/btn_yes_parmater"
        app:layout_constraintEnd_toEndOf="@+id/btn_yes_parmater"
        app:layout_constraintStart_toStartOf="@+id/btn_yes_parmater" />

    <Button
        android:id="@+id/btn_yes_parmater"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:text="調用有參方法"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_no_parmater_return"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="調用無參且有返回值"
        app:layout_constraintEnd_toEndOf="@+id/btn_yes_parmater"
        app:layout_constraintStart_toStartOf="@+id/btn_yes_parmater"
        app:layout_constraintTop_toBottomOf="@+id/btn_yes_parmater" />
</android.support.constraint.ConstraintLayout>

index.html添加如下代碼:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>java調用js</title>
        <script type="text/javascript">
            function noParamter() {
                alert("我是js無參函數");
            }
            
            function yesParamter(info) {
                alert("來自java的信息:" + info);
            }
            
            function returnResult() {
                var a = "我處理完了夏哭。"
                // 將結果返回給Android
                window.Result.getResult(a);
            }
        </script>
    </head>
    <body>
    </body>
</html>

小結:java和js互調的基本操作就講完了,一些要注意地方已經在代碼中注釋了献联。

JSBridge的實現

JSBridge位置處于js和java之間竖配,如下圖所示:

jsbridge位置.png

在前面也曾提過,在Android 4.2以下的addJavascriptInterface()方法存在漏洞里逆,其解決方案是JSBridge进胯。簡單來說就是自定義協議,暴漏對app沒影響的信息原押,有影響的隱藏掉胁镐。當然,Android 4.2以上也可以使用這個方案诸衔。

java調用js依然采用WebView.loadUrl()盯漂,而js調用java就不能再采用addJavascriptInterface()了,需要換一個思路笨农。WebChromeClient類就缆,我們已經接觸過了,它允許app顯示js的彈窗谒亦。常見的彈窗有alert(警告框)竭宰、confirm(確認框)和prompt(提示框)空郊,前兩個出現的頻率相對后者更高,而prompt更加適合用來傳遞信息到Android羞延,以下是它們在Android對應的源碼實現:

/**
     * Tell the client to display a javascript alert dialog.  If the client
     * returns {@code true}, WebView will assume that the client will handle the
     * dialog.  If the client returns {@code false}, it will continue execution.
     * @param view The WebView that initiated the callback.
     * @param url The url of the page requesting the dialog.
     * @param message Message to be displayed in the window.
     * @param result A JsResult to confirm that the user hit enter.
     * @return boolean Whether the client will handle the alert dialog.
     */
    public boolean onJsAlert(WebView view, String url, String message,
            JsResult result) {
        return false;
    }

    /**
     * Tell the client to display a confirm dialog to the user. If the client
     * returns {@code true}, WebView will assume that the client will handle the
     * confirm dialog and call the appropriate JsResult method. If the
     * client returns false, a default value of {@code false} will be returned to
     * javascript. The default behavior is to return {@code false}.
     * @param view The WebView that initiated the callback.
     * @param url The url of the page requesting the dialog.
     * @param message Message to be displayed in the window.
     * @param result A JsResult used to send the user's response to
     *               javascript.
     * @return boolean Whether the client will handle the confirm dialog.
     */
    public boolean onJsConfirm(WebView view, String url, String message,
            JsResult result) {
        return false;
    }

    /**
     * Tell the client to display a prompt dialog to the user. If the client
     * returns {@code true}, WebView will assume that the client will handle the
     * prompt dialog and call the appropriate JsPromptResult method. If the
     * client returns false, a default value of {@code false} will be returned to to
     * javascript. The default behavior is to return {@code false}.
     * @param view The WebView that initiated the callback.
     * @param url The url of the page requesting the dialog.
     * @param message Message to be displayed in the window.
     * @param defaultValue The default value displayed in the prompt dialog.
     * @param result A JsPromptResult used to send the user's reponse to
     *               javascript.
     * @return boolean Whether the client will handle the prompt dialog.
     */
    public boolean onJsPrompt(WebView view, String url, String message,
            String defaultValue, JsPromptResult result) {
        return false;
    }

當app接受到要顯示js的彈窗時渣淳,會根據彈窗的類型執(zhí)行相應的方法,如prompt()對應onJsPrompt()伴箩。所以入愧,我們可以重寫onJsPrompt()方法,請求處理完后將其攔截嗤谚,也就是返回true棺蛛,那這個彈窗就不會顯示了。換句話說巩步,可以在這調用已經寫好的Java方法旁赊。

接下就是要解決自定義協議了。我們可以模仿http的url格式椅野,http://host:port/param=value终畅,轉換過來,JSBridge://className:callbackAddress/methodName?jsonObj竟闪。js向Android發(fā)送信息(url)必須按這個格式离福,而Java層只處理符合這個協議(格式)的請求,其它的一概不處理炼蛤。下面對這個協議進行解釋:

  • JSBridge:便于檢驗該url是否合格
  • className:要暴露出去的類的名字妖爷,但它不是js要調用的目標類,在本demo中是JSBridge
  • callbackAddress:js回調函數存在數組的位置理朋,也就是下標
  • methodName:js要調用的方法絮识,它的具體參數(比如個數)是無法得知的,在本demo中是showToast
  • jsonObj:真正傳遞給Android的信息嗽上,要求是json格式的字符串次舌,至于具體是什么格式看需求了

最后,將協議轉換成代碼兽愤。

根據上述的項目結構圖彼念,在assets/jsbridge目錄下新建空白的index.htmlJSBridge.js文件。新建CallBack類烹看,負責將Java方法的執(zhí)行結果通知js,其代碼如下:

public class CallBack {

    private String mPort;

    private WebView mWebView;

    public CallBack(WebView webView, String mPort) {
        this.mPort = mPort;
        this.mWebView = webView;
    }

    /**
     * 通知js
     * @param jsonObject Java層處理完后返回給js層的信息
     */
    public void apply(JSONObject jsonObject) {
        if (mWebView != null) {
            mWebView.loadUrl("javascript:onAndroidFinished('" + mPort + "', " + String.valueOf(jsonObject) + ")");
        }
        Log.d("TAG", "CallBack:apply");
    }
}

新建Methods類洛史,用于封裝供js調用的方法且有以下約定:

  • 方法必須是publicstatic
  • 參數必須有3
  • 第一個參數必須是WebView惯殊,第二個參數必須是JSONObject,第三個參數必須是CallBack

只有滿足以上三個條件的方法才能被js調用也殖,才會暴露出去土思。其代碼如下:

public class Methods {

    public static void showToast(WebView view, JSONObject param, CallBack callBack) {
        // 解析得到key=msg的值
        String message = param.optString("msg");

        Toast.makeText(view.getContext(), message, Toast.LENGTH_SHORT).show();

        if (callBack != null) {
            try {
                JSONObject result = new JSONObject();
                result.put("key", "value");
                result.put("key1", "value1");
                callBack.apply(result);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    }

}

新建JSBridge類务热,負責管理暴露給js的類和方法,以及根據js傳入的url內容找到對應的java類己儒,并執(zhí)行指定的Java方法崎岂,代碼如下:

public class JSBridge {

    // 存儲需要暴露給js的方法
    private static Map<String, HashMap<String, Method>> exposedMethods = new HashMap<>();

    /**
     * 注冊要暴露的類
     * @param exposeName JSBridge
     * @param classz 要暴露的類
     */
    public static void register(String exposeName, Class<?> classz) {
        // 將符合要求的classz類中的所有方法添加到exposedMethods中
        if (!exposedMethods.containsKey(exposeName)) {
            exposedMethods.put(exposeName, getAllMethod(classz));
        }
        Log.d("TAG", "JSBridge:register");
    }

    private static HashMap<String, Method> getAllMethod(Class injectedCls) {
        HashMap<String, Method> methodHashMap = new HashMap<>();

        // 獲取該類的所有方法
        Method[] methods = injectedCls.getDeclaredMethods();

        for (Method method : methods) {
            // 剔除不符合要求的方法
            if (method.getModifiers() != (Modifier.PUBLIC | Modifier.STATIC) || method.getName() == null) {
                continue;
            }

            // 方法的參數
            Class[] paramters = method.getParameterTypes();
            // 進一步尋找符合要求的方法
            if (paramters != null && paramters.length == 3) {
                if (paramters[0] == WebView.class && paramters[1] == JSONObject.class && paramters[2] == CallBack.class) {
                    methodHashMap.put(method.getName(), method);
                }
            }
        }

        return methodHashMap;
    }

    /**
     * 調用相應的java方法去處理js的請求
     * @param webView WebView
     * @param urlString 根據協議,js層給java傳遞的信息
     * @return null
     */
    public static String callJava(WebView webView, String urlString) {
        String className = "";
        String methodName = "";
        String param = "";
        String port = "";

        // 驗證該urlString是否符合協議的基本要求
        if (!urlString.equals("") && urlString != null && urlString.startsWith("JSBridge")) {
            Uri uri = Uri.parse(urlString);
            className = uri.getHost();   // 要調用的類
            param = uri.getQuery();      // js層給Java層傳遞的信息(json格式)
            port = uri.getPort() + "";   // js層回調函數的地址
            methodName = uri.getPath().replace("/", "");  // 要調用的方法

            if (exposedMethods.containsKey(className)) {
                // 找到該類的所有符合要求的方法
                HashMap<String, Method> methodHashMap = exposedMethods.get(className);

                if (methodHashMap != null && methodHashMap.size() != 0 && methodHashMap.containsKey(methodName)) {
                    // 根據方法名找到指定的方法
                    Method method = methodHashMap.get(methodName);
                    if (method != null) {
                        try {
                            // 在這里真正處理js的請求闪湾,CallBack用于告訴js層我的活干完了冲甘,該你了
                            method.invoke(null, webView, new JSONObject(param), new CallBack(webView, port));
                            Log.d("TAG", "JSBridge:callJava");
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

        return null;
    }

}

新建JSBridgeChromeClient類且繼承WebChromeClient,在此類處理js的請求途样,代碼如下:

public class JSBridgeChromeClient extends WebChromeClient {

    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
        // 在簡單講述是否調用result.confirm()的區(qū)別
        // 如果調用江醇,js的回調函數才會被調用陶夜,參數的值是返回給js的
        // 如果沒調用裆站,js即使有回調函數也不會執(zhí)行
        // 可以使用console.log()的方式來調式js条辟,項目運行起來后可在run窗口查看
        result.confirm(JSBridge.callJava(view, message));
//        JSBridge.callJava(view, message);
        Log.d("TAG", "JSBridgeChromeClient");

        return true;
    }
}

新建JSBridgeActivity且設置為啟動Activity,代碼如下:

public class JSBridgeActivity extends AppCompatActivity {

    private WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_jsbridge);

        webView = findViewById(R.id.webView);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.setWebViewClient(new WebViewClient());
        webView.setWebChromeClient(new JSBridgeChromeClient());
        webView.loadUrl("file:///android_asset/jsbridge/index.html");

        JSBridge.register("JSBridge", Methods.class);
        Log.d("TAG", "JSBridgeActivity");
    }
}

布局文件activity_jsbridge.xml的代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".jsbridge.JSBridgeActivity">

    <WebView
        android:id="@+id/webView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    
</android.support.constraint.ConstraintLayout>

JSBridge.js的代碼如下:

var callbacks = new Array();

/**
 * js層調用Android層方法
 * @param {Object} obj Android層的類
 * @param {Object} method 該obj中的那個方法
 * @param {Object} params 使用json的數據格式給Android傳遞信息
 * @param {Object} callback js層的回調方法宏胯,當Android層處理好了js層要如何處理
 */
function jsCallAndroid(obj, method, params, callback) {
    // 保存callback回調函數
    var port = callbacks.length;
    callbacks[port] = callback;
    
    // 組合出符合規(guī)則的url羽嫡,并傳遞給Java層
    var url = 'JSBridge://' + obj + ':' + port + '/' + method + '?' + JSON.stringify(params);
    
    window.prompt(url);
}

/**
 * 當js調用完Android層時執(zhí)行
 * @param {Object} port 回調函數的地址,也就是在數組中的位置
 * @param {Object} jsonObj 從Android層傳過來的參數
 */
function onAndroidFinished(port, jsonObj) {
    // 從callbacks取出對應的回調函數
    var callback = callbacks[port];
    
    callback(jsonObj);
    
    // 從callbacks中刪除callback
    delete callbacks[port];
}

index.html的代碼如下:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <script type="text/javascript" src="JSBridge.js" ></script>
    </head>
    <body>
        <button onclick="jsCallAndroid('JSBridge', 'showToast',
            {'msg':'hello I come from js.'},function(res) {alert(JSON.stringify(res))})">Js調用Android</button>
    </body>
</html>

對函數的調用過程進行概括:點擊按鈕胳嘲,觸發(fā)jsCallAndroid()方法厂僧,通過調用window.prompt(url)向Android發(fā)送請求(信息)。然后在JSBridgeChromeClient.onJsPrompt()方法對請求進行攔截處理颜屠,就是實現了js調用java,JSBridge.callJava(view, message)甫窟。執(zhí)行到callJava()方法內部蛙婴,會調用Methods.showToast()方法,緊接著會調用CallBack.apply(result)方法街图,最后是調用js的onAndroidFinished()方法浇衬。如果JsPromptResult.confirm()被調用了,js的回調函數會被同步調用餐济。

總結

本章的內容就將完了耘擂。

HyBridDemo

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末醉冤,一起剝皮案震驚了整個濱河市铃绒,隨后出現的幾起案子归粉,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件埠忘,死亡現場離奇詭異绰上,居然都是意外死亡,警方通過查閱死者的電腦和手機蜈块,發(fā)現死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門鉴腻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人百揭,你說我怎么就攤上這事爽哎。” “怎么了器一?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵课锌,是天一觀的道長。 經常有香客問我盹舞,道長产镐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任踢步,我火速辦了婚禮癣亚,結果婚禮上,老公的妹妹穿的比我還像新娘获印。我一直安慰自己述雾,他們只是感情好,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布兼丰。 她就那樣靜靜地躺著玻孟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鳍征。 梳的紋絲不亂的頭發(fā)上黍翎,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天,我揣著相機與錄音艳丛,去河邊找鬼匣掸。 笑死,一個胖子當著我的面吹牛氮双,可吹牛的內容都是我干的碰酝。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼戴差,長吁一口氣:“原來是場噩夢啊……” “哼送爸!你這毒婦竟也來了?” 一聲冷哼從身側響起暖释,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤袭厂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后饭入,有當地人在樹林里發(fā)現了一具尸體嵌器,經...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年谐丢,在試婚紗的時候發(fā)現自己被綠了爽航。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡乾忱,死狀恐怖讥珍,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情窄瘟,我是刑警寧澤衷佃,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站蹄葱,受9級特大地震影響氏义,放射性物質發(fā)生泄漏锄列。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一惯悠、第九天 我趴在偏房一處隱蔽的房頂上張望邻邮。 院中可真熱鬧,春花似錦克婶、人聲如沸筒严。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鸭蛙。三九已至,卻和暖如春筋岛,著一層夾襖步出監(jiān)牢的瞬間娶视,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工睁宰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留歇万,地道東北人。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓勋陪,卻偏偏與公主長得像贪磺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子诅愚,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內容

  • 在Android開發(fā)中寒锚,能夠實現Web調用Native代碼的方法主要有以下方法:1.Schema:WebView攔...
    Drc15H閱讀 1,715評論 0 5
  • 鏈接:http://www.reibang.com/p/fd61e8f4049e 一、簡介 這部分主要介紹下 W...
    柒黍閱讀 1,820評論 0 4
  • ¥開啟¥ 【iAPP實現進入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程违孝,因...
    小菜c閱讀 6,419評論 0 17
  • 更多文章請關注:開發(fā)者技術前線 Android開發(fā)目前現狀來說刹前,開發(fā)者大部分時間花在UI的屏幕適配上,使用原生控件...
    Tamic閱讀 17,624評論 15 80
  • WebView·開車指南 2016-08-31BugDev 北京市東城區(qū)首席Bug布道師開山之作雌桑,一整月交通事故血...
    53c021c38a1d閱讀 829評論 0 1