Как создать браузер в Android Studio на Kotlin?

Многие начинающие разработчики задумываются о том, как сделать собственный браузер прямо в Android Studio. На первый взгляд задача кажется сложной, но на деле всё сводится к использованию готового компонента WebView, который уже встроен в операционную систему. В этом руководстве мы пройдём путь от пустого проекта до работающего мини-браузера с адресной строкой, кнопками навигации и поддержкой загрузки страниц. Писать будем на Kotlin, а все действия разберём по шагам, чтобы справился даже новичок.


Что понадобится для браузера

Основой будущего браузера станет WebView — компонент, который использует движок WebKit для отображения веб-страниц. Он умеет загружать сайты по URL, выполнять JavaScript и корректно обрабатывать переходы по ссылкам. Помимо него нам потребуются поле ввода адреса, кнопки «Назад», «Вперёд» и «Обновить», а также индикатор загрузки. Все эти элементы мы разместим в одном экране и свяжем с WebView.

Для доступа в интернет нужно добавить разрешение в манифест. Без него WebView не сможет загрузить ни одну страницу. Также стоит разрешить использование незащищённого трафика, если вы планируете тестировать на локальных HTTP-ресурсах.


Добавление разрешений в манифест

Откройте файл AndroidManifest.xml и внутри тега <manifest> добавьте разрешение на интернет. Для поддержки HTTP (не только HTTPS) добавьте атрибут usesCleartextTraffic в тег <application>.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.browser">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.Browser"
        android:usesCleartextTraffic="true">

        <activity android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Обратите внимание, что usesCleartextTraffic стоит включать только для разработки. В реальном проекте лучше использовать HTTPS, чтобы не подвергать пользователей риску.


Создание интерфейса браузера

Разметка браузера состоит из верхней панели с полем ввода и кнопками, и самого WebView, занимающего всё оставшееся место. Для удобства расположим элементы вертикально: сначала строка управления, затем область отображения страницы.

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="8dp">

        <Button
            android:id="@+id/btnBack"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="&lt;" />

        <Button
            android:id="@+id/btnForward"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="&gt;" />

        <EditText
            android:id="@+id/etUrl"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="Введите адрес"
            android:inputType="textUri" />

        <Button
            android:id="@+id/btnGo"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Перейти" />

        <Button
            android:id="@+id/btnRefresh"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="⟳" />
    </LinearLayout>

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

Кнопки «Назад» и «Вперёд» позволят перемещаться по истории посещённых страниц, «Перейти» загрузит введённый URL, а «Обновить» перезагрузит текущую страницу.


Инициализация WebView и обработка навигации

В MainActivity.kt получим ссылки на все элементы, настроим WebView и свяжем кнопки с действиями. Прежде всего включим поддержку JavaScript — без него многие современные сайты будут отображаться некорректно. Затем установим WebViewClient, чтобы переходы по ссылкам происходили внутри WebView, а не открывались во внешнем браузере.

class MainActivity : AppCompatActivity() {

    private lateinit var webView: WebView
    private lateinit var etUrl: EditText

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        webView = findViewById(R.id.webView)
        etUrl = findViewById(R.id.etUrl)
        val btnBack = findViewById<Button>(R.id.btnBack)
        val btnForward = findViewById<Button>(R.id.btnForward)
        val btnGo = findViewById<Button>(R.id.btnGo)
        val btnRefresh = findViewById<Button>(R.id.btnRefresh)

        webView.settings.javaScriptEnabled = true
        webView.webViewClient = WebViewClient()

        btnGo.setOnClickListener {
            var url = etUrl.text.toString().trim()
            if (!url.startsWith("http://") && !url.startsWith("https://")) {
                url = "https://$url"
            }
            webView.loadUrl(url)
        }

        btnBack.setOnClickListener {
            if (webView.canGoBack()) webView.goBack()
        }

        btnForward.setOnClickListener {
            if (webView.canGoForward()) webView.goForward()
        }

        btnRefresh.setOnClickListener {
            webView.reload()
        }
    }
}

Теперь пользователь может ввести адрес вручную и перейти по нему, а кнопки навигации работают по назначению.


Обработка переходов по ссылкам

Чтобы все клики по ссылкам внутри загруженной страницы обрабатывались внутри WebView, нужно переопределить метод shouldOverrideUrlLoading в нашем WebViewClient. По умолчанию WebView уже делает это, но явная реализация даёт больше контроля и гарантирует правильное поведение на всех версиях Android.

webView.webViewClient = object : WebViewClient() {
    override fun shouldOverrideUrlLoading(
        view: WebView?,
        request: WebResourceRequest?
    ): Boolean {
        val url = request?.url?.toString() ?: return false
        view?.loadUrl(url)
        return true
    }
}

Также полезно обновлять адресную строку после перехода, чтобы она всегда показывала актуальный URL. Для этого добавим слушатель doUpdateVisitedHistory, который срабатывает при изменении истории навигации.

webView.webChromeClient = object : WebChromeClient() {
    override fun onProgressChanged(view: WebView?, newProgress: Int) {
        // можно отобразить прогресс загрузки
    }
}

webView.webViewClient = object : WebViewClient() {
    // ...
    override fun doUpdateVisitedHistory(view: WebView?, url: String?, isReload: Boolean) {
        etUrl.setText(url)
    }
}

Индикатор загрузки

Чтобы пользователь видел, что страница загружается, добавим горизонтальный ProgressBar над WebView. В разметке поместим его между панелью управления и WebView:

<ProgressBar
    android:id="@+id/progressBar"
    style="?android:attr/progressBarStyleHorizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:max="100"
    android:visibility="gone" />

В коде подключим WebChromeClient и будем обновлять прогресс в методе onProgressChanged:

webView.webChromeClient = object : WebChromeClient() {
    override fun onProgressChanged(view: WebView?, newProgress: Int) {
        val progressBar = findViewById<ProgressBar>(R.id.progressBar)
        progressBar.progress = newProgress
        if (newProgress == 100) {
            progressBar.visibility = View.GONE
        } else {
            progressBar.visibility = View.VISIBLE
        }
    }
}

Сохранение состояния при повороте экрана

При повороте устройства Activity пересоздаётся, и WebView теряет текущую страницу. Чтобы этого избежать, нужно сохранить состояние в onSaveInstanceState и восстановить в onCreate.

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    webView.saveState(outState)
}

override fun onCreate(savedInstanceState: Bundle?) {
    // ...
    if (savedInstanceState != null) {
        webView.restoreState(savedInstanceState)
    }
}

Теперь после поворота страница останется на месте, и пользователю не придётся заново вводить адрес.


Полезные дополнения

  • Обработка ошибок загрузки. Переопределите onReceivedError в WebViewClient, чтобы показывать сообщение об ошибке вместо пустого экрана.
  • Разрешение на файлы. Для поддержки загрузки файлов используйте DownloadListener и setDownloadListener.
  • Инкогнито-режим. WebView поддерживает settings.setSupportMultipleWindows(false) и другие параметры, которые могут пригодиться при построении приватного браузера.

Что мы получили в итоге

Мы собрали полноценный мини-браузер на базе WebView. Он умеет загружать страницы по URL, отображать ссылки внутри себя, поддерживает кнопки навигации и индикатор загрузки. Код получился компактным, а функциональность можно легко расширять. Попробуйте добавить закладки или историю посещений — это станет отличным продолжением данного урока. Теперь вы знаете, как сделать браузер в Android Studio на Kotlin, и можете гордиться своим первым полноценным приложением.