書籍転載:[iOS/Android対応]HTML5ハイブリッドアプリ開発[実践]入門(9)
カスタムURLスキーム方式/iOSで特定のページの読み込みを制限する
JavaScriptからネイティブ側へ命令を投げる方法(Android&iOS対応)を紹介。またiOSでセキュリティのために、特定のドメインのページの読み込みを制限する方法も紹介。書籍転載の9本目。
オープンソースのフレームワーク「Apache Cordova」(Adobe版:「 PhoneGap」)を用いると、HTML5でiOSとAndroid向けのアプリをまとめて作成できます。この連載記事(=書籍転載)の第1回~第6回で、その開発方法を一通り解説しています。また、第7回からは、「JavaScriptコード」と「iOS/Andoridネイティブ機能」をつなぐ仕組みを説明しています。
書籍転載について
ご注意
本記事は、書籍の内容を改変することなく、そのまま転載したものです。このため用字用語の統一ルールなどはBuild Insiderのそれとは一致しません。あらかじめご了承ください。
前回は「addJavascriptInterface方式(後編)/ネイティブからJavaScriptへ値を渡す」を説明しました。本稿はその続きです。
■
11.5 カスタムURLスキーム方式
今度は、カスタムURLスキームを用いてJavaScriptからネイティブ側へ命令を投げる方法を紹介します。この方法はAndroidとiOS、両方のプラットフォームで利用できます。
iOSのUIWebViewクラスとAndroidのWebViewクラスの両方で、オリジナルのスキームを持ったURLに対する読み込み方法を制御できます。例えば「http://」から始まるURLは通常どおりページを読み込ませるけれども、ある特定のスキームを持ったURLを読み込む場合は全く別の挙動をする、といった設定ができます。
この方法では、JavaScriptからネイティブへ値を受け渡すことはできますが、ネイティブから結果を受け取ることができません。したがって、ネイティブからJavaScriptへ値を受け渡す方法も同時に用いられます。
具体的なカスタムURLの例としては、リスト11.14のように「apicall://」というスキーマのURLを読み込もうとした場合には、そのURLに渡されたAPI名やパラメータを用いてネイティブ側でネイティブAPIの呼び出しを行う、といったことを行います。
apicall://(呼び出しAPI名)/?(JSONパラメータ)
|
11.5.1 Androidでの実装
Androidでは、WebViewに対してWebViewClientオブジェクトを設定することで、カスタムURLスキームを扱うときの挙動を設定できます。具体的には、WebViewClientクラスのshouldOverrideUrlLoadingメソッドを設定します。
WebView内で新たなURLを読み込む際には、必ずこのshouldOverrideUrlLoadingメソッドが呼ばれます。このメソッドの返り値にtrueを返すことで、WebViewのデフォルトの挙動を消すことができます。
例えば、WebViewに「http://google.com」というURLにアクセスさせたくない場合には、shouldOverrideUrlLoadingメソッド内で渡されたURLが「http://google.com」を含んでいればtrueを返す、というロジックを記述することで実現できます。
このメソッドの引数には、これから読み込む予定のURLが渡ってきます。このURLがカスタムスキームを含んでいればネイティブ側の処理を呼び出す、そうでなければデフォルトの動作をする、というロジックでこのブリッジは提供されます(リスト11.15)。
package com.example.hybridappsandbox;
import java.net.MalformedURLException;
import java.net.URL;
import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class CustomSchemaExample extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WebView webView = new WebView(this);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new MyWebViewClient());
webView.loadUrl("file:///android_asset/customSchemaExample.html");
setContentView(webView);
}
class MyWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("apicall://")) {
String[] params = url.split("/?");
// apicall://というURLが来たときには、ネイティブの処理を呼び出す
// ...
return true;
}
return false;
}
}
}
|
JavaScript側では、リスト11.16のようにiframe要素やlocation.hrefを利用することでカスタムURLスキームを呼び出すことができます。
<script type="text/javascript">
document.addEventListener('load', function() {
var element = document.createElement('iframe');
element.setAttribute('width', 0);
element.setAttribute('height', 0);
element.setAttribute('src', 'about:blank');
document.body.appendChild(element);
// ネイティブ側へAPI呼び出しをする関数
window.callAPI = function(name, parameters) {
element.src = 'apicall://' name + '/?' + JSON.stringify(parameters);
};
}, false);
</script>
|
11.5.2 iOSでの実装
iOSでは、UIWebViewクラスに対して、delegateとしてUIWebViewDelegateを設定してカスタムURLスキームを実装します。UIWebViewクラスでなんらかのURLが読み込まれる前に、UIWebViewDelegateのwebView:shouldStartLoadWithRequest:navigationType:というメソッドが呼ばれます。このメソッドがYESを返せば、そのURLの読み込みが実行されます。逆にNOを返すと、そのURLに対する読み込みは実行されません。
リスト11.17は、「apicall://」というカスタムURLスキームを使ってJavaScriptからネイティブへの機能を呼び出す例です。UIViewControllerクラスを継承しているので、そのまま利用されます。
#import <UIKit/UIKit.h>
@interface CustomSchemaExampleViewController : UIViewController
<UIWebViewDelegate>
@end
@implementation CustomSchemaExampleViewController
- (void)viewDidLoad
{
[super viewDidLoad];
CGRect screenRect = UIScreen.mainScreen.bounds;
UIWebView *webView =
[UIWebView.alloc initWithFrame:CGRectMake(0, 0,
screenRect.size.width, screenRect.size.height)];
NSURL *url = [NSURL fileURLWithPath:
[NSBundle.mainBundle pathForResource:@"www/custom_schema_example.html"
ofType:nil]];
webView.delegate = self;
[webView loadRequest:[NSURLRequest requestWithURL:url]];
[self.view addSubview:webView];
}
- (BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType
{
NSString *prefix = @"apicall://";
NSString *urlStr = request.URL.absoluteString;
if ([urlStr hasPrefix:prefix]) {
NSLog(@"hogehoge!!");
NSLog(@"host=%@ query=%@", request.URL.host, request.URL.query);
return NO;
}
return YES;
}
@end
|
11.6 iOSで特定のページの読み込みを制限する
AndroidのWebViewと同様に、iOSのUIWebViewクラスでもセキュリティを担保するために特定のドメインのページの読み込みを制限する必要があります。ここでは、iOSでの制限方法を紹介します。
11.6.1 webView:shouldStartLoadWithRequest:navigationType:を用いる
iOSのUIWebViewクラスにはdelegateを設定できます。とくにWebViewDelegateプロトコルのwebView:shouldStartLoadWithRequest:navigationType:メソッドを記述することで、URLによって読み込みの挙動を変えることができます。
リスト11.18のWebViewDelegateに実装したメソッドでは、URLのスキームを「file://」のみ許可して、それ以外のスキーム―例えば「http」や「https」を持つURLへのアクセスを禁止しています。これはカスタムURLスキーム方式と同じです。
- (BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType
{
NSString *prefix = @"file://";
NSString *urlStr = request.URL.absoluteString;
if ([urlStr hasPrefix:prefix]) {
// fileプロトコルの読み込みのみ許可する
return YES;
}
return NO;
}
|
11.6.2 NSURLProtocolクラスを用いる
iOSでは、個別のUIWebViewクラスに対して読み込むURLを制限できるUIWebView Delegateを用いる方法の他にも、NSURLProtocolクラスを用いてURLを制限する方法があります。NSURLProtocolクラスを用いると、どのWebViewに対しても共通の制限を加えることができます。
NSURLProtocolクラスは、NSURLRequestクラスの読み込み処理を自由に拡張するための仕組みを提供しています。canInitWithRequest:メソッドでYESを返すことで、そのリクエストの読み込み処理をstartLoadingメソッドやstopLoadingメソッドで実装できます(リスト11.19)。
@interface MyURLProtocol : NSURLProtocol
@end
@implementation MyURLProtocol
+(BOOL)canInitWithRequest:(NSURLRequest *)request
{
return [request.URL.scheme isEqualToString:@"http"]
|| [request.URL.scheme isEqualToString:@"https"];
}
- (void)startLoading
{
NSLog(@"[DEBUG] startLoading: %@", self.request.URL);
}
- (void)stopLoading
{
NSLog(@"[DEBUG] stopLoading");
}
@end
|
まず、NSURLProtocolクラスを継承したMyURLProtocolクラスを宣言します。ここでは、「http」や「https」のスキームを持つURLをブロックする処理をしています。startLoadingメソッド内では、読み込み処理を記述していないので、結果として「http」と「https」プロトコルを持つURLの読み込みは自動的にブロックされます。
ここで宣言したMyURLProtocolクラスを有効にするには、リスト11.20のように、WebViewを読み込む前にNSURLProtocolクラスのregisterメソッドでMyURLProtocolクラスを追加します。
[NSURLProtocol registerClass:MyURLProtocol.class];
|
■
次回は「JsAlert方式/ローカルHTTPサーバ方式」を説明します。
久保田光則(くぼたみつのり)
東京都在住。アシアル株式会社に所属するUI/UXデザイナー兼ソフトウェアエンジニア。社内では,HTML5ハイブリッドアプリの開発に多数関わる。優れたデザインとエンジニアリングを両立したオーバークオリティなアプリケーションの開発を実現するために日々,頑張る。
アシアル株式会社(あしあるかぶしきがいしゃ、Asial Corporation)
PHPなどのサーバサイドの技術と,PhoneGapなどのスマートフォン関連を中心とした開発を手がける技術ベンチャー。HTML5ハイブリッドアプリをブラウザ上で開発できるMonacaや,PhoneGapの日本語情報を配信するPhoneGap Fanなどのウェブサービスを手がける。
※以下では、本稿の前後を合わせて5回分(第6回~第10回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
6. Android向けのCordovaプラグインを実装する
アプリの一部をネイティブで記述するには、プラグインの実装が必要。そこでAndroid向けにCordovaプラグインを実装する方法を解説。今回で「Cordovaアプリ開発の基礎」に関する部分の転載完了。
7. JavaScriptからネイティブの機能を呼び出す方法 ― addJavascriptInterface方式(前編)
Apache Cordova/Adobe PhoneGapによるハイブリッドアプリ内での、JavaScriptとネイティブとの通信の仕組みを解説する。書籍転載の7本目(「Part 2《実践編》 第11章 JavaScriptとネイティブとのブリッジ」より)。
8. addJavascriptInterface方式(後編)/ネイティブからJavaScriptへ値を渡す
Apache Cordova/Adobe PhoneGapによるハイブリッドアプリ内での、JavaScriptとネイティブとの通信の仕組みを解説。書籍転載の8本目。
9. 【現在、表示中】≫ カスタムURLスキーム方式/iOSで特定のページの読み込みを制限する
JavaScriptからネイティブ側へ命令を投げる方法(Android&iOS対応)を紹介。またiOSでセキュリティのために、特定のドメインのページの読み込みを制限する方法も紹介。書籍転載の9本目。
10. JavaScriptとネイティブの通信の仕組み ― JsAlert方式/ローカルHTTPサーバ方式
ついに書籍転載最終回。Apache Cordova/Adobe PhoneGapによるハイブリッドアプリ内での、JavaScriptとネイティブの通信の仕組みの解説が完結する。