当前位置:首页>Xamarin教程>Android教程>基于Xamarin Android实现的简单的浏览器

基于Xamarin Android实现的简单的浏览器

来源:本站整理


 最近做了一个Android浏览器,当然功能比较简单,主要实现了自己想要的一些功能……现在有好多浏览器为什么还要自己写?当你使用的时候总有那么一些地方不如意,于是就想自己写一个。

  开发环境:Xamarin Android(非Forms)+联想机子(5.0)+荣耀机子(8.0)

  【开发目标】

  1、浏览器的基本功能,关联Http和Https(在另一个APP中打开网页时,可以弹出本应用)

  2、创建应用目录,用来存放离线网页文件

  3、可以离线保存网页(格式为mht)

  4、关联mht和mhtml格式的文件

  【涉及到的技术点】

  1、重写Activity中的OnBackPressed方法,实现webview回退和再按一次退出程序的功能

  2、重写Activity中的OnConfigurationChanged方法,实现横竖屏功能

  【webview相关技术点】

  1、开启一些常用的设置:JavaScriptEnabled、DomStorageEnabled(如果DomStorageEnabled不启用,网页中的下拉刷新和加载更多将不起作用;例子:百度首页加载新闻)

  2、重写WebViewClient中的ShouldOverrideUrlLoading方法,在点击打开网页中的链接时,用自己的webview中打开连接,而不是打开其他的浏览器

  3、重写WebChromeClient中的OnReceivedTitle和OnProgressChanged方法,分别获取页面标题(作为离线文件的名称)和加载进度

  4、采用事件的方式,通知主Activity关于页面加载开始、加载结束、标题、加载进度等的一些事情,进而更新UI(这里和Java的写法有些不同)

  5、页面加载进度条

  【悬浮按钮】1、全屏(退出)按钮  2、保存网页  3、扫描二维码(版本兼容问题尚未实现)

  【网址输入框】

  1、输入正确的网址之后点击输入法中的“前往”调转

  2、隐藏输入法

  以上列到的功能基本实现,最后在荣耀V10上测试时,其他的功能还好,就是在打开离线文件时也不报错,就是打不开……郁闷啊!最后查了一下也没有找到原因。这里说一下场景,以方便大神发现问题,希望大神不吝赐教。在我的联想手机上测试时发现本地文件路径是这样的:file:///storage/emulated/0/DDZMyBrowser/SavePages/1.mht  此时可以正常浏览,而V10中得到的路径是这样的,内部存储:content://com.huawei.hidisk.fileprovider/root/storage/emulated/0/DDZMyBrowser/SavePages/1.mht   SD卡:content://com.huawei.hidisk.fileprovider/root/storage/0ABF-6213/1.mht  这两个都打不开。我查询的结果,这路径应该是利用FileProvider生成的(7.0以上),哎,并非真正的android开发,并不太懂,一脸懵逼,不知道是不是因为这个原因……开始我还寄希望于将content://转为file:///格式的,但是都失败了,最后想想网上说的webview支持content://开头的啊,自己在输入框中手动修改为:file:///storage/emulated/0/DDZMyBrowser/SavePages/1.mht  发现是可以征程浏览的……

  上一下截图:

  1、应用首页

  2、再按一次退出程序

  3、横屏

  4、竖屏

  5、网页中的下拉刷新

  6、加载更多

  7、用自己的webview中打开连接,而不是打开其他的浏览器;进度条

  8、全屏

  9、离线保存

  10、关联MHT

  11、关联HTTP和HTTPS

   12、actionGo

   13、最后再来一张V10加载异常的图片

   去去去,传上去之后发现图片太大了,全是百度的图片……这事儿弄得

  最后在贴一下代码,记录一下

  CS代码:

using Android.App;
using Android.Widget;
using Android.OS;
using Android.Webkit;
using System;
using Android.Support.Design.Widget;
using Android.Content;
using Android.Views;
using Java.IO;
using Android.Views.InputMethods;
using Android.Content.PM;
using Android.Content.Res;
using Android.Provider;
using Android.Database;

namespace DDZ.MyBrowser
{
    /// <summary>
    /// 获取网页Title
    /// </summary>
    /// <param name="title"></param>
    public delegate void MyWebViewTitleDelegate(string title);

