إنشاء تطبيقات ويب في WebView

استخدِم WebView لإرسال تطبيق ويب أو صفحة ويب كجزء من تطبيق العميل. فئة WebView هي إضافة إلى فئة View في Android التي تتيح لك عرض صفحات الويب كجزء من تنسيق نشاطك. ولا يتضمّن ميزات متصفح ويب مطوّر بالكامل، مثل عناصر التحكّم في التنقّل أو شريط العناوين. لا يؤدي WebView تلقائيًا سوى إلى عرض صفحة ويب.

يمكن أن تساعدك WebView في تقديم معلومات في تطبيقك قد تحتاج إلى تعديلها، مثل اتفاقية المستخدم النهائي أو دليل المستخدم. ضمن تطبيق Android، يمكنك إنشاء Activity يحتوي على WebView، ثم استخدامه لعرض المستند المستضاف على الإنترنت.

يمكن أن يساعدك WebView أيضًا عندما يقدّم تطبيقك بيانات للمستخدم تتطلّب الاتصال بالإنترنت لاسترداد البيانات، مثل البريد الإلكتروني. في هذه الحالة، قد تجد أنّه من الأسهل إنشاء WebView في تطبيق Android يعرض صفحة ويب تتضمّن جميع بيانات المستخدمين، بدلاً من تنفيذ طلب شبكة، ثم تحليل البيانات وعرضها بتنسيق Android. بدلاً من ذلك، يمكنك تصميم صفحة ويب مخصّصة للأجهزة التي تعمل بنظام التشغيل Android، ثم تنفيذ WebView في تطبيق Android الذي يحمّل صفحة الويب.

يوضّح هذا المستند كيفية بدء استخدام WebView وكيفية ربط JavaScript من صفحة الويب برمز جهة العميل في تطبيق Android وكيفية معالجة التنقّل في الصفحة وكيفية إدارة النوافذ عند استخدام WebView.

العمل مع WebView على إصدارات Android الأقدم

لاستخدام إمكانات WebView الأحدث بأمان على الجهاز الذي يعمل عليه تطبيقك، أضِف مكتبة AndroidX Webkit. هذه مكتبة static يمكنك إضافتها إلى تطبيقك لاستخدام واجهات برمجة تطبيقات android.webkit التي لا تتوفّر لإصدارات النظام الأساسي السابقة.

أضِفه إلى ملف build.gradle على النحو التالي:

Kotlin

dependencies {
    implementation("androidx.webkit:webkit:1.8.0")
}

رائع

dependencies {
    implementation ("androidx.webkit:webkit:1.8.0")
}

يمكنك الاطّلاع على WebView مثال على GitHub للحصول على مزيد من التفاصيل.

إضافة WebView إلى تطبيقك

لإضافة WebView إلى تطبيقك، يمكنك تضمين عنصر <WebView> في تنسيق النشاط أو ضبط نافذة Activity بأكملها على أنّها WebView في onCreate().

إضافة WebView في تنسيق النشاط

لإضافة WebView إلى تطبيقك في التنسيق، أضِف الرمز التالي إلىملف XML لتنسيق نشاطك:

<WebView
    android:id="@+id/webview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
/>

لتحميل صفحة ويب في WebView، استخدِم loadUrl()، كما هو موضح في المثال التالي:

Kotlin

val myWebView: WebView = findViewById(R.id.webview)
myWebView.loadUrl("http://www.example.com")

Java

WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.loadUrl("http://www.example.com");

إضافة WebView في onCreate()

لإضافة WebView إلى تطبيقك في طريقة onCreate() لنشاط معيّن بدلاً من ذلك، استخدِم منطقًا مشابهًا لما يلي:

Kotlin

val myWebView = WebView(activityContext)
setContentView(myWebView)

Java

WebView myWebView = new WebView(activityContext);
setContentView(myWebView);

بعد ذلك، حمِّل الصفحة:

Kotlin

myWebView.loadUrl("http://www.example.com")

Java

myWebView.loadUrl("https://www.example.com");

أو يمكنك تحميل عنوان URL من سلسلة HTML:

Kotlin

// Create an unencoded HTML string, then convert the unencoded HTML string into
// bytes. Encode it with base64 and load the data.
val unencodedHtml =
     "<html><body>'%23' is the percent code for ‘#‘ </body></html>";
val encodedHtml = Base64.encodeToString(unencodedHtml.toByteArray(), Base64.NO_PADDING)
myWebView.loadData(encodedHtml, "text/html", "base64")

Java

// Create an unencoded HTML string, then convert the unencoded HTML string into
// bytes. Encode it with base64 and load the data.
String unencodedHtml =
     "<html><body>'%23' is the percent code for ‘#‘ </body></html>";
String encodedHtml = Base64.encodeToString(unencodedHtml.getBytes(),
        Base64.NO_PADDING);
