When Web Page Title meet Na6ve App SUBTITLE - - PowerPoint PPT Presentation

when web page title meet na6ve app
SMART_READER_LITE
LIVE PREVIEW

When Web Page Title meet Na6ve App SUBTITLE - - PowerPoint PPT Presentation

When Web Page Title meet Na6ve App SUBTITLE App Eric Chuang CONFIDENTIAL & PROPRIETARY 1 When Web meet Na6ve App Yahoo Taiwan Mobile Team


slide-1
SLIDE 1

CONFIDENTIAL & PROPRIETARY

Page ¡Title

SUBTITLE

1

When ¡ ¡ Web ¡ meet ¡ ¡ Na6ve ¡App

Eric ¡Chuang

當網⾴頂遇上原⽣甠App

slide-2
SLIDE 2

CONFIDENTIAL & PROPRIETARY

When ¡Web ¡meet ¡Na6ve ¡App

Yahoo ¡Taiwan ¡Mobile ¡Team ¡ Eric ¡Chuang

2

slide-3
SLIDE 3

CONFIDENTIAL & PROPRIETARY

About ¡Me

  • Eric ¡Chuang ¡(ddsakura) ¡
  • WebConf ¡2013 ¡Speaker ¡
  • Yahoo ¡Lead ¡Engineer

3

  • Full ¡Stack ¡Engineer ¡? ¡

– Developed ¡Yahoo ¡E-­‑Commerce ¡Mobile ¡Web ¡ – Developed ¡Yahoo ¡E-­‑Commerce ¡超級商城 ¡iPhone ¡App ¡ – Developed ¡Yahoo ¡E-­‑Commerce ¡拍賣 ¡Android ¡App

slide-4
SLIDE 4

Auc6on ¡Android ¡App

slide-5
SLIDE 5

猜猜哪裡是 ¡Web?

slide-6
SLIDE 6

Web ¡in ¡Auc6on ¡Android ¡App

slide-7
SLIDE 7

CONFIDENTIAL & PROPRIETARY

What ¡is ¡Webview

  • A ¡View ¡that ¡displays ¡web ¡pages. ¡
  • Android ¡

– Since ¡API ¡1 ¡

  • iOS ¡

– UIWebView ¡ – MKWebView

7

slide-8
SLIDE 8

CONFIDENTIAL & PROPRIETARY

曾經我以為

Android ¡Webview ¡> ¡iOS ¡UIWebview ¡

¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡(can ¡not ¡use ¡Nitro ¡JavaScript ¡Engine ¡)

8

Android ¡Webview ¡== ¡Android ¡Browser

slide-9
SLIDE 9

CONFIDENTIAL & PROPRIETARY 9

hXp://beta.html5test.com/

slide-10
SLIDE 10

CONFIDENTIAL & PROPRIETARY

hXp://beta.html5test.com/

slide-11
SLIDE 11

CONFIDENTIAL & PROPRIETARY

結果…

11

Android ¡Webview ¡!= ¡Android ¡Browser Android ¡Webview ¡v.s. ¡iOS ¡UIWebview/MKWebview

slide-12
SLIDE 12

CONFIDENTIAL & PROPRIETARY 12

slide-13
SLIDE 13

CONFIDENTIAL & PROPRIETARY

Android ¡Webview ¡version

  • Android ¡4.4 ¡

– Chromium ¡30

13

  • Android ¡4.4.3 ¡

– Chromium ¡33

  • Android ¡5.0 ¡

– Chromium ¡37 ¡ – And ¡in ¡google ¡play ¡

– hXps://play.google.com/store/apps/details?id=com.google.android.webview

slide-14
SLIDE 14

CONFIDENTIAL & PROPRIETARY 14

slide-15
SLIDE 15

CONFIDENTIAL & PROPRIETARY

Android ¡Webview ¡version

  • Android ¡before ¡4.4 ¡

– Old ¡Webview ¡ – Vendor ¡may ¡“improve” ¡their ¡webview ¡ – ref: ¡hXp://slides.com/html5test/the-­‑android-­‑browser#/12

15

slide-16
SLIDE 16

CONFIDENTIAL & PROPRIETARY