    /// <summary>
    /// 获取网页加载进度
    /// </summary>
    public delegate void MyWebViewProgressChangedDelegate(int newProgress);

    /// <summary>
    /// 网页加载完成事件
    /// </summary>
    public delegate void MyWebViewPageFinishedDelegate();

    [IntentFilter(
        new[] { Intent.ActionView },
        Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
        DataSchemes = new[] { "http", "https" })]
    [IntentFilter(
        new[] { Intent.ActionView },
        Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
        DataSchemes = new[] { "file", "content" }, DataMimeType = "*/*", DataHost = "*", DataPathPattern = ".*\\\\.mhtml")]
    [IntentFilter(
        new[] { Intent.ActionView },
        Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
        DataMimeType = "*/*", DataSchemes = new[] { "file", "content" }, DataHost = "*", DataPathPattern = ".*\\\\.mht")]
    [Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true,
        ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.KeyboardHidden)]
    public class MainActivity : Activity
    {
        WebView myBrowser;
        EditText edtTxtUrl;
        FloatingActionButton fabMain;
        FloatingActionButton fabSubQRcodeScan;
        FloatingActionButton fabSubToggleFullScreen;
        FloatingActionButton fabSubSaveMHT;
        ProgressBar myBrowserPBar;

        private static bool isFabOpen;
        private static bool isFullScreen;
        private static DateTime lastClickGoBack = DateTime.Now;

        private static string currentPageTitle;
        private readonly string externalStorageDirPath = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath;
        private readonly string selfFolderName = "DDZMyBrowser";
        private static string selfApplicationDirPath;
        protected override void OnCreate(Bundle savedInstanceState)
        {
            // https://blog.csdn.net/niunan/article/details/71774292
            base.OnCreate(savedInstanceState);
            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.activity_main);

            //  1、浏览器控件相关
            myBrowser = FindViewById<WebView>(Resource.Id.myBrowser);
            //  要与Javascript交互,则webview必须设置支持Javascript
            myBrowser.Settings.JavaScriptEnabled = true;
            //  支持通过JS打开新窗口 
            myBrowser.Settings.JavaScriptCanOpenWindowsAutomatically = true;
            myBrowser.Settings.DomStorageEnabled = true;
            myBrowser.Settings.AllowFileAccessFromFileURLs = true;

            var myWebViewClient = new MyWebViewClient();
            myWebViewClient.GetWebViewPageFinishedDelegate += MyWebViewClient_GetWebViewPageFinishedDelegate;
            myBrowser.SetWebViewClient(myWebViewClient);
            var myWebChromeClient = new MyWebChromeClient();
            myWebChromeClient.GetWebViewTitleDelegate += MyWebChromeClient_GetWebViewTitleDelegate;
            myWebChromeClient.GetWebViewProgressChangedDelegate += MyWebChromeClient_GetWebViewProgressChangedDelegate;
            myBrowser.SetWebChromeClient(myWebChromeClient);
            edtTxtUrl = FindViewById<EditText>(Resource.Id.edtTxtUrl);
            edtTxtUrl.EditorAction += EdtTxtUrl_EditorAction;
            myBrowserPBar = FindViewById<ProgressBar>(Resource.Id.myBrowserPBar);

            //  2、右下方悬浮控件
            fabMain = FindViewById<FloatingActionButton>(Resource.Id.fabMain);
            fabSubQRcodeScan = FindViewById<FloatingActionButton>(Resource.Id.fabSubQRcodeScan);
            fabSubToggleFullScreen = FindViewById<FloatingActionButton>(Resource.Id.fabSubToggleFullScreen);
            fabSubSaveMHT = FindViewById<FloatingActionButton>(Resource.Id.fabSubSaveMHT);
            fabMain.Click += FabMain_Click;
            fabSubQRcodeScan.Click += FabSubQRcodeScan_Click;
            fabSubToggleFullScreen.Click += FabSubToggleFullScreen_Click; ;
            fabSubSaveMHT.Click += FabSubSaveMHT_Click;

            //  3、第三方应用使用该应用打开网页时,"this.Intent.DataString" 获取需要打开的网址
            //      自己打开时,"this.Intent.DataString" 的值为空
            String url = this.Intent.DataString;
            if (!String.IsNullOrEmpty(url))
            {              
                if (this.Intent.Data.Scheme == "content")
                {
                    //  DocumentsContract.IsDocumentUri(this, this.Intent.Data):false

                    //  this.Intent.Data.Authority:com.huawei.hidisk.fileprovider
                    //  this.Intent.Data.Host:com.huawei.hidisk.fileprovider
                    //  this.Intent.Data.Path:/root/storage/0ABF-6213/xxx.mht
                    //  this.Intent.Data.PathSegments:this.Intent.Data.Path的数组形式

                    //  Android.Support.V4.Content.FileProvider.GetUriForFile()
                    //   this.Intent.SetFlags(ActivityFlags.GrantReadUriPermission).SetFlags(ActivityFlags.GrantWriteUriPermission);
                }
            }
            edtTxtUrl.Text = url;
            LoadOnePage(url);

            //  4、创建应用目录
            CreateSelfApplicationFolder();
        }       

        private void EdtTxtUrl_EditorAction(object sender, TextView.EditorActionEventArgs e)
        {
            string inputUrl = edtTxtUrl.Text.Trim();
            if (e.ActionId == ImeAction.Go)
            {
                HideSoftInputFn();
                LoadOnePage(inputUrl);
            }
        }

        #region 获取WebView加载页面相关信息的一些自定义事件
        private void MyWebViewClient_GetWebViewPageFinishedDelegate()
        {
            Toast.MakeText(this, "加载完成", ToastLength.Long).Show();
        }

        private void MyWebChromeClient_GetWebViewProgressChangedDelegate(int newProgress)
        {
            myBrowserPBar.Visibility = ViewStates.Visible;
            myBrowserPBar.Progress = newProgress;
            if (newProgress == 100)
            {
                myBrowserPBar.Visibility = ViewStates.Gone;
            }
        }

        private void MyWebChromeClient_GetWebViewTitleDelegate(string title)
        {
            currentPageTitle = title;
        }
        #endregion

        #region 悬浮按钮
        private void FabMain_Click(object sender, EventArgs e)
        {
            if (!isFabOpen)
            {
                HideSoftInputFn();
                ShowFabMenu();
            }
            else
            {
                CloseFabMenu();
            }
            SetToggleFullScreenBtnImg();
        }

        private void FabSubQRcodeScan_Click(object sender, EventArgs e)
        {
            Toast.MakeText(this, "扫描二维码", ToastLength.Long).Show();
        }

        private void FabSubSaveMHT_Click(object sender, EventArgs e)
        {
            string savePageDirPath = $"{selfApplicationDirPath}{File.Separator}SavePages";
            File dir = new File(savePageDirPath);
            if (!dir.Exists())
            {
                bool retBool = dir.Mkdir();
            }
            myBrowser.SaveWebArchive($"{savePageDirPath}{File.Separator}{currentPageTitle}.mht");
        }

        private void FabSubToggleFullScreen_Click(object sender, EventArgs e)
        {
            if (isFullScreen)
            {   //  目前为全屏状态,修改为非全屏
                edtTxtUrl.Visibility = ViewStates.Visible;
                this.Window.ClearFlags(WindowManagerFlags.Fullscreen);
            }
            else
            {   //  目前为非全屏状态,修改为全屏
                edtTxtUrl.Visibility = ViewStates.Gone;
                this.Window.SetFlags(WindowManagerFlags.Fullscreen, WindowManagerFlags.Fullscreen);
            }
            isFullScreen = !isFullScreen;
            SetToggleFullScreenBtnImg();
        }

        private void ShowFabMenu()
        {
            isFabOpen = true;
            fabSubQRcodeScan.Visibility = ViewStates.Visible;
            fabSubToggleFullScreen.Visibility = ViewStates.Visible;
            fabSubSaveMHT.Visibility = ViewStates.Visible;

            fabMain.Animate().Rotation(135f);
            fabSubQRcodeScan.Animate()
               .TranslationY(-600f)
               .Rotation(0f);
            fabSubToggleFullScreen.Animate()
               .TranslationY(-410f)
               .Rotation(0f);
            fabSubSaveMHT.Animate()
                .TranslationY(-220f)
                .Rotation(0f);
        }

        private void CloseFabMenu()
        {
            isFabOpen = false;

            fabMain.Animate().Rotation(0f);
            fabSubQRcodeScan.Animate()
                .TranslationY(0f)
                .Rotation(90f);
            fabSubToggleFullScreen.Animate()
                .TranslationY(0f)
                .Rotation(90f);
            fabSubSaveMHT.Animate()
                .TranslationY(0f)
                .Rotation(90f);

            fabSubQRcodeScan.Visibility = ViewStates.Gone;
            fabSubToggleFullScreen.Visibility = ViewStates.Gone;
            fabSubSaveMHT.Visibility = ViewStates.Gone;
        }

        private void SetToggleFullScreenBtnImg()
        {
            if (isFullScreen)
            {
                fabSubToggleFullScreen.SetImageResource(Resource.Drawable.fullscreenExit);
            }
            else
            {
                fabSubToggleFullScreen.SetImageResource(Resource.Drawable.fullscreen);
            }
        }
        #endregion

        #region 重写基类 Activity 方法
        public override void OnConfigurationChanged(Configuration newConfig)
        {
            base.OnConfigurationChanged(newConfig);
            if (newConfig.Orientation == Android.Content.Res.Orientation.Portrait)
            {
                edtTxtUrl.Visibility = ViewStates.Visible;
                fabMain.Visibility = ViewStates.Visible;
                this.Window.ClearFlags(WindowManagerFlags.Fullscreen);
                isFullScreen = false;
                Toast.MakeText(Application.Context, "竖屏模式!", ToastLength.Long).Show();
            }
            else
            {
                CloseFabMenu();
                edtTxtUrl.Visibility = ViewStates.Gone;
                fabMain.Visibility = ViewStates.Gone;
                this.Window.SetFlags(WindowManagerFlags.Fullscreen, WindowManagerFlags.Fullscreen);
                isFullScreen = true;
                Toast.MakeText(Application.Context, "横屏模式!", ToastLength.Long).Show();
            }
        }
        public override void OnBackPressed()
        {
            if (myBrowser.CanGoBack())
            {
                myBrowser.GoBack();
            }
            else
            {
                if ((DateTime.Now - lastClickGoBack).Seconds > 2)
                {
                    Toast.MakeText(this, $"再按一次退出程序", ToastLength.Long).Show();
                    lastClickGoBack = DateTime.Now;
                }
                else
                {
                    this.Finish();
                }
            }
        }
        #endregion

        private void LoadOnePage(String url = "")
        {
            currentPageTitle = null;
            if (String.IsNullOrEmpty(url)) url = "https://www.baidu.com/";
            myBrowser.LoadUrl(url);
        }

        private void HideSoftInputFn()
        {
            //  隐藏键盘
            InputMethodManager imm = (InputMethodManager)this.GetSystemService(Context.InputMethodService);
            imm.HideSoftInputFromWindow(edtTxtUrl.WindowToken, 0);
        }

        private void CreateSelfApplicationFolder()
        {
            selfApplicationDirPath = $"{externalStorageDirPath}{File.Separator}{selfFolderName}";
            File dir = new File(selfApplicationDirPath);
            if (!dir.Exists())
            {
                bool retBool = dir.Mkdir();
            }
        }

        private String GetRealPathFromURI(Context context, Android.Net.Uri uri)
        {
            String retPath = null;
            if (context == null || uri == null) return retPath;
            Boolean isKitKat = Build.VERSION.SdkInt >= BuildVersionCodes.Kitkat;
            if (isKitKat && DocumentsContract.IsDocumentUri(context, uri))
            {
                if (uri.Authority.Equals("com.android.externalstorage.documents", StringComparison.OrdinalIgnoreCase))
                {
                    String docId = DocumentsContract.GetDocumentId(uri);
                    String[] split = docId.Split(':');
                    if (split[0].Equals("primary", StringComparison.OrdinalIgnoreCase))
                    {
                        retPath = Android.OS.Environment.ExternalStorageDirectory + "/" + split[1];
                    }
                }
            }
            return retPath;
        }

        private String GetFilePathFromContentUri(Context context,Android.Net.Uri url)
        {
            String filePath = null;
            String[] filePathColumn = { MediaStore.MediaColumns.Data };
            using (ICursor cursor = context.ContentResolver.Query(url, filePathColumn, null, null, null))
            {
                if (cursor != null && cursor.MoveToFirst())
                {
                    int columnIndex = cursor.GetColumnIndexOrThrow(filePathColumn[0]);
                    if (columnIndex > -1)
                    {
                        filePath = cursor.GetString(columnIndex);
                    }
                }
            }
            return filePath;
        }
    }

    public class MyWebViewClient : WebViewClient
    {
        public event MyWebViewPageFinishedDelegate GetWebViewPageFinishedDelegate;
        public override bool ShouldOverrideUrlLoading(WebView view, IWebResourceRequest request)
        {
            view.LoadUrl(request.Url.ToString());
            return true;
        }

        public override void OnPageFinished(WebView view, string url)
        {
            base.OnPageFinished(view, url);
            GetWebViewPageFinishedDelegate();
        }
    }

    public class MyWebChromeClient : WebChromeClient
    {
        public event MyWebViewTitleDelegate GetWebViewTitleDelegate;//声明一个事件

        public event MyWebViewProgressChangedDelegate GetWebViewProgressChangedDelegate;
        public override void OnReceivedTitle(WebView view, string title)
        {
            base.OnReceivedTitle(view, title);
            GetWebViewTitleDelegate(title);
        }

        public override void OnProgressChanged(WebView view, int newProgress)
        {
            base.OnProgressChanged(view, newProgress);
            GetWebViewProgressChangedDelegate(newProgress);
        }
    }
}

  布局代码:

 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <ProgressBar
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="match_parent"
            android:layout_height="3dip"
            android:max="100"
            android:progress="0"
            android:visibility="gone"
            android:id="@+id/myBrowserPBar" />
        <android.webkit.WebView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:id="@+id/myBrowser" />
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/edtTxtUrl"
                android:inputType="text"
                android:singleLine="true"
                android:imeOptions="actionGo"
                android:hint="请输入网址" />
        </LinearLayout>
    </LinearLayout>
    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_margin="18dp"
        android:visibility="gone"
        android:rotation="90"
        android:src="@drawable/qrcodeScan"
        app:fabSize="mini"
        android:id="@+id/fabSubQRcodeScan" />
    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_margin="18dp"
        android:visibility="gone"
        android:rotation="90"
        android:src="@drawable/saveMht"
        app:fabSize="mini"
        android:id="@+id/fabSubSaveMHT" />
    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_margin="18dp"
        android:visibility="gone"
        android:rotation="90"
        app:fabSize="mini"
        android:id="@+id/fabSubToggleFullScreen" />
    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_margin="10dp"
        android:src="@drawable/plus"
        app:fabSize="normal"
        android:id="@+id/fabMain" />