myWebView.loadData(encodedHtml, "text/html", "base64");

يجب أن يكون تطبيقك متصلاً بالإنترنت. للوصول إلى الإنترنت، اطلب إذن INTERNET فيملف البيان، كما هو موضّح في المثال التالي:

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

يمكنك تخصيص WebView من خلال تنفيذ أيٍّ ممّا يلي:

  • تفعيل إمكانية استخدام وضع ملء الشاشة باستخدام WebChromeClient يتم أيضًا استدعاء هذه الفئة عندما يحتاج WebView إلى إذن لتغيير واجهة مستخدم التطبيق المضيف، مثل إنشاء النوافذ أو إغلاقها أو إرسال مربّعات حوار JavaScript إلى المستخدِم. لمزيد من المعلومات عن تصحيح الأخطاء في هذا السياق، يُرجى الاطّلاع على مقالة تصحيح أخطاء التطبيقات المبرمَجة على الويب.
  • التعامل مع الأحداث التي تؤثّر في عرض المحتوى، مثل الأخطاء في عمليات إرسال نماذج أو التنقل باستخدام WebViewClient يمكنك أيضًا استخدام هذه الفئة الفرعية لمنع تحميل عناوين URL.
  • تفعيل JavaScript من خلال تعديل WebSettings
  • استخدام JavaScript للوصول إلى كائنات إطار عمل Android التي تم إدخالها في WebView

استخدام JavaScript في WebView

إذا كانت صفحة الويب التي تريد تحميلها في WebView تستخدم JavaScript، عليك تفعيل JavaScript في WebView. بعد تفعيل JavaScript، يمكنك إنشاء واجهات بين رمز تطبيقك ورمز JavaScript.

تفعيل JavaScript

تكون JavaScript غير مفعّلة في WebView تلقائيًا. يمكنك تفعيلها من خلال WebSettings المرفق بجهاز WebView. استردّ WebSettings باستخدام getSettings()، ثم فعِّل JavaScript باستخدام setJavaScriptEnabled().

راجِع المثال التالي:

Kotlin

val myWebView: WebView = findViewById(R.id.webview)
myWebView.settings.javaScriptEnabled = true

Java

WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);

WebSettings يتيح الوصول إلى مجموعة متنوعة من الإعدادات الأخرى التي قد تجدها مفيدة. على سبيل المثال، إذا كنت تُطوّر تطبيق ويب مصمّمًا خصيصًا لـ WebView في تطبيق Android، يمكنك تحديد سلسلة مخصّصة لوكيل المستخدم باستخدام setUserAgentString()، ثم طلب معلومات من وكيل المستخدم المخصّص في صفحة الويب للتحقّق من أنّ العميل الذي يطلب صفحة الويب هو تطبيق Android.

ربط رمز JavaScript برمز Android

عند تطوير تطبيق ويب مصمّم خصيصًا للصفحة WebView في تطبيق Android، يمكنك إنشاء واجهات بين رمز JavaScript و رمز Android من جهة العميل. على سبيل المثال، يمكن لرمز JavaScript استدعاء طريقة في رمز Android لعرض Dialog، بدلاً من استخدام دالة alert() في JavaScript.

لربط واجهة جديدة بين رمز JavaScript ورمز Android، استخدِم addJavascriptInterface()، مع تمرير مثيل فئة لربطه برمز JavaScript واسم واجهة يمكن لرمز JavaScript الاتصال به للوصول إلى الفئة.

على سبيل المثال، يمكنك تضمين الفئة التالية في تطبيق Android:

Kotlin

/** Instantiate the interface and set the context.  */
class WebAppInterface(private val mContext: Context) {

    /** Show a toast from the web page.  */
    @JavascriptInterface
    fun showToast(toast: String) {
        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show()
    }
}

Java

public class WebAppInterface {
    Context mContext;