SO ¡WE ¡NO ¡LONGER ¡HAVE ¡ ONE ¡WEBVIEW ¡FOR ¡ EACH ¡ANDROID ¡VERSION

BUT

16

ONE ¡FOR ¡SAMSUNG, AND ¡ONE ¡FOR ¡HTC, AND ¡ONE ¡FOR ¡...

slide-17
SLIDE 17

CONFIDENTIAL & PROPRIETARY

Let’s ¡start ¡webview

  • We ¡need ¡permission

17

<uses-­‑permission ¡android:name="android.permission.INTERNET" ¡/>

  • Basic ¡usage

WebView ¡mWebview ¡= ¡new ¡WebView(this); ¡ mWebview.loadUrl(“file:///android_asset/www/index.html”); ¡ mWebview.loadUrl(“hXp://tw.yahoo.com/“); ¡ String ¡summary ¡= ¡“<html><body>Hello ¡World</body></html>”; ¡ mWebview.loadData(summary, ¡"text/html", ¡null); ¡

slide-18
SLIDE 18

CONFIDENTIAL & PROPRIETARY

Local ¡Assets

  • file:///android_asset/ ¡

– The ¡assets ¡directory ¡of ¡an ¡Android ¡app ¡is ¡located ¡at ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ src/main/assets ¡inside ¡your ¡Android ¡Studio ¡project.

18

slide-19
SLIDE 19

CONFIDENTIAL & PROPRIETARY

How ¡about ¡JavaScript

  • WebViews ¡don't ¡allow ¡JavaScript ¡by ¡default.

19

// ¡Enable ¡JavascriptWebSepngs ¡ ¡ webSepngs ¡= ¡mWebView.getSepngs(); ¡ webSepngs.setJavaScriptEnabled(true);

slide-20
SLIDE 20

CONFIDENTIAL & PROPRIETARY

Configure ¡webview

  • Websepngs ¡

– setJavaScriptEnabled ¡ ¡

  • The ¡default ¡is ¡false. ¡

– setGeoloca6onEnabled ¡ ¡

  • The ¡default ¡is ¡true. ¡ ¡
  • ACCESS_COARSE_LOCATION, ¡ACCESS_FINE_LOCATION ¡

– setBuiltInZoomControls ¡ ¡

  • The ¡default ¡is ¡false. ¡ ¡

– setDomStorageEnabled ¡ ¡

  • The ¡default ¡value ¡is ¡false.

20

hXp://developer.android.com/reference/android/webkit/WebSepngs.html

slide-21
SLIDE 21

CONFIDENTIAL & PROPRIETARY

Configure ¡webview ¡cont.

  • UserAgent ¡

– getUserAgentString ¡ – setUserAgentString

21

Mozilla/5.0 ¡(Linux; ¡U; ¡Android ¡4.1.1; ¡en-­‑gb; ¡Build/KLP) ¡AppleWebKit/534.30 ¡(KHTML, ¡like ¡Gecko) ¡Version/4.0 ¡Safari/534.30 Mozilla/5.0 ¡(Linux; ¡Android ¡4.4; ¡Nexus ¡5 ¡Build/_BuildID_) ¡AppleWebKit/537.36 ¡(KHTML, ¡like ¡Gecko) ¡Version/4.0 ¡Chrome/ 30.0.0.0 ¡Mobile ¡Safari/537.36

hXps://developer.chrome.com/mul6device/user-­‑agent

slide-22
SLIDE 22

WebViewClient WebChromeClient

slide-23
SLIDE 23

CONFIDENTIAL & PROPRIETARY

WebViewClient

  • Instance ¡of ¡WebViewClient ¡that ¡is ¡the ¡client ¡callback. ¡
  • It ¡will ¡be ¡called ¡when ¡things ¡happen ¡that ¡impact ¡the ¡

rendering ¡of ¡the ¡content, ¡

  • Func6ons ¡

– onLoadResource ¡ – onPageStart ¡ – onPageFinish ¡ – onReceiveError ¡ – shouldInterceptRequest ¡

23

hXp://developer.android.com/reference/android/webkit/WebViewClient.html

slide-24
SLIDE 24

CONFIDENTIAL & PROPRIETARY

WebChromeClient

  • Instance ¡of ¡WebChromeClient ¡for ¡handling ¡all ¡chrome ¡
  • func6ons. ¡
  • This ¡class ¡is ¡called ¡when ¡something ¡that ¡might ¡impact ¡a ¡

browser ¡UI ¡happens, ¡for ¡instance, ¡progress ¡updates ¡and ¡ JavaScript ¡alerts ¡are ¡sent ¡here ¡ ¡

  • Func6ons ¡

  • nCloseWindow ¡

  • nCreateWindow ¡

  • nJsAlert ¡

  • nJsPrompt ¡

  • nJsConfirm ¡

24

hXp://developer.android.com/reference/android/webkit/WebChromeClient.html

slide-25
SLIDE 25

CONFIDENTIAL & PROPRIETARY

Example ¡between ¡WebViewClient ¡and ¡WebChromeClient

25

slide-26
SLIDE 26

CONFIDENTIAL & PROPRIETARY

Handling ¡Links

  • Default ¡behavior: ¡load ¡that ¡URL ¡of ¡the ¡link ¡in ¡the ¡default ¡

Android ¡browser. ¡

26

Intercept ¡the ¡url ¡!!

slide-27
SLIDE 27

CONFIDENTIAL & PROPRIETARY

Intercep6ng ¡WebView ¡HTTP ¡Requests

  • public ¡boolean ¡shouldOverrideUrlLoading ¡(WebView ¡view, ¡

String ¡url) ¡

– Give ¡the ¡host ¡applica6on ¡a ¡chance ¡to ¡take ¡over ¡the ¡control ¡when ¡a ¡ new ¡url ¡is ¡about ¡to ¡be ¡loaded ¡in ¡the ¡current ¡WebView. ¡

27

slide-28
SLIDE 28

CONFIDENTIAL & PROPRIETARY

Intercep6ng ¡example ¡1

28

if ¡(path.contains(ECWebView.WEB_URL_ECAUCTION_TYPE_PRODUCT_ITEM)) ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡showItemPage(url); ¡// ¡call ¡na6ve ¡component ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡return ¡true; ¡ } ¡ if ¡(path.contains(ECWebView.WEB_URL_ECAUCTION_TYPE_SELLER_BOOTH)) ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡showSellerBooth(url); ¡// ¡call ¡na6ve ¡component ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡return ¡true; ¡ }

slide-29
SLIDE 29

CONFIDENTIAL & PROPRIETARY

Intercep6ng ¡example ¡2

29

if (whiteList.indexOf(host) != -1) { toggleLoadingUI(true); return false; }

slide-30
SLIDE 30

CONFIDENTIAL & PROPRIETARY

From ¡JavaScript ¡to ¡Java ¡-­‑ ¡Android ¡Part

30

public ¡class ¡WebAppInterface ¡{ ¡ ¡ ¡ ¡ ¡Context ¡mContext; ¡ ¡ ¡ ¡ ¡/** ¡Instan6ate ¡the ¡interface ¡and ¡set ¡the ¡context ¡*/ ¡ ¡ ¡ ¡ ¡WebAppInterface(Context ¡c) ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡mContext ¡= ¡c; ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡ ¡ ¡/** ¡Show ¡a ¡toast ¡from ¡the ¡web ¡page ¡*/ ¡ ¡ ¡ ¡ ¡@JavascriptInterface ¡ ¡ ¡ ¡ ¡public ¡void ¡showToast(String ¡toast) ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡Toast.makeText(mContext, ¡toast, ¡Toast.LENGTH_SHORT).show(); ¡ ¡ ¡ ¡ ¡} ¡ }

