Android – 網際網路相關

對於大多數的App而言, 應用網際網路的資源而與App產生資料交換的情況, 應該是相當常見的應用.

 

網路狀態


當app實現了與網際網路互動交換資料的應用, 應該在此程序之前, 檢視使用者行動裝置當時的網路狀態, 才能對於後續相關的存取資料程序有所掌握.

首先, 必須設定使用權限ACCESS_NETWORK_STATE

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

先呼叫getSystemService(), 並傳遞參數Context.CONNECTIVITY, 轉型為ConnectivityManager物件實體

private ConnectivityManager cm;

……

cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);

 

再透過ConnectivityManager物件實體來取得網路連線的狀態:

  private boolean isConnectNetwork(){

       NetworkInfo activeNetwork = cm.getActiveNetworkInfo();

       boolean isConnected = activeNetwork != null &&

               activeNetwork.isConnectedOrConnecting();

       return isConnected;

   }

Wi-Fi


承上的相同模式, 也可以進一步了解目前使用Wifi的狀態: 

  private boolean isConnectWifi(){

       NetworkInfo wifi = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);

       return wifi.isConnected();

   }

當使用者透過4G電信網路連線時, isConnectWifi() 將傳回 false, 但是isConnectNetwork() 傳回true.

 

隨時掌握網路狀


通常應用程式應該隨時掌握當時的網路連線狀況, 則應該使用BroadcastReceiver來處理即時的狀況, 以因應當前的應用程式狀態. 首先, 先自定一個內部類別繼承自BroadcastReceiver, 並Override onReceive()方法:

 private class MyNetworkBroadcastReceiver extends BroadcastReceiver {

       @Override

       public void onReceive(Context context, Intent intent) {

           Log.i("brad", "receiver:" + isConnectNetwork());

       }

   }

並在onCreate()方中建構物件, 並registerReceiver()方法, 設定IntentFilter的Action為ConnectivityManager.CONNECTIVITY_ACTIVE:

private MyNetworkBroadcastReceiver receiver;

……

   @Override

   protected void onCreate(Bundle savedInstanceState) {

…...

       receiver = new MyNetworkBroadcastReceiver();

       IntentFilter filter = new IntentFilter();

       filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);

       registerReceiver(receiver, filter);

……

 }

 

並在finish()方法中解除:

  @Override

   public void finish() {

       unregisterReceiver(receiver);

       super.finish();

   }

這樣就可以隨時掌握使用者網路連線的狀態, 並在onReceive()中進行處理相關的商業邏輯.

 

 

存取網際網路資源


行動裝置透過網際網路的方式存取豐富的資源, 一般可以應用TCP或是UDP的通訊協定, 但是最常見的方式還是以基於TCP之HTTP或是HTTPS的通訊協定進行資料交換為主. 應用的觀念與瀏覽器的觀念一樣, 是以用戶端的角色與遠端的伺服器進行Http Request與Http Response.

在Android專案開發中, 任何使用到網際網路的相關處理應用, 都必須注意到以下兩點:

  • 開啟相關的權限: INTERNET
  • 不得以Main Thread(UI Thread)進行處理, 通常會以一個Thread, AsyncTask或是以Service方式進行

 

模擬器的網路位址


模擬器(AVD)架構在開發者的作業系統之下, 而模擬器的觀念就是一個虛擬主機. 開發者的作業系統下如果架設了Web Server, 例如Apache, 其所服務的位址可能涵蓋了本地迴路的網路位址: 127.0.0.1, 而該位址對於模擬器而言, 模擬器自己本身的網路位址為10.0.2.15, 外部的127.0.0.1將會被別名(alias)為10.0.2.2的網路位址. 因此如果以模擬器連接外部作業系統時, 將會以網路位址10.0.2.2進行操作應用.

 

以GET方式提出要求


大多數透過瀏覽器的網址列的輸入是以GET方法與遠端的網頁伺服器提出HTTP Request, 進而等候HTTP Response的頁面原始碼回傳給瀏覽器. 因此相同的原理觀念, 行動裝置上也是以GET方法進行資料交換處理.

 

先建立一個URL的物件實體, 相當於網址列的輸入文字資料, 例如:

URL url = new URL("https://www.google.com");

 

再來針對該URL物件實體呼叫openConnection()方法, 來開啟進行連接物件HttURLConnection.

HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();

 