</RelativeLayout>

  用到的一些权限

 

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


  到此就结束了,webview在8.0下打开content://文件失败的问题,如果有大神看到,希望帮忙解答,十分感谢!以后有机会在慢慢完善他的功能。

  【2018-06-26更新】

  昨天在小米note3测试本应用,本来是想测试一下看看能不能打开离线文件的问题,但是连关联MHT都不行,打开的时候,应用列表没有该应用……郁闷了,越淌水越深啊!

  【2018-11-08更新】

  开始因为版本兼容的问题,扫描二维码未处理,一直想着过后看看官方能不能解决,但是今天看看,还是不行……但是VS给出了解决方案:

  VS会依次提示单独安装上述的几个包,开始这个项目只安装了一个包,现在的依赖如下:

  这次添加的相机权限

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

  相机相关代码

  1、初始化

复制代码
        protected override void OnCreate(Bundle savedInstanceState)
        {
            ………………
            ……………… // 5、初始化  MobileBarcodeScanner.Initialize(Application);
        }
复制代码

  2、点击扫描二维码执行的代码

复制代码
        private void FabSubQRcodeScan_Click(object sender, EventArgs e)
        { //Toast.MakeText(this, "扫描二维码", ToastLength.Long).Show(); Task.Run(() => { var scanner = new MobileBarcodeScanner(); var result = scanner.Scan(); if (result != null)
                { string scanResult = result.Result.Text; this.RunOnUiThread(new Java.Lang.Runnable(() => {
                        edtTxtUrl.Text = scanResult;
                        LoadOnePage(scanResult);
                    }));
                }
            });
        }
复制代码

  经过这次更新,就可以扫描二维码了……


上一篇: 已经没有上一个了
下一篇: 项目.Android 缺少生成所需的 Android SDK。双击此消息,然后按照提示安装它们。