webView.addJavascriptInterface(new ¡WebAppInterface(this), ¡"Android");

slide-31
SLIDE 31

CONFIDENTIAL & PROPRIETARY

From ¡JavaScript ¡to ¡Java ¡-­‑ ¡HTML ¡Part

31

<input ¡type="buXon" ¡value="Say ¡hello" ¡onClick="showAndroidToast('Hello ¡Android!')" ¡/> ¡ <script ¡type="text/javascript"> ¡ ¡ ¡ ¡ ¡func6on ¡showAndroidToast(toast) ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡Android.showToast(toast); ¡ ¡ ¡ ¡ ¡} ¡ </script>

slide-32
SLIDE 32

CONFIDENTIAL & PROPRIETARY

From ¡Java ¡to ¡JavaScript

32

mWebView.loadUrl("javascript:window.cartList.closeOverlay()");

slide-33
SLIDE 33

CONFIDENTIAL & PROPRIETARY

Naviga6ng ¡web ¡page ¡history

33

@Override ¡ public ¡boolean ¡onKeyDown(int ¡keyCode, ¡KeyEvent ¡event) ¡{ ¡ ¡ ¡ ¡ ¡// ¡Check ¡if ¡the ¡key ¡event ¡was ¡the ¡Back ¡buXon ¡and ¡if ¡there's ¡history ¡ ¡ ¡ ¡ ¡if ¡((keyCode ¡== ¡KeyEvent.KEYCODE_BACK) ¡&& ¡myWebView.canGoBack()) ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡myWebView.goBack(); ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡return ¡true; ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡ ¡ ¡// ¡If ¡it ¡wasn't ¡the ¡Back ¡key ¡or ¡there's ¡no ¡web ¡page ¡history, ¡bubble ¡up ¡to ¡the ¡default ¡ ¡ ¡ ¡ ¡// ¡system ¡behavior ¡(probably ¡exit ¡the ¡ac6vity) ¡ ¡ ¡ ¡ ¡return ¡super.onKeyDown(keyCode, ¡event); ¡ }

slide-34
SLIDE 34

CONFIDENTIAL & PROPRIETARY

Cache ¡Web ¡Resources

34

@Override ¡ public ¡WebResourceResponse ¡shouldInterceptRequest(WebView ¡view, ¡String ¡url) ¡{ ¡ ¡ ¡ ¡ ¡if(url.startsWith("hXp://mydomain.com/ar6cle/") ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡String ¡cacheFileName ¡= ¡url.substring(url.lastIndexOf("/"), ¡url.length()); ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡this.urlCache.register(url, ¡cacheFileName, ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡"text/html", ¡"UTF-­‑8", ¡60 ¡* ¡UrlCache.ONE_MINUTE); ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡ ¡ ¡return ¡this.urlCache.load(url); ¡ } ¡

slide-35
SLIDE 35

CONFIDENTIAL & PROPRIETARY

Android ¡Lollipop

  • Mixed ¡content ¡issue

35

¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡if ¡(android.os.Build.VERSION.SDK_INT ¡>= ¡21) ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡webSepngs.setMixedContentMode(WebSe<ngs.MIXED_CONTENT_ALWAYS_ALLOW); ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡}

slide-36
SLIDE 36

CONFIDENTIAL & PROPRIETARY

Cookie

  • CookieManager ¡

– Manages ¡the ¡cookies ¡used ¡by ¡an ¡applica6on's ¡WebView ¡instances. ¡

  • setCookie(String ¡url, ¡String ¡value) ¡
  • getCookie(url) ¡
  • CookieSyncManager ¡

– This ¡class ¡was ¡deprecated ¡in ¡API ¡level ¡21. ¡The ¡WebView ¡now ¡ automa6cally ¡syncs ¡cookies ¡as ¡necessary. ¡You ¡no ¡longer ¡need ¡to ¡ create ¡or ¡use ¡the ¡CookieSyncManager. ¡To ¡manually ¡force ¡a ¡sync ¡you ¡ can ¡use ¡the ¡CookieManager ¡method ¡flush() ¡which ¡is ¡a ¡synchronous ¡ replacement ¡for ¡sync(). ¡

36

slide-37
SLIDE 37

CONFIDENTIAL & PROPRIETARY

Handle ¡HXp ¡Error

37

¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡@Override ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡public ¡void ¡onReceivedError(WebView ¡view, ¡int ¡errorCode, ¡String ¡descrip6on, ¡String ¡ failingUrl) ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡… ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡@Override ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡public ¡void ¡onReceivedSslError(WebView ¡view, ¡SslErrorHandler ¡handler, ¡SslError ¡error) ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡handler.proceed(); ¡// ¡Ignore ¡SSL ¡cer6ficate ¡errors ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡}

slide-38
SLIDE 38

CONFIDENTIAL & PROPRIETARY

Handle ¡HXp ¡Error ¡bug

  • onReceivedError ¡STILL ¡does ¡not ¡receive ¡HTTP ¡Errors ¡

– hXps://code.google.com/p/android/issues/detail?id=82069

38

@Override public void onPageFinished(WebView view, String url) { if (WEBPAGE_ERROR_HTML_TITLE.indexOf(mWebContentTitle) != -1) { mListener.onPageReceivedError(view, WEBPAGE_ERROR_CODE, mWebContentTitle, url); } }

slide-39
SLIDE 39

CONFIDENTIAL & PROPRIETARY

Debugging ¡webview

  • Requirements ¡

– Chrome ¡32 ¡or ¡later ¡installed ¡on ¡your ¡development ¡machine. ¡ – A ¡USB ¡cable ¡to ¡connect ¡your ¡Android ¡device. ¡ – For ¡app ¡debugging: ¡Android ¡4.4+ ¡and ¡a ¡WebView ¡configured ¡for ¡ debugging.

39

¡if ¡(Build.VERSION.SDK_INT ¡>= ¡Build.VERSION_CODES.KITKAT) ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡WebView.setWebContentsDebuggingEnabled(true); ¡ ¡ ¡}

