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

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

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

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

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

استخدام WebView على إصدارات Android السابقة

لاستخدام إمكانات WebView الأحدث بأمان على الجهاز الذي يتم تشغيل تطبيقك عليه ، أضِف مكتبة Jetpack Webkit. هذه مكتبة ثابتة يمكنك إضافتها إلى تطبيقك لاستخدام واجهات برمجة التطبيقات 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 للوصول إلى الفئة.

لمزيد من المعلومات عن التواصل بين JavaScript والرمز البرمجي الأصلي، بما في ذلك واجهات برمجة التطبيقات الأكثر حداثة وأمانًا، يُرجى الاطّلاع على الوصول إلى واجهات برمجة التطبيقات الأصلية باستخدام JSBridge.

على سبيل المثال، يمكنك تضمين الفئة التالية في تطبيق 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);
}

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

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 أي إجراء بعد أن يصل المستخدم إلى نهاية السجلّ.

للحفاظ على سجلّ التنقّل أثناء إعادة إنشاء النشاط أو استعادة العملية في الخلفية ، استخدِم saveState لتسلسل سجلّ التنقّل في WebView's في Bundle، وrestoreState لاستعادته. للحصول على إرشادات حول إدارة حدود حجم الحالة المحفوظة بأمان وتجنُّب TransactionTooLargeException، يُرجى الاطّلاع على إدارة حالة WebView بكفاءة.

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

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

لتعديل السلوك التلقائي لنشاطك، يمكنك تغيير طريقة تعامله مع orientation تغييرات في البيان. لمزيد من المعلومات عن التعامل مع تغييرات الإعدادات أثناء وقت التشغيل، يُرجى قراءة مقالة التعامل مع تغييرات الإعدادات.

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

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

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