之後就可以HttpsURLConnection物件執行connect()方法, 相當於在瀏覽器的網址列輸入後按下Enter的動作.

conn.connect();

 

就可以等候對方伺服器回傳的頁面原始碼, 透過呼叫getInputStream()的輸入串流物件來取得, 以下舉例是針對一般頁面文字內容資料處理.

import android.os.Bundle;

package tw.brad.ch10_gettest;


import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;

public class MainActivity extends AppCompatActivity {

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

      new Thread(){
          @Override
          public void run() {
              getUrlByGet();
          }
      }.start();
  }

  private void getUrlByGet(){

      try {

          URL url = new URL("https://www.google.com");

         

          HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();

          conn.setReadTimeout(3000);

          conn.setConnectTimeout(3000);

          conn.setRequestMethod("GET");

          conn.setDoInput(true);

          conn.connect();

         

          InputStream inputStream = conn.getInputStream();

          InputStreamReader inputStreamReader = new InputStreamReader(inputStream);

          BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

          String readLine = null;

          while ((readLine = bufferedReader.readLine()) != null){

              Log.i("brad", "=> " + readLine);

          }

      } catch (MalformedURLException e) {

          Log.i("brad", "URL Exception : " + e.toString());

      } catch (IOException e) {

          Log.i("brad", "I/O Exception : " + e.toString());

      }


  }


}

 

以POST方式提出要求


處理的觀念與GET非常類似, 唯獨在於資料參數的傳遞方式, 無法透過”key1=value1&key2=value2”的方式進行傳送, 而必須藉由輸出串流來將資料參數進行處理.

 

以下假設遠端伺服器相關資料:

URL: http://10.0.2.2/bradserver/phpmysql/brad02.php

Method: POST

Parameters:

  • cname
  • account
  • passwd

 

建立URL物件實體及產生一個HttpURLConnection物件實體, 進行相關的設定:

 

 

接下處理參數資料, 另外撰寫一個方法來處理, 將會接收的參數放在ContentValues的資料結構物件中, 將其Key, Value的資料依照”key1=value1&key2=value2&…”格式處理, 但是Key與Value則必須以URLEncoder()方式進行編碼.

           URL url = new URL("http://10.0.2.2/bradserver/phpmysql/brad02.php");


           HttpURLConnection conn = (HttpURLConnection) url.openConnection();

           conn.setReadTimeout(3000);

           conn.setConnectTimeout(3000);

           conn.setRequestMethod("POST");

           conn.setDoInput(true);

           conn.setDoOutput(true);

參數傳遞的處理:

   private String queryString(ContentValues data){

       Set<String> keys = data.keySet();

       StringBuilder sb = new StringBuilder();

       try {

           for (String key : keys) {

               sb.append(URLEncoder.encode(key, "UTF-8"));

               sb.append("=");

               sb.append(URLEncoder.encode(data.getAsString(key), "UTF-8"));

               sb.append("&");

           }

           sb.deleteCharAt(sb.length()-1); // 移掉最後一個&

           return sb.toString();

       }catch(Exception e){

           return null;

       }

   }

 

最後透過HttpURLConnection物件實體取得OutputStream來將資料進行輸出:

import android.content.ContentValues;

import android.os.Bundle;

import android.support.v7.app.AppCompatActivity;

import android.util.Log;


import java.io.BufferedWriter;

import java.io.IOException;

import java.io.OutputStream;

import java.io.OutputStreamWriter;

import java.net.HttpURLConnection;

import java.net.MalformedURLException;

import java.net.URL;

import java.net.URLEncoder;

import java.util.Set;


public class MainActivity extends AppCompatActivity {


   @Override

   protected void onCreate(Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);

       setContentView(R.layout.activity_main);