slide-40
SLIDE 40

CONFIDENTIAL & PROPRIETARY

Debugging ¡webview ¡cont.

  • The ¡chrome://inspect ¡page ¡displays ¡a ¡list ¡of ¡debug-­‑

enabled ¡WebViews ¡on ¡your ¡device. ¡

  • To ¡start ¡debugging, ¡click ¡inspect ¡below ¡the ¡WebView ¡you ¡

want ¡to ¡debug. ¡ ¡

  • As ¡of ¡KitKat ¡4.4.3, ¡screencast ¡is ¡available ¡for ¡both ¡browser ¡

tabs ¡and ¡Android ¡WebViews.

40

slide-41
SLIDE 41

CONFIDENTIAL & PROPRIETARY

Debugging ¡webview ¡cont.

  • The ¡chrome://inspect ¡page ¡displays ¡a ¡list ¡of ¡debug-­‑

enabled ¡WebViews ¡on ¡your ¡device. ¡

  • To ¡start ¡debugging, ¡click ¡inspect ¡below ¡the ¡WebView ¡you ¡

want ¡to ¡debug. ¡ ¡

  • As ¡of ¡KitKat ¡4.4.3, ¡screencast ¡is ¡available ¡for ¡both ¡browser ¡

tabs ¡and ¡Android ¡WebViews.