    /** Instantiate 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();
    }
}

في هذا المثال، تسمح فئة WebAppInterface لصفحة الويب بإنشاء رسالة Toast باستخدام الطريقة showToast().

يمكنك ربط هذه الفئة برمز JavaScript الذي يتم تشغيله في WebView باستخدام addJavascriptInterface()، كما هو موضّح في المثال التالي:

Kotlin

val webView: WebView = findViewById(R.id.webview)
webView.addJavascriptInterface(WebAppInterface(this), "Android")

Java

WebView webView = (WebView) findViewById(R.id.webview);
webView.addJavascriptInterface(new WebAppInterface(this), "Android");

يؤدي ذلك إلى إنشاء واجهة تُسمى Android لبرنامج JavaScript الذي يتم تشغيله في WebView. في هذه المرحلة، يمكن لتطبيق الويب الوصول إلى فئة WebAppInterface. على سبيل المثال، إليك بعض علامات HTML وJavaScript التي تنشئ رسالة مصغّرة باستخدام الواجهة الجديدة عندما ينقر المستخدم على زر:

<input type="button" value="Say hello" onClick="showAndroidToast('Hello Android!')" />

<script type="text/javascript">
    function showAndroidToast(toast) {
        Android.showToast(toast);
    }
</script>

ليس عليك إعداد واجهة Android من JavaScript. توفّر علامة WebView المحتوى تلقائيًا على صفحة الويب. لذلك، عندما ينقر مستخدم على الزر، تستخدِم الدالة showAndroidToast() واجهة Android لاستدعاء طريقة WebAppInterface.showToast().

التحكّم في التنقّل في الصفحة

عندما ينقر المستخدم على رابط من صفحة ويب في WebView، يبدأ Android تلقائيًا في تشغيل تطبيق يعالج عناوين URL. وعادةً ما يتم فتح متصفّح الويب التلقائي ويحمل عنوان URL الوجهة. ومع ذلك، يمكنك إلغاء هذا السلوك في WebView لفتح الروابط داخل WebView. يمكنك بعد ذلك السماح للمستخدم بالتنقّل للأمام والخلف من خلال سجلّ صفحات الويب الذي يحتفظ به WebView.

لفتح الروابط التي ينقر عليها المستخدم، قدِّم WebViewClient لWebView باستخدام setWebViewClient(). يتم تحميل جميع الروابط التي ينقر عليها المستخدم في WebView. إذا كنت تريد التحكّم بشكل أكبر في مكان تحميل الرابط الذي تم النقر عليه، أنشئ WebViewClient خاصًا بك يحلّ محلّ الأسلوب shouldOverrideUrlLoading(). يفترض المثال التالي أنّ MyWebViewClient فئة داخلية لـ Activity.

Kotlin

private class MyWebViewClient : WebViewClient() {

    override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
        if (Uri.parse(url).host == "www.example.com") {
            // This is your website, so don't override. Let your WebView load
            // the page.
            return false
        }
        // Otherwise, the link isn't for a page on your site, so launch another
        // Activity that handles URLs.
        Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
            startActivity(this)
        }
        return true
    }
}

Java

private class MyWebViewClient extends WebViewClient {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
        if ("www.example.com".equals(request.getUrl().getHost())) {
      // This is your website, so don't override. Let your WebView load the
      // page.
      return false;
    }
    // Otherwise, the link isn't for a page on your site, so launch another
    // Activity that handles URLs.
    Intent intent = new Intent(Intent.ACTION_VIEW, request.getUrl());
    startActivity(intent);
    return true;
  }
}

بعد ذلك، أنشئ مثيلًا لهذا WebViewClient الجديد للWebView:

Kotlin

val myWebView: WebView = findViewById(R.id.webview)
myWebView.webViewClient = MyWebViewClient()

Java

WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.setWebViewClient(new MyWebViewClient());

الآن عندما ينقر المستخدم على رابط، يستدعي النظام الأسلوب shouldOverrideUrlLoading() الذي يتحقّق مما إذا كان مضيف عنوان URL يتطابق مع نطاق معيّن، كما هو محدّد في المثال السابق. وفي حال تطابق العنوان، تعرِض الوسيطة قيمة خاطئة ولا تلغي تحميل عنوان URL. ويسمح ذلك لعلامة WebView بتحميل عنوان URL كالمعتاد. إذا لم يتطابق مضيف عنوان URL، يتم إنشاء Intent لتشغيل Activity التلقائي لمعالجة عناوين URL، والذي يُحلّ إلى متصفّح الويب التلقائي للمستخدم.

عناوين URL المخصّصة للأسماء المعرِّفة

WebView تفرض قيودًا عند طلب الموارد وتحليل الروابط التي تستخدم مخطّط عنوان URL مخصّصًا. على سبيل المثال، في حال تنفيذ وظائف الاستدعاء مثل shouldOverrideUrlLoading() أو shouldInterceptRequest()، لن يستدعي WebView هذه الدوال إلا لعناوين URL الصالحة.

على سبيل المثال، قد لا يستدعي WebView طريقة shouldOverrideUrlLoading() للروابط التالية:

<a href="showProfile">Show Profile</a>

يتم التعامل مع عناوين URL غير الصالحة، مثل العنوان المعروض في المثال السابق، بشكل غير متّسق في WebView، لذا ننصحك باستخدام عنوان URL صالح بدلاً من ذلك. يمكنك استخدام مخطّط مخصّص أو عنوان URL لبروتوكول HTTPS لنطاق تديره مؤسستك.

بدلاً من استخدام سلسلة بسيطة في رابط، كما هو الحال في المثال السابق، يمكنك استخدام مخطّط مخصّص مثل ما يلي:

<a href="example-app:showProfile">Show Profile</a>

يمكنك بعد ذلك التعامل مع عنوان URL هذا في طريقة shouldOverrideUrlLoading() على النحو التالي:

Kotlin

// The URL scheme must be non-hierarchical, meaning no trailing slashes.
const val APP_SCHEME = "example-app:"

override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
    return if (url?.startsWith(APP_SCHEME) == true) {
        urlData = URLDecoder.decode(url.substring(APP_SCHEME.length), "UTF-8")
        respondToData(urlData)
        true
    } else {
        false
    }
}

Java

// The URL scheme must be non-hierarchical, meaning no trailing slashes.
private static final String APP_SCHEME = "example-app:";

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    if (url.startsWith(APP_SCHEME)) {
        urlData = URLDecoder.decode(url.substring(APP_SCHEME.length()), "UTF-8");
        respondToData(urlData);
        return true;
    }
    return false;
}

تهدف واجهة برمجة التطبيقات shouldOverrideUrlLoading() في المقام الأول إلى إطلاق النوايا لعناوين URL معيّنة. عند تنفيذها، احرص على عرض false لعناوين URL الأسماء المعرِّفة WebView. لا تقتصر على إطلاق النوايا. يمكنك استبدال نوايا الإطلاق بأي سلوك مخصّص في نماذج الرمز المبرمَج السابق.

عندما تلغي WebView تحميل عنوان URL، تُجمِّع تلقائيًا سجلًّا بصفحات الويب التي تمت زيارتها. يمكنك الانتقال للأمام وللخلف في السجلّ باستخدام goBack() و goForward().

على سبيل المثال، يوضّح ما يلي كيفية استخدام Activity زر الرجوع في الجهاز للتنقّل للخلف:

Kotlin

override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
    // Check whether the key event is the Back button and if there's history.
    if (keyCode == KeyEvent.KEYCODE_BACK && myWebView.canGoBack()) {
        myWebView.goBack()
        return true
    }
    // If it isn't the Back button or there isn't web page history, bubble up to
    // the default system behavior. Probably exit the activity.
    return super.onKeyDown(keyCode, event)
}

Java

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

إذا كان تطبيقك يستخدم الإصدار 1.6.0 من AndroidX AppCompat أو الإصدارات الأحدث، يمكنك تبسيط مقتطف الرمز البرمجي السابق بشكل أكبر:

Kotlin

onBackPressedDispatcher.addCallback {
    // Check whether there's history.
    if (myWebView.canGoBack()) {
        myWebView.goBack()
    }
}

Java

onBackPressedDispatcher.addCallback {
    // Check whether there's history.
    if (myWebView.canGoBack()) {
        myWebView.goBack();
    }
}

تُعرِض الطريقة canGoBack() قيمة صحيحة إذا كان هناك سجلّ لصفحات الويب التي زارها المستخدم. وبالمثل، يمكنك استخدام canGoForward() للتحقّق مما إذا كان هناك سجلّ للرسائل المُرسَلة. في حال عدم إجراء هذا التحقّق، لن يؤديgoBack() وgoForward() إلى أي إجراء بعد وصول المستخدم إلى نهاية السجلّ.

التعامل مع التغييرات في إعدادات الجهاز

أثناء التشغيل، تحدث تغييرات في حالة النشاط عند تغيُّر إعدادات الجهاز، مثل عندما يدير المستخدمون الجهاز أو يغلِقون "أداة التعديل الخاصة بطريقة الإدخال" (IME). تؤدي هذه التغييرات إلى إتلاف نشاط كائن WebView وإنشاء نشاط جديد، ما يؤدي أيضًا إلى إنشاء كائن WebView جديد يحمِّل عنوان URL للكائن الذي تم إتلافه. لتعديل السلوك التلقائي لنشاطك، يمكنك تغيير طريقة تعامله مع تغييرات orientation في البيان. لمزيد من المعلومات حول التعامل مع تغييرات الإعدادات أثناء التشغيل، يُرجى الاطّلاع على مقالة التعامل مع تغييرات الإعدادات.

إدارة النوافذ

يتم تلقائيًا تجاهل طلبات فتح نوافذ جديدة. وينطبق ذلك سواءً تم فتحها من خلال JavaScript أو من خلال سمة target في الرابط. يمكنك تخصيص WebChromeClient لضبط السلوك الخاص بك لفتح عدة نوافذ.

للحفاظ على أمان تطبيقك، من الأفضل منع فتح النوافذ المنبثقة والنوافذ الجديدة. إنّ الطريقة الأكثر أمانًا لتنفيذ هذا السلوك هي تمرير "true" إلى setSupportMultipleWindows() ولكن بدون إلغاء الطريقة onCreateWindow() التي تعتمد عليها setSupportMultipleWindows(). يمنع هذا المنطق تحميل أي صفحة تستخدم target="_blank" في روابطها.