- Программа должна открывать ссылки глобальной сети, свободно переходить по страничкам вперёд и назад;
- Иметь возможность скачивать файлы и загружать обратно в сеть;
- Создавать закладки и сохранять их;
- Иметь возможность загружать ссылки, отправленные с других приложений;
- Должна быть кнопка домашней страницы, меню с различными настройками и т.д.
В общем, полноценный браузер своими руками. Воплотим это в код.
Программа написана на основе стандартного webview, входящего в Android. В качестве стартовой страницы использую Яндекс, это дело вкуса. В качестве основного Activity будет MainActivity.
Первым делом задаём разметку xml файла -activity_main.xml. В качестве главного контейнера используем LinearLayout — в него заворачиваем ProgressBar для отображения процесса загрузки. Далее создаём ещё один контейнер LinearLayout — в него заворачиваем наш Webview и FrameLayout (его используем для растягивания воспроизводимого видео на весь экран).
LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<ProgressBar
android:id="@+id/progress1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dip"
android:indeterminateDrawable="@drawable/spinner_png"
android:indeterminateOnly="true"
android:layout_gravity="center_horizontal"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<WebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<FrameLayout
android:id="@+id/fullscreen_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
android:visibility="gone"/>
</LinearLayout>
</LinearLayout>
Начнём писать код в MainActivity
Полный код MainActivity.
import java.io.File;
import android.R.menu;
import android.annotation.SuppressLint;
import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.DownloadManager;
import android.app.DownloadManager.Request;
import android.app.KeyguardManager;
import android.app.SearchManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Parcelable;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.webkit.ConsoleMessage;
import android.webkit.DownloadListener;
import android.webkit.ValueCallback;
import android.webkit.WebBackForwardList;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import android.widget.SearchView;
import android.widget.Toast;
import android.graphics.Bitmap;
import android.webkit.URLUtil;
public class MainActivity extends Activity {
//Логическая переменная для статуса соединения
Boolean isInternetPresent = false;
ConnectionDetector cd;
private WebChromeClient.CustomViewCallback mFullscreenViewCallback;
private FrameLayout mFullScreenContainer;
private View mFullScreenView;
private WebView mWebView;
String urload;
int cache = 1;
SharedPreferences sPref;
final Activity activity = this;
public Uri imageUri;
private static final int FILECHOOSER_RESULTCODE = 2888;
private ValueCallback<Uri> mUploadMessage;
private Uri mCapturedImageURI = null;
private DownloadManager downloadManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Создаем пример класса connection detector:
cd = new ConnectionDetector(getApplicationContext());
// создаём кнопу home
final ActionBar actionBar = getActionBar();
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
// ловим intent что файл загружен и оповещаем
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
loadEnd();
}
}
};
// ловим intent что файл загружен
registerReceiver(receiver, new IntentFilter(
DownloadManager.ACTION_DOWNLOAD_COMPLETE));
mWebView = (WebView) findViewById(R.id.web_view);
mFullScreenContainer = (FrameLayout) findViewById(R.id.fullscreen_container);
mWebView.setWebChromeClient(mWebChromeClient);
mWebView.loadUrl("http://yandex.ru");
handleIntent(getIntent());
class HelloWebViewClient extends WebViewClient {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
findViewById(R.id.progress1).setVisibility(View.VISIBLE);
setTitle(url);
urload=mWebView.getUrl();
ConnectingToInternet ();
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
// запускаем ссылки на маркет
Uri uri = Uri.parse(url);
if (uri.getScheme().equals("market")) {
Intent i = new Intent(android.content.Intent.ACTION_VIEW);
i.setData(uri);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
mWebView.canGoBack();
{
mWebView.goBack();
}
}
// запускаем email
if (uri.getScheme().equals("mailto")) {
Intent i = new Intent(android.content.Intent.ACTION_SEND);
i.setType("text/html");
i.putExtra(Intent.EXTRA_SUBJECT, "Введите тему");
i.putExtra(Intent.EXTRA_TEXT, "Введите текст");
i.putExtra(Intent.EXTRA_EMAIL, new String[]{url});
startActivity(i);
mWebView.canGoBack();
{
mWebView.goBack();
}
}
// запускаем звонилку
if (uri.getScheme().equals("tel")) {
Intent i = new Intent(android.content.Intent.ACTION_DIAL);
i.setData(uri);
startActivity(i);
mWebView.canGoBack();
{
mWebView.goBack();
}
}
// запускаем лoкцию
if (uri.getScheme().equals("geo")) {
Intent i = new Intent(android.content.Intent.ACTION_VIEW);
i.setData(uri);
startActivity(i);
mWebView.canGoBack();
{
mWebView.goBack();
}
}
return true;
}
@Override
public void onPageFinished(WebView view, String url) {
findViewById(R.id.progress1).setVisibility(View.GONE);
}
@Override
public void onReceivedError(WebView view, int errorCode,
String description, String failingUrl) {
ConnectingToInternet ();
mWebView.loadUrl("file:///android_asset/error.png");
}
}
mWebView.setWebViewClient(new HelloWebViewClient());
// загрузка файлов на устройство
mWebView.setDownloadListener(new DownloadListener() {
@Override
public void onDownloadStart( final String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
final String fileName = URLUtil.guessFileName(url, contentDisposition, mimetype);
final AlertDialog.Builder downloadDialog = new AlertDialog.Builder(MainActivity.this);
downloadDialog.setTitle("Менеджер загрузок");
downloadDialog.setMessage("Загрузить этот файл в папку Donwload ?" + '\n' + mimetype + '\n' + url);
downloadDialog.setPositiveButton("Да", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
doDownload( url, fileName);
dialogInterface.dismiss();
}
});
downloadDialog.setNegativeButton("Нет", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
}
});
downloadDialog.show();
}
});
}
// ****************************************
//*****************************************
//*****************************************
public void ConnectingToInternet (){
//Получаем статус Интернет соединения
isInternetPresent = cd.ConnectingToInternet();
//Проверяем Интернет статус:
if (isInternetPresent) {
//Интернет соединение есть
//делаем HTTP запросы:
} else {
//Интернет соединения нет
Toast.makeText(this, " Интернет отвалился !!!", Toast.LENGTH_SHORT).show();
}
}
@SuppressLint("SetJavaScriptEnabled")
@Override // настройки
public void onResume(){
super.onResume();
SharedPreferences sPref =
PreferenceManager.getDefaultSharedPreferences(this);
if (sPref.getBoolean("img", false)) {
mWebView.getSettings().setLoadsImagesAutomatically(false);
} else {
mWebView.getSettings().setLoadsImagesAutomatically(true);
}
if (sPref.getBoolean("js", false)) {
mWebView.getSettings().setJavaScriptEnabled(false);
} else {
mWebView.getSettings().setJavaScriptEnabled(true);
}
if (sPref.getBoolean("cache", false)) {
cache = 2;
} else {
cache = 1;
}
}
// пишем закладку
public void saveBm(String urlPage1, String urlTitle1) {
Intent intent = new Intent(this, SaveBmActivity.class);
intent.putExtra("urlTitle", urlTitle1);
intent.putExtra("urlPage", urlPage1);
startActivity(intent);
}
public void pref() { // настройки
Intent intent = new Intent(this, PreferencesActivity.class);
startActivity(intent);
}
// чистим кэш и историю
private void clCache(){
clearCache(activity);
mWebView.clearCache(true);
mWebView.clearHistory();
Toast.makeText(this, "Кеш и История очищены", Toast.LENGTH_SHORT).show();
}
@Override
protected void onUserLeaveHint() {
super.onUserLeaveHint();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {// кнопка назад
if ((keyCode == KeyEvent.KEYCODE_BACK)) {
mWebView.canGoBack();
{
mWebView.goBack();
}
return true;
}
return super.onKeyDown(keyCode, event);
}
// ловим url запустившей программы
private boolean handleIntent(Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_VIEW.equals(action)) {
String url = intent.getDataString();
Toast.makeText(this, url, Toast.LENGTH_SHORT).show();
mWebView.loadUrl(url);// грузим страницу
return true;
}
return false;
}
// менеджер загрузки
private void doDownload(String url,String fileName) {
Uri uriOriginal = Uri.parse(url);
try {
Toast.makeText(MainActivity.this, "Downloading " + fileName, Toast.LENGTH_LONG).show();
Request request = new DownloadManager.Request(Uri.parse(url));
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
final DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
dm.enqueue(request);
} catch (Exception e) {
Toast.makeText(this, "Ошибка", Toast.LENGTH_SHORT).show();
Log.e("", "Problem downloading: " + uriOriginal, e);
}
}
// тянем видео на весь экран
private final WebChromeClient mWebChromeClient = new WebChromeClient() {
@Override
@SuppressWarnings("deprecation")
public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) {
onShowCustomView(view, callback);
}
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
if (mFullScreenView != null) {
callback.onCustomViewHidden();
return;
}
mFullScreenView = view;
mWebView.setVisibility(View.GONE);
mFullScreenContainer.setVisibility(View.VISIBLE);
mFullScreenContainer.addView(view);
mFullscreenViewCallback = callback;
}
@Override
public void onHideCustomView() {
super.onHideCustomView();
if (mFullScreenView == null) {
return;
}
mWebView.setVisibility(View.VISIBLE);
mFullScreenView.setVisibility(View.GONE);
mFullScreenContainer.setVisibility(View.GONE);
mFullScreenContainer.removeView(mFullScreenView);
mFullscreenViewCallback.onCustomViewHidden();
mFullScreenView = null;
}
// ********************************************* грузим файлы в сеть
// openFileChooser for Android 3.0+
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
// Сообщение об обновлении
mUploadMessage = uploadMsg;
try {
// Создать AndroidExampleFolder в sdcard
File imageStorageDir = new File(
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES)
, "AndroidExampleFolder");
if (!imageStorageDir.exists()) {
// Создать AndroidExampleFolder в sdcard
imageStorageDir.mkdirs();
}
// Создать камеру захваченное изображение путь к файлу и имя
File file = new File(
imageStorageDir + File.separator + "IMG_"
+ String.valueOf(System.currentTimeMillis())
+ ".jpg");
mCapturedImageURI = Uri.fromFile(file);
// Камера захвата изображения intent
final Intent captureIntent = new Intent(
MediaStore.ACTION_IMAGE_CAPTURE);
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mCapturedImageURI);
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
// Создать файл селектора intent
Intent chooserIntent = Intent.createChooser(i, "Image Chooser");
// Установить камеры намерении выбора файлов
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS
, new Parcelable[]{captureIntent});
// На выбор изображения обхода метода onactivityresult вызов метода активности
startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE);
} catch (Exception e) {
Toast.makeText(getBaseContext(), "Exception:" + e,
Toast.LENGTH_LONG).show();
}
}
// openFileChooser for Android < 3.0
@SuppressWarnings("unused")
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
openFileChooser(uploadMsg, "");
}
//
@SuppressWarnings("unused")
public void openFileChooser(ValueCallback<Uri> uploadMsg,
String acceptType,
String capture) {
openFileChooser(uploadMsg, acceptType);
}
public boolean onConsoleMessage(ConsoleMessage cm) {
onConsoleMessage(cm.message(), cm.lineNumber(), cm.sourceId());
return true;
}
public void onConsoleMessage(String message, int lineNumber, String sourceID) {
//Log.d("androidruntime", "Show console messages, Used for debugging: " + message);
};
};// End setWebChromeClient
// Получаем результат
@SuppressWarnings("unused")
private Object data; @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (data == null) {
return;
}
String urlPage2 = data.getStringExtra("urlPage2");
mWebView.loadUrl(urlPage2);
if (requestCode == FILECHOOSER_RESULTCODE) {
if (null == this.mUploadMessage) {
return;
}
Uri result = null;
try {
if (resultCode != RESULT_OK) {
result = null;
} else {
// извлечь из собственной переменной, если намерение состоит в нуль
result = data == null ? mCapturedImageURI : data.getData();
}
} catch (Exception e) {
Toast.makeText(getApplicationContext(), "activity :" + e,
Toast.LENGTH_LONG).show();
}
mUploadMessage.onReceiveValue(result);
mUploadMessage = null;
}
}
//*****************************
public void loadEnd () {
Toast.makeText(this, "Файл Загружен в папку Donwload", Toast.LENGTH_SHORT).show();
}
// меню
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
// *******************************************************
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:// кнопка home
mWebView.loadUrl("http://yandex.ru");
return true;
case R.id.item1:// назад
mWebView.canGoBack();
{
mWebView.goBack();
}
return true;
case R.id.item2:
// вперёд
mWebView.canGoForward();
{
mWebView.goForward();
}
return true;
case R.id.item3:
// перезагрузка
mWebView.reload();
{
mWebView.reload();
}
return true;
case R.id.item4:// чистим кеш
mWebView.clearCache(true);
clearCache(activity);
Toast.makeText(this, "Кэш чист.", Toast.LENGTH_SHORT).show();
return true;
case R.id.item5:
mWebView.clearHistory();// чистим историю
Toast.makeText(this, "История чиста.", Toast.LENGTH_SHORT).show();
return true;
case R.id.item6:
saveBm(mWebView.getUrl(), mWebView.getTitle());// пишим закладку
return true;
case R.id.item7:// панель закладок
Intent intent1 = new Intent(this, SaveBmActivity.class);
startActivityForResult(intent1, 1);
return true;
case R.id.item8:
// стоп загрузка
mWebView.stopLoading();
return true;
case R.id.item9:
pref();// настройки
return true;
case R.id.item10:
// пока пусто
return true;
case R.id.item11:// выход
if (cache == 2) {
clCache();
}
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@SuppressWarnings("deprecation")
@Override
public void onDestroy() {
super.onDestroy();
mWebView.stopLoading();
mWebView.clearCache(true);
mWebView.clearView();
mWebView.freeMemory();
mWebView.destroy();
mWebView = null;
}
// чистим кеш
void clearCache(Context context)
{
clearCacheFolder(context.getCacheDir());
}
void clearCacheFolder(final File dir)
{
if (dir!= null && dir.isDirectory())
{
try
{
for (File child:dir.listFiles())
{
//рекурсивно чистим сначала каталоги
if (child.isDirectory())
clearCacheFolder(child);
else //потом собственно файлы
child.delete();
}
}
catch(Exception e)
{
}
}
}
}
Проект можно скачать отсюда.
Комментарии (16)
nizulko
28.01.2016 18:00"// ловим url запустившей программы"
Сейчас, главное, с «таким» минусов не наловить…
Terranz
28.01.2016 18:16+14>android.webkit.WebView
>свой браузер
вы издеваетесь или это замкнулась спираль тех, кто кидал на форму в делфи 5 компонент TWebBrowser с полем ввода да тремя кнопками и называл это «свой браузер»?
teamfighter
28.01.2016 18:16-4trilodi
28.01.2016 18:31+3Юмора ради, забыли упомянуть про:
<uses-permission android:name=«android.permission.INTERNET»></uses-permission>
firexel
28.01.2016 18:47+6Не первый раз уже вижу пост от школьника/студента, который который показывается несколько часов пока его сольют и скроют в черновики. Мне кажется, что для сообществу, состоящему из инженеров разных мастей должно быть по плечу решить подобную системную проблему. Сам, когда получал аккаунт через песочницу 4 года назад, написал очень похожую статью, правда ее не слили.
А давайте представим, что на хабре есть специальный раздел, где вот такие вот совсем зеленые ребята могут выкладывать свои поделки и получать действительно полезные комментарии от специалистов и профессионалов, а не эти стенания о том, что это топ-1 IT ресурс страны? И вот этот вот пост выложен в этом гипотетическом разделе. Вот, например, мои замечания:
- Слишком громкий заголовок статьи. Проект на 1 класс, использующий стандартный системный компонент не может считаться браузером
- Метод донесения информации статьи. Было бы неплохо услышать про процесс реализации, даже вот такого неказистого проекта. Просто вываленный код неинтересно смотреть
- Метод донесения исходного кода. В 2016-м году никто не будет смотреть код из архивов в дропбоксе. Нужно выложить на публичный хост, например github.com
- Стиль кода и реализация. Тут все понятно — автору нужно еще многому научиться. Обязательны к изучению такие книги как «Чистый код» Мартина, «Филисофия Java» Эккеля, и fundamentals из документации Android.
bigfatbrowncat
28.01.2016 20:46+4А давайте представим, что на хабре есть специальный раздел, где вот такие вот совсем зеленые ребята могут выкладывать свои поделки и получать действительно полезные комментарии от специалистов и профессионалов
А давайте представим себе, что мы уже находимся в этом «специальном разделе». Я вполне допускаю, что на хабре есть много специалистов такого уровня, что для них всё, что напишу и я, и вы — это вот такой вот «свой браузер».
Хотя, конечно, автору было бы неплохо немного скромнее формулировать. Вместо «пишем браузер» лучше назвать статью «учимся использовать WebView в Android». Больше никаких серьезных претензий к статье нет.
С вашими рекомендациями согласен.
lazexe
29.01.2016 10:48Абсолютно согласен, и хотелось бы подробнее расписать что именно не так в коде:
0. А отступы?
1. Зачем столько пустых линий? Код не стал читабельнее.
2. Обоснованные именна методов. Это что вообще такое?))
public void pref() { // настройки
Intent intent = new Intent(this, PreferencesActivity.class);
startActivity(intent);
}
3. Ах да, и скобки {} лучше бы были все в одном стиле.
Коментарии сверху отностятся больше к «Чистому коду» Р. Мартина.
А теперь по Android:
0. Смена ориентации экрана? Нет, не слышал.
1. Почему строки/сообщения не находятся в strings.xml?
2. Зачем это? Аннотация говорит сама за себя))
@SuppressWarnings(«unused»)
private Object data;
3. И много других.
Да, браузер слабоват. + я уверен на 99%, что потоковое видео здесь не будет воспроизводиться так как нужно(проверять откровенно поленился).
pfemidi
28.01.2016 19:12Чёрт! Это реально достойно хаба «Разработка под Android»! Такие хитрые твики и недокументированные подходы описаны… Ух!
zagayevskiy
29.01.2016 01:57+4Когда, говорите, вам «Eclips» выдал хэллоу ворлд? Android studio, чтоб всё как у людей.
Текст пишите всегда в ресурсах.
Откажитесь от God-класса.
Уберите это с хабра.
Suvitruf
29.01.2016 03:10+6Меня больше поражает не сама статья, а люди, которые подобные статьи из песочницы пропускают…
IRainman
29.01.2016 15:23+3Приглашен:
28 января 2016 в 17:51 по приглашению НЛО
P.S. я правда чувствую, что сейчас сам окажусь на месте автора ибо «критика власти запрещена», но всё же это факт.
Assada
Вы издеваетесь?
Набор кода в спойлерах практически из документации, ссылка на сомнительный zip? Это статья для топ IT ресурса рунета?