41

slide-42
SLIDE 42

CONFIDENTIAL & PROPRIETARY

Tes6ng ¡with ¡Webview

  • Robo6um ¡

– hXps://code.google.com/p/robo6um/

42

¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡// ¡Type ¡in ¡text ¡box ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡WebElement ¡txtSearch ¡= ¡solo.getWebElement(By.name("q"), ¡0); ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡txtSearch.setTextContent("Hello"); ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡// ¡click ¡buXon ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡WebElement ¡btnSearch ¡= ¡(WebElement) ¡solo.getWebElement(By.name("btnG"), ¡0); ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡solo.clickOnWebElement(btnSearch);

slide-43
SLIDE 43

CONFIDENTIAL & PROPRIETARY

Project ¡that ¡Improve ¡Webview

  • Crosswalk ¡

– hXps://crosswalk-­‑project.org/ ¡ – Develop ¡around ¡device ¡fragmenta6on ¡ – Provide ¡a ¡feature ¡rich ¡experience ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ all ¡on ¡Android ¡4.x ¡devices ¡ – Easily ¡debug ¡with ¡Chrome ¡DevTools ¡ – Improve ¡the ¡performance ¡of ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ HTML, ¡CSS, ¡and ¡JavaScript

43

slide-44
SLIDE 44

CONFIDENTIAL & PROPRIETARY

Conclusion

  • 這不是⾮靟⿊黒即⽩百的世界 ¡

– Web ¡跟 ¡Na6ve ¡App ¡亦然 ¡

  • 讓 ¡Web ¡和 ¡Na6ve ¡App ¡共舞 ¡

– 提供使⽤甩者最好的體驗

44

slide-45
SLIDE 45

CONFIDENTIAL & PROPRIETARY

Thank ¡you

45

Eric ¡Chuang

hXp://bit.ly/1JkyR1W