       new Thread(){

           @Override

           public void run() {

               getUrlByPost();

           }

       }.start();

   }


   private void getUrlByPost() {

       try {

           URL url = new URL("http://10.0.2.2/bradserver/phpmysql/brad02.php");


           HttpURLConnection conn = (HttpURLConnection) url.openConnection();

           conn.setReadTimeout(3000);

           conn.setConnectTimeout(3000);

           conn.setRequestMethod("POST");

           conn.setDoInput(true);

           conn.setDoOutput(true);


           ContentValues values = new ContentValues();

           values.put("cname","a1");

           values.put("account","a2");

           values.put("passwd","a3");

           String query = queryString(values);


           OutputStream os = conn.getOutputStream();

           BufferedWriter writer = new BufferedWriter(

                   new OutputStreamWriter(os, "UTF-8"));

           writer.write(query);

           writer.flush();

           os.close();


           conn.connect();


           int code = conn.getResponseCode();

           String mesg = conn.getResponseMessage();

           Log.i("brad", code + ":" + mesg);



       } catch (MalformedURLException e) {

           Log.i("brad", "URL Exception : " + e.toString());

       } catch (IOException e) {

           Log.i("brad", "I/O Exception : " + e.toString());

       }


   }


   private String queryString(ContentValues data){

       Set<String> keys = data.keySet();

       StringBuilder sb = new StringBuilder();

       try {

           for (String key : keys) {

               sb.append(URLEncoder.encode(key, "UTF-8"));

               sb.append("=");

               sb.append(URLEncoder.encode(data.getAsString(key), "UTF-8"));

               sb.append("&");

           }

           sb.deleteCharAt(sb.length()-1); // 移掉最後一個&

           return sb.toString();

       }catch(Exception e){

           return null;

       }

   }


}

 

上傳機制


一般與遠端伺服器的運用除了文字資料的傳遞之外, 檔案上傳更是常見的應用, 例如相片或是文件檔案進行上傳, 此時運用的技術與POST傳遞是一樣的, 但是必須支援使用multipart/form-data. 因此, 可以將這樣的處理簡化成以下的Java原始碼:

import java.io.BufferedReader;

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.OutputStream;

import java.io.OutputStreamWriter;

import java.io.PrintWriter;

import java.net.HttpURLConnection;

import java.net.URL;

import java.net.URLConnection;

import java.util.ArrayList;

import java.util.List;


public class MultipartUtility {

   private final String boundary;

   private static final String LINE_FEED = "\r\n";

   private HttpURLConnection httpConn;

   private String charset;

   private OutputStream outputStream;

   private PrintWriter writer;


   public MultipartUtility(String requestURL, String token, String charset)

           throws IOException {

       this.charset = charset;


       boundary = "===" + System.currentTimeMillis() + "===";


       URL url = new URL(requestURL);

       httpConn = (HttpURLConnection) url.openConnection();

       httpConn.setUseCaches(false);

       httpConn.setDoOutput(true); // indicates POST method

       httpConn.setDoInput(true);

       httpConn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

       httpConn.setRequestProperty("Authorization", "Bearer " + token);

       outputStream = httpConn.getOutputStream();

       writer = new PrintWriter(new OutputStreamWriter(outputStream, charset),

               true);

   }


   public void addFormField(String name, String value) {

       writer.append("--" + boundary).append(LINE_FEED);

       writer.append("Content-Disposition: form-data; name=\"" + name + "\"").append(LINE_FEED);

       writer.append("Content-Type: text/plain; charset=" + charset).append(LINE_FEED);

       writer.append(LINE_FEED);

       writer.append(value).append(LINE_FEED);

       writer.flush();

   }


   public void addFilePart(String fieldName, File uploadFile)

           throws IOException {

       String fileName = uploadFile.getName();

       writer.append("--" + boundary).append(LINE_FEED);

       writer.append(

               "Content-Disposition: form-data; name=\"" + fieldName

                       + "\"; filename=\"" + fileName + "\"")

               .append(LINE_FEED);

       writer.append(

               "Content-Type: "

                       + URLConnection.guessContentTypeFromName(fileName))

               .append(LINE_FEED);

       writer.append("Content-Transfer-Encoding: binary").append(LINE_FEED);

       writer.append(LINE_FEED);

       writer.flush();


       FileInputStream inputStream = new FileInputStream(uploadFile);

       byte[] buffer = new byte[4096];

       int bytesRead = -1;

       while ((bytesRead = inputStream.read(buffer)) != -1) {

           outputStream.write(buffer, 0, bytesRead);

       }

       outputStream.flush();

       inputStream.close();


       writer.append(LINE_FEED);

       writer.flush();

   }


   public void addHeaderField(String name, String value) {

       writer.append(name + ": " + value).append(LINE_FEED);

       writer.flush();

   }


   public List<String> finish() throws IOException {

       List<String> response = new ArrayList<String>();


       writer.append(LINE_FEED).flush();

       writer.append("--" + boundary + "--").append(LINE_FEED);

       writer.close();


       int status = httpConn.getResponseCode();


       BufferedReader reader = new BufferedReader(new InputStreamReader(

               httpConn.getInputStream()));

       String line = null;

       while ((line = reader.readLine()) != null) {

           response.add(line);

       }

       reader.close();

       httpConn.disconnect();


       return response;

   }

}

 

實際範例


以一個實際的範例來進行說明.

建立一個專案, 有一個Button可以連接至特定網站將指定的Url網頁內容轉換成為一個pdf檔案, 放在行動裝置的外存空間, 再由另一個Button將該檔案上傳到特定的伺服器.

 

因為該專案將會進行網際網路, 存取外存空間, 因此在AndroidManifest.xml中設定使用權限:

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

   <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

 

版面配置上, 加上兩個Button:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout 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="tw.brad.ch10_upload.MainActivity"

   android:orientation="vertical"

   >


   <Button

       android:layout_width="match_parent"

       android:layout_height="wrap_content"

       android:text="Download"

       android:textAllCaps="false"

       android:onClick="download"

       />

   <Button

       android:layout_width="match_parent"

       android:layout_height="wrap_content"

       android:text="Upload"

       android:textAllCaps="false"

       android:onClick="upload"

       />


</LinearLayout>

 

前置處理所需要的設定. 分別是下載檔案路徑及檔案, 以及所需要的執行階段權限:

   private File downloadPath, downloadFile;


   @Override

   protected void onCreate(Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);

       setContentView(R.layout.activity_main);


       if (ContextCompat.checkSelfPermission(this,

               Manifest.permission.WRITE_EXTERNAL_STORAGE)

               != PackageManager.PERMISSION_GRANTED) {


           ActivityCompat.requestPermissions(this,

                   new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,

                           Manifest.permission.READ_EXTERNAL_STORAGE},

                   1);

       }else{

           init();

       }

   }


   @Override

   public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

       super.onRequestPermissionsResult(requestCode, permissions, grantResults);

       if (grantResults.length > 0

               && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

           init();

       }

   }


   private void init(){

       downloadPath = Environment.getExternalStoragePublicDirectory(

               Environment.DIRECTORY_DOWNLOADS);

       downloadFile = new File(downloadPath, "brad.pdf");

   }

 

至此已經預備將檔案下載到downloadFile

處理檔案下載到行動裝置的部分:

   public void download(View view){

       new DownloadTask().execute();

   }


   private class DownloadTask extends AsyncTask {

       @Override

       protected Object doInBackground(Object[] objects) {

           try {

               URL url = new URL("http://pdfmyurl.com/?url=http://www.pchome.com.tw");

               HttpURLConnection connection =

                       (HttpURLConnection) url.openConnection();

               connection.connect();

               BufferedInputStream bufferedInputStream =

                       new BufferedInputStream(connection.getInputStream());

               BufferedOutputStream bufferedOutputStream =

                       new BufferedOutputStream(new FileOutputStream(downloadFile));

               byte[] buf = new byte[4096]; int readLen = -1;

               while ( (readLen = bufferedInputStream.read(buf)) != -1){

                   bufferedOutputStream.write(buf,0,readLen);

               }

               bufferedOutputStream.flush();

               bufferedOutputStream.close();

               bufferedInputStream.close();


           }catch (Exception e){

               Log.i("brad", "ERROR: " + e.toString());

           }

           return null;

       }


       @Override

       protected void onPostExecute(Object o) {

           super.onPostExecute(o);

           Log.i("brad", "Download OK");

       }

   }

 

再來就搭配一開始就處理好的MultipartUtility類別即可輕鬆處理上傳機制.

   public void upload(View view){

       new UploadTask().execute();

   }


   private class UploadTask extends AsyncTask {

       @Override

       protected Object doInBackground(Object[] objects) {

           try {

               MultipartUtility multipartUtility =

                       new MultipartUtility(

                               "http://10.0.2.2/bradserver/apptest/post2.php",null,"UTF-8");

               multipartUtility.addFilePart("upload", downloadFile);

               multipartUtility.finish();


           } catch (IOException e) {

               e.printStackTrace();

           }

           return null;

       }


       @Override

       protected void onPostExecute(Object o) {

           super.onPostExecute(o);

           Log.i("brad", "Upload OK");

       }

   }

WebView


如果想要呈現的內容, 想要以一般瀏覽器的效果為主, 則可使用WebView進行使用者介面的呈現處理, 而以HTML + CSS + JavaScript進行內容架構規劃, 通常會以響應式網頁設計(Responsive Web Design)規劃而能同時適用於一般瀏覽器, 平板電腦及手機.  

如果資料內容並不需要透過網際網路存取資料, 則不需要要求使用者網際網路的權限. 以下先介紹使用這種專案資源的WebView應用方式.

先在 File → New → Folder → Assets Folder,

出現以下對話框, 直接點擊Finish即可.

之後將會在Project的app架構下產生一個新的assets資料夾.

 

接下來在此資料夾下建立一個一般的檔案, 假設檔案名稱為main.html, 而將在此進行HTML的網頁設計處理. 例如:

<h1>Brad Big Company</h1>

<hr />

Hello, World

 

實作layout的版面配置處理, app → res → layout → activity_main.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout 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"

  tools:context="tw.brad.ch10_webview1.MainActivity">


  <WebView

      android:id="@+id/webView"

      android:layout_width="match_parent"

      android:layout_height="match_parent"

      />


</LinearLayout>

 

放置了一個WebView, 並設定其 id 為 webView. 回到 MainActivity.java

import android.os.Bundle;

import android.support.v7.app.AppCompatActivity;

import android.webkit.WebView;


public class MainActivity extends AppCompatActivity {

  private WebView webView;


  @Override

  protected void onCreate(Bundle savedInstanceState) {

      super.onCreate(savedInstanceState);

      setContentView(R.layout.activity_main);


      webView = (WebView)findViewById(R.id.webView);

      initWebView();

  }


  private void initWebView(){

      webView.loadUrl("file:///android_asset/main.html");

  }


}

 

使WebView載入專案中網頁檔案資源, 呼叫其loadUrl(“專案資源檔案Url字串”). 而專案資源的格式為: file:///android_asset/檔案名稱, 將會對應到 app → assets → 檔案名稱. 也就是說, 通訊協定為file://, 而資料的根為/android_assets, 存取main.hmtl檔案資源.

呈現結果如下圖:

如果WebView所載入的內容來自於網際網路, 則必須先開啟使用權限.

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

使WebView載入網際網路的網頁檔案資源, 也是呼叫其loadUrl()方法, 傳遞的字串參數與一般瀏覽器的網址列輸入一樣.

 

巡訪網頁


網頁之間通常都會透過url的切換, 在預設的狀況下, 當使用者點擊了一個轉換網頁的url的時候, 對於WebView而言, 則是對系統要求發送了一個Uri的Intent Action, 而將啟動該行動裝置上的瀏覽器啟動. 但是, 這網忘不是開發者所想要的模式, 而應該是在原本的WebView來呈現要轉換網頁的內容. 針對這要需求, 必須使WebView設定一個WebViewClient物件實體, 才將會具有網頁用戶端的角色.

webView.setWebViewClient(new WebViewClient());

 

預設的狀況下, WebView可能不支援使用JavaScript, 而要使WebView支援使用JavaScript, 則必須透過其物件實體呼叫getSettings()取得WebSettings物件實體後, 進行呼叫setJavaScriptEnabled()方法.

WebSettings webSettings = webView.getSettings();

webSettings.setJavaScriptEnabled(true);

 

雖然一經支援使用JavaScript, 但是卻在使用alert(), prompt()或是confirm()時, 發現並沒有任何浮現對話框出現, 此時, 必須先為WebView物件實體設定一個WebChromeClient物件, 並且使其WebSettings物件設定setJavaScriptCanOpenWindowsAutomatically()為true.

private void initWebView(){

  webView.setWebViewClient(new WebViewClient());

  webView.setWebChromeClient(new WebChromeClient());


  WebSettings webSettings = webView.getSettings();

  webSettings.setJavaScriptEnabled(true);

  webSettings.setJavaScriptCanOpenWindowsAutomatically(true);


  webView.loadUrl("file:///android_asset/main.html");

}

 

最後, 整合一下JavaScript的應用, 將main.html 改寫如下:

<script src="js/jquery-3.2.1.min.js"></script>

<script>

  function test1(){

      $("#name").html("Brad")

  }

  function test2(){

      alert("Hello, Brad")

  }


</script>

<h1>Brad Big Company</h1>

<hr />

Hello, <span id='name'>World</span><br />

<a href="page2.html">Page 2</a>

<hr />

<button onclick="test1()">Test 1</button><br />

<button onclick="test2()">Test 2</button><br />

同時, 也下載了jquery放在 app → assets → js 資料夾下. test1()將會發現JavaScript已經可以使用在jquery, 而test2()則可以浮現JavaScript的對話框.

 

Android與WebView之互動方式

Android呼叫JavaScript方法


先在網頁資源檔案中加上一個JavaScript的function定義, 例如:

function callFromAndroid(name) {

  $("#name").html(name)

}

 

以下範例稍微修改一下版面配置如下:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout 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"

  tools:context="tw.brad.ch10_webview1.MainActivity">


  <RelativeLayout

      android:layout_width="match_parent"

      android:layout_height="wrap_content">

      <Button

          android:id="@+id/test3"

          android:layout_width="wrap_content"

          android:layout_height="wrap_content"

          android:text="Test3"

          android:textAllCaps="false"

          android:onClick="test3"

          android:layout_alignParentRight="true"

          />

      <EditText

          android:id="@+id/inputName"

          android:layout_width="match_parent"

          android:layout_height="wrap_content"

          android:layout_alignParentLeft="true"

          android:layout_alignTop="@id/test3"

          android:layout_alignBottom="@id/test3"

          />

  </RelativeLayout>

  <WebView

      android:id="@+id/webView"

      android:layout_width="match_parent"

      android:layout_height="match_parent"

      />

</LinearLayout>

 

回到MainActivity.java:

private EditText inputName;

……

@Override

protected void onCreate(Bundle savedInstanceState) {

  super.onCreate(savedInstanceState);

  setContentView(R.layout.activity_main);


  inputName = (EditText)findViewById(R.id.inputName);


  webView = (WebView)findViewById(R.id.webView);

  initWebView();

}


private void initWebView(){

  webView.setWebViewClient(new WebViewClient());

  webView.setWebChromeClient(new WebChromeClient());


  WebSettings webSettings = webView.getSettings();

  webSettings.setJavaScriptEnabled(true);

  webSettings.setJavaScriptCanOpenWindowsAutomatically(true);


  webView.loadUrl("file:///android_asset/main.html");

}


public void test3(View view){

  String name = inputName.getText().toString();

  webView.loadUrl("javascript:callFromAndroid('" + name + "')");

}

 

在Android中, 仍然呼叫WebView之loadUrl()方法, 透過傳遞字串格式: “javascript:呼叫的方法(傳遞參數)”, 即可達到此一目的.

可以應用的場合非常多, 例如以 WebView來呈現網頁的Google Map, 而由Android來取得使用者當前的所在位置的經緯度資訊, 傳遞給WebView, 即時變更地圖中心位置相關資訊, 或是導航資訊等等.

 

JavaScript觸發Android的方式


反過來的應用, 就是在WebView的JavaScript中可以傳遞資料給Android, 處理上是透過一個JavaScript在Android端的介面物件來進行處理.

 

先在Android中定義一個自訂類別, 如下:

public class MyJSObject {


  @JavascriptInterface

  public void callFromJavaScript(String mesg) {

      Toast.makeText(MainActivity.this, mesg, Toast.LENGTH_SHORT).show();

  }

}

設計重點如下:

  • 一般Java類別定義
  • 提供的物件方法, 必須是public的存取修飾字, 並加上@JavascriptInterface
  • 如果有參數, 通常是String型別

 

在由WebView物件實體, 呼叫addJavascriptInterface()方法引入該自訂類別的物件實體, 並提供自訂的介面名稱, 此將提供給WebView的JavaScript呼叫使用.

webView.addJavascriptInterface(new MyJSObject(), "Brad");

 

回到JavaScript來處理:

function test3(){

  Brad.callFromJavaScript("Brad");

}

此處的”Brad”, 就是在WebView所設定的介面名稱. 如此處理, 即可將JavaScript的資料提供給Android進行處理.

 

Android 4.4 之後的WebView


在Android 4.4+ 之後的版本, 將以不同的WebView進行實作, 這是基於Chromium實作的新版本WebView, 除了增進其效能, 並且支援標準的HTML5, CSS3, 以及最新的網頁瀏覽器的JavaScript. 因此建議讀者將專案的targetSdkVersion設定在19以上.

 

本站資源一切隨緣,
不用註冊, 不看廣告
如果對您有所助益,
歡迎功德隨喜, 金額隨意,
請點擊以下...(感謝您)

功德箱/打賞箱

%d bloggers like this: