隐藏

跨过几个坑,终于完成了我的第一个Xamarin Android App!

发布:2019/4/15 17:26:15作者:管理员 来源:本站 浏览次数:1160

App项目用的是VS里的Android Blank App,先上个图让大家看看我手机上的显示效果,自己用就不需要那么华丽丽了(关键是不会。。。)

Android App使用的是显示与逻辑分离的设计模式,虽然我基本是做Winform的,也是基本能看懂的。Resources\layout文件夹里放视图文件,而代码逻辑文件放在最外层,整个项目的结构如下图:

这个App主界面使用的是GridLayout进行垂直布局,用法类似于HTML中的Table和WPF中的Grid,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:paddingLeft="10dp"
    android:paddingRight="10dp"
    android:rowCount="6"
    android:columnCount="2">
    <TextView
        android:id="@+id/tvMachineCode"
        android:layout_width="wrap_content"
        android:layout_marginRight="5dp"
        android:textSize="20sp"
        android:layout_marginTop="50dp"
        android:text="机器码" />
    <EditText
        android:id="@+id/txtMachineCode"
        android:textSize="20sp"
        android:maxLength ="20"
        android:layout_marginTop="50dp"
        android:layout_width="fill_parent" />
    <TextView
        android:id="@+id/tvActiviationCode"
        android:layout_width="wrap_content"
        android:layout_marginRight="5dp"
        android:textSize="20sp"
        android:layout_marginTop="30dp"
        android:text="激活码" />
    <EditText
        android:id="@+id/txtActiviationCode"
        android:textSize="20sp"
        android:layout_marginTop="30dp"
        android:layout_width="fill_parent" />
    <Button
        android:id="@+id/btnGetActiviationCode"
        android:layout_marginTop="30dp"
        android:layout_columnSpan="2"
        android:layout_width="match_parent"
        android:text="获取激活码" />
    <Button
        android:id="@+id/btnScanQRCode"
        android:layout_columnSpan="2"
        android:layout_width="match_parent"
        android:text="扫描二维码" />
    <Button
        android:id="@+id/btnReadQRCode"
        android:layout_columnSpan="2"
        android:layout_width="match_parent"
        android:text="读取二维码" />
  <Button
      android:id="@+id/btnCopy"
      android:layout_columnSpan="2"
      android:layout_width="match_parent"
      android:text="复制激活码" />
  <Button
      android:id="@+id/btnShare"
      android:layout_columnSpan="2"
      android:layout_width="match_parent"
      android:text="发送激活码" />
    <Button
        android:id="@+id/btnClear"
        android:layout_columnSpan="2"
        android:layout_width="match_parent"
        android:text="清除" />
</GridLayout>

Main.axml

界面设计好之后,开始写逻辑代码。App默认是从MainActivity开始启动(JAVA开发可以在Properties\AndroidManifest.xml中修改,有谁知道Xamarin里是怎么改的?)。开始实现第一个按钮的功能,自我感觉还是比较容易的,基本可以直接复制粘贴我Winform里的代码,然而,我发现掉到第一个坑里去了。先看看从Winform里复制来的字符串取MD5的代码,这个在VS自带的模拟器中执行是正常的,得到的结果与Winform一致,但安装到手机里得到的就不对了。

private string MD5(string str, bool clearsplitter = true, bool islower = true)
        {
            var md5 = MD5CryptoServiceProvider.Create();
            var output = md5.ComputeHash(Encoding.Default.GetBytes(str));
            StringBuilder strbvalue = new StringBuilder(BitConverter.ToString(output).Replace("-", string.Empty).Substring(8, 16));
            if (!clearsplitter)
                strbvalue.Insert(12, '-').Insert(8, '-').Insert(4, '-');
            return islower ? strbvalue.ToString().ToLower() : strbvalue.ToString().ToUpper();
        }

上网查了下,也问了下别人,电脑里Encoding.Default用的编码是GB2312,而手机里可能是ASCII。由于不能修改之前的代码,只能改这个了,把Encoding.Default改成了Encoding.GetEncoding("gb2312"),结果出乎预料,竟然闪退了。。。又上网搜了下,需要引用Xamarin安装自带的I18N.CJK,总算是搞定了第一个按钮。以下是【获取激活码】和【清除】的代码:

private void Btngetactiviationcode_Click(object sender, EventArgs e)
        {
            string strerr = ValidateFormat(txtMachineCode.Text);
            if (strerr != string.Empty)
            {
                var dlg = new AlertDialog.Builder(this).SetTitle("警告")
                    .SetMessage("输入的机器码格式不正确!\n" + strerr);
                dlg.Show();
                Btnclear_Click(this, null);
                return;
            }
            txtActiviationCode.Text = GetActiveCode(txtMachineCode.Text);
        }

        private void Btnclear_Click(object sender, EventArgs e)
        {
            txtMachineCode.Text = txtActiviationCode.Text = string.Empty;
        }

        private string GetActiveCode(string machinecode)
        {
            string guid = "cd89e66c-b897-4ed8-a19f-ef5a30846f0a";
            return MD5(machinecode + MD5(guid, false, false), false, false);
        }

        private string MD5(string str, bool clearsplitter = true, bool islower = true)
        {
            var md5 = MD5CryptoServiceProvider.Create();
            var output = md5.ComputeHash(Encoding.GetEncoding("gb2312").GetBytes(str));
            StringBuilder strbvalue = new StringBuilder(BitConverter.ToString(output).Replace("-", string.Empty).Substring(8, 16));
            if (!clearsplitter)
                strbvalue.Insert(12, '-').Insert(8, '-').Insert(4, '-');
            return islower ? strbvalue.ToString().ToLower() : strbvalue.ToString().ToUpper();
        }

【获取激活码】和【清除】

这个App的主要便利用途就是能够扫描和识别二维码,上网搜了下,使用ZXing库会比较简单,它有个.net移动开发版本叫ZXing.Net.Mobile,可以使用Nuget直接下载添加引用,由于它依赖于Xamarin.Android.Support.v4,所以也要一起下载安装。直接按照ZXing.Net.Mobile官网上的扫描二维码示例代码,就做好了最简单的二维码扫描功能。注意:要在OnCreate方法里先初始化一下:

MobileBarcodeScanner.Initialize(Application);

private async void Btnscanqrcode_Click(object sender, EventArgs e)
        {
            var scanner = new ZXing.Mobile.MobileBarcodeScanner();
            var result = await scanner.Scan();
            if (result == null)
                return;
            txtMachineCode.Text = result.Text.Trim();
            Btngetactiviationcode_Click(this, null);
        }

完成扫描二维码的功能,顿时信心大增,以为识别图片中的二维码也很简单,结果发现又掉第二个坑里去了。原来,ZXing.Net.Mobile里没有现成简单的识别二维码的方法,只查到可以用IBarcodeReader.Decode()方法来识别,然而它第一个参数byte[] rawRGB是个什么鬼?为毛不能提供一个Bitmap让我爽一下?!去网上搜JAVA版的都是传递Bitmap对象,再去看了下ZXing.Net.Mobile的源码,竟然是有些项目类型是Bitmap对象,有些是byte[]。没时间深究,我还是自己来弄个byte[]吧。

印象中看到过一篇教程里介绍过这个方法,说rawRGB参数指的是每个像素点的RGB值数组,而不是图像文件的二进制数组,这就要读取图像中的所有点的颜色值到数组里里再传递了。

private void Btnreadqrcode_Click(object sender, EventArgs e)
        {
            Intent = new Intent();
            //从文件浏览器和相册等选择图像文件
            Intent.SetType("image/*");
            Intent.SetAction(Intent.ActionGetContent);
            StartActivityForResult(Intent, 1);
        }

        protected override void OnActivityResult(int requestCode, [GeneratedEnum] Android.App.Result resultCode, Intent data)
        {
            base.OnActivityResult(requestCode, resultCode, data);
            if(requestCode == 1 && resultCode == Android.App.Result.Ok && data != null)
            {
                // create a barcode reader instance
                IBarcodeReader reader = new BarcodeReader();
                // load a bitmap
                int width = 0, height = 0;
                //像素颜色值列表(注意:一个像素的每个颜色值都是一个列表中单独的元素,
                //后面将会把像素颜色值转换成ARGB32格式的颜色,每个像素颜色值就有4个元素加入到列表中)
                List<byte> pixelbytelist = new List<byte>();
                try
                {
                    //根据选择的文件路径生成Bitmap对象
                    using (Bitmap bmp = Android.Provider.MediaStore.Images.Media.GetBitmap(ContentResolver, data.Data))
                    {
                        width = bmp.Width; //图像宽度
                        height = bmp.Height;  //图像高度
                        // detect and decode the barcode inside the bitmap
                        bmp.LockPixels();
                        int[] pixels = new int[width * height];
                        //一次性读取所有像素的颜色值(一个整数)到pixels
                        bmp.GetPixels(pixels, 0, width, 0, 0, width, height);
                        bmp.UnlockPixels();
                        for (int i = 0; i < pixels.Length; i++)
                        {
                            int p = pixels[i];  //取出一个像素颜色值
                            //将像素颜色值中的alpha颜色(透明度)添加到列表
                            pixelbytelist.Add((byte)Color.GetAlphaComponent(p));
                            //将像素颜色值中的红色添加到列表
                            pixelbytelist.Add((byte)Color.GetRedComponent(p));
                            //将像素颜色值中的绿色添加到列表
                            pixelbytelist.Add((byte)Color.GetGreenComponent(p));
                            //将像素颜色值中的蓝色添加到列表
                            pixelbytelist.Add((byte)Color.GetBlueComponent(p));
                        }
                    }
                    //识别
                    var result = reader.Decode(pixelbytelist.ToArray(), width, height, RGBLuminanceSource.BitmapFormat.ARGB32);
                    if (result != null)
                    {
                        txtMachineCode.Text = result.Text.Trim();
                        Btngetactiviationcode_Click(this, null);
                    }
                    else
                        Toast.MakeText(this, "未能识别到二维码!", ToastLength.Short).Show();
                }
                catch (Exception ex)
                {
                    var dlg = new AlertDialog.Builder(this).SetTitle("警告")
                        .SetMessage("获取图像时发生错误!\n" + ex.ToString());
                    dlg.Show();
                }
            }
        }

上面就完成了识别二维码的功能,不过上面红色文字那里又出现个只在手机上出现的诡异问题,识别出来的二维码后面会多出一个不可见的字符,它会影响EditText中Text的长度,但不影响Text的值,可以被删除,删除前后计算出的激活码是相同的。没有去看源码,不知道怎么产生的,有人知道吗?

后面的复制激活码和发送激活码比较简单,都是直接找的网上的代码,调用系统功能来做。

private void Btncopy_Click(object sender, EventArgs e)
        {
            ClipboardManager clip = (ClipboardManager)GetSystemService(ClipboardService);
            StringBuilder strbcontent = new StringBuilder();
            strbcontent.AppendLine("机器码:" + txtMachineCode.Text)
                .AppendLine("激活码:" + txtActiviationCode.Text);
            ClipData clipdata = ClipData.NewPlainText("激活码", strbcontent.ToString());
            clip.PrimaryClip = clipdata;
            Toast.MakeText(this, "激活码已复制到剪贴板", ToastLength.Short).Show();
        }

        private void Btnshare_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrWhiteSpace(txtActiviationCode.Text))
            {
                var dlg = new AlertDialog.Builder(this).SetTitle("警告")
                    .SetMessage("请先获取激活码!");
                dlg.Show();
                return;
            }
            string strerr = ValidateFormat(txtMachineCode.Text);
            if (strerr != string.Empty)
            {
                var dlg = new AlertDialog.Builder(this).SetTitle("警告")
                    .SetMessage("输入的机器码格式不正确!\n" + strerr);
                dlg.Show();
                return;
            }
            Intent intent = new Intent(Intent.ActionSend);
            intent.SetType("text/plain");//所有可以分享文本的app
            StringBuilder strbcontent = new StringBuilder();
            strbcontent.AppendLine("机器码:" + txtMachineCode.Text)
                .AppendLine("激活码:" + txtActiviationCode.Text);
            intent.PutExtra(Intent.ExtraText, strbcontent.ToString());
            StartActivity(Intent.CreateChooser(intent, "发送激活码"));
        }

        private string ValidateFormat(string str)
        {
            if(str.Length<19)
                return "输入的格式不正确";
            if (str.Length != 19)
                str = str.Substring(0, 19);
            string[] strs = str.Split('-');
            if (strs.Length != 4)
                return "不能分隔为4组";
            foreach (string s in strs)
            {
                if (s.Length != 4)
                    return s + "的长度不是4";
                if (!System.Text.RegularExpressions.Regex.IsMatch(s, "^[A-F0-9]{4}$"))
                    return s + "的格式不正确";
            }
            return string.Empty;
        }

【复制激活码】和【发送激活码】

断断续续写了几个晚上,终于写完这篇随笔了。在眼睛彻底睁不开之前赶紧贴上完整代码。

using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using System.Text;
using System.Security.Cryptography;
using ZXing.Mobile;
using Android.Graphics;
using ZXing;
using Android.Database;
using System.Collections.Generic;

namespace FMSKeygen_Android
{
    [Activity(Label = "流程管理系统注册机", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity
    {
        private EditText txtMachineCode = null;
        private EditText txtActiviationCode = null;

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);

            // 初始化二维码扫描仪,后面要用到
            MobileBarcodeScanner.Initialize(Application);

            txtMachineCode = FindViewById<EditText>(Resource.Id.txtMachineCode);
            //设置自动转换小写字母为大写
            txtMachineCode.SetFilters(new Android.Text.IInputFilter[] { new Android.Text.InputFilterAllCaps() });
            txtActiviationCode = FindViewById<EditText>(Resource.Id.txtActiviationCode);
            //取消对验证码文本框的所有按键监听
            txtActiviationCode.KeyListener = null;
            Button btnclear = FindViewById<Button>(Resource.Id.btnClear);
            btnclear.Click += Btnclear_Click;
            Button btngetactiviationcode = FindViewById<Button>(Resource.Id.btnGetActiviationCode);
            btngetactiviationcode.Click += Btngetactiviationcode_Click;
            Button btnscanqrcode = FindViewById<Button>(Resource.Id.btnScanQRCode);
            btnscanqrcode.Click += Btnscanqrcode_Click;
            Button btncopy = FindViewById<Button>(Resource.Id.btnCopy);
            btncopy.Click += Btncopy_Click;
            Button btnreadqrcode = FindViewById<Button>(Resource.Id.btnReadQRCode);
            btnreadqrcode.Click += Btnreadqrcode_Click;
            Button btnshare = FindViewById<Button>(Resource.Id.btnShare);
            btnshare.Click += Btnshare_Click;
        }
        

        private void Btnshare_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrWhiteSpace(txtActiviationCode.Text))
            {
                var dlg = new AlertDialog.Builder(this).SetTitle("警告")
                    .SetMessage("请先获取激活码!");
                dlg.Show();
                return;
            }
            string strerr = ValidateFormat(txtMachineCode.Text);
            if (strerr != string.Empty)
            {
                var dlg = new AlertDialog.Builder(this).SetTitle("警告")
                    .SetMessage("输入的机器码格式不正确!\n" + strerr);
                dlg.Show();
                return;
            }
            Intent intent = new Intent(Intent.ActionSend);
            intent.SetType("text/plain");//所有可以分享文本的app
            StringBuilder strbcontent = new StringBuilder();
            strbcontent.AppendLine("机器码:" + txtMachineCode.Text)
                .AppendLine("激活码:" + txtActiviationCode.Text);
            intent.PutExtra(Intent.ExtraText, strbcontent.ToString());
            StartActivity(Intent.CreateChooser(intent, "发送激活码"));
        }

        private string ValidateFormat(string str)
        {
            if(str.Length<19)
                return "输入的格式不正确";
            if (str.Length != 19)
                str = str.Substring(0, 19);
            string[] strs = str.Split('-');
            if (strs.Length != 4)
                return "不能分隔为4组";
            foreach (string s in strs)
            {
                if (s.Length != 4)
                    return s + "的长度不是4";
                if (!System.Text.RegularExpressions.Regex.IsMatch(s, "^[A-F0-9]{4}$"))
                    return s + "的格式不正确";
            }
            return string.Empty;
        }

        private void Btnreadqrcode_Click(object sender, EventArgs e)
        {
            Intent = new Intent();
            //从文件浏览器和相册等选择图像文件
            Intent.SetType("image/*");
            Intent.SetAction(Intent.ActionGetContent);
            StartActivityForResult(Intent, 1);
        }

        protected override void OnActivityResult(int requestCode, [GeneratedEnum] Android.App.Result resultCode, Intent data)
        {
            base.OnActivityResult(requestCode, resultCode, data);
            if(requestCode == 1 && resultCode == Android.App.Result.Ok && data != null)
            {
                // create a barcode reader instance
                IBarcodeReader reader = new BarcodeReader();
                // load a bitmap
                int width = 0, height = 0;
                //像素颜色值列表(注意:一个像素的每个颜色值都是一个列表中单独的元素,
                //后面将会把像素颜色值转换成ARGB32格式的颜色,每个像素颜色值就有4个元素加入到列表中)
                List<byte> pixelbytelist = new List<byte>();
                try
                {
                    //根据选择的文件路径生成Bitmap对象
                    using (Bitmap bmp = Android.Provider.MediaStore.Images.Media.GetBitmap(ContentResolver, data.Data))
                    {
                        width = bmp.Width; //图像宽度
                        height = bmp.Height;  //图像高度
                        // detect and decode the barcode inside the bitmap
                        bmp.LockPixels();
                        int[] pixels = new int[width * height];
                        //一次性读取所有像素的颜色值(一个整数)到pixels
                        bmp.GetPixels(pixels, 0, width, 0, 0, width, height);
                        bmp.UnlockPixels();
                        for (int i = 0; i < pixels.Length; i++)
                        {
                            int p = pixels[i];  //取出一个像素颜色值
                            //将像素颜色值中的alpha颜色(透明度)添加到列表
                            pixelbytelist.Add((byte)Color.GetAlphaComponent(p));
                            //将像素颜色值中的红色添加到列表
                            pixelbytelist.Add((byte)Color.GetRedComponent(p));
                            //将像素颜色值中的绿色添加到列表
                            pixelbytelist.Add((byte)Color.GetGreenComponent(p));
                            //将像素颜色值中的蓝色添加到列表
                            pixelbytelist.Add((byte)Color.GetBlueComponent(p));
                        }
                    }
                    //识别
                    var result = reader.Decode(pixelbytelist.ToArray(), width, height, RGBLuminanceSource.BitmapFormat.ARGB32);
                    if (result != null)
                    {
                        txtMachineCode.Text = result.Text.Trim();
                        Btngetactiviationcode_Click(this, null);
                    }
                    else
                        Toast.MakeText(this, "未能识别到二维码!", ToastLength.Short).Show();
                }
                catch (Exception ex)
                {
                    var dlg = new AlertDialog.Builder(this).SetTitle("警告")
                        .SetMessage("获取图像时发生错误!\n" + ex.ToString());
                    dlg.Show();
                }
            }
        }

        private void Btncopy_Click(object sender, EventArgs e)
        {
            ClipboardManager clip = (ClipboardManager)GetSystemService(ClipboardService);
            StringBuilder strbcontent = new StringBuilder();
            strbcontent.AppendLine("机器码:" + txtMachineCode.Text)
                .AppendLine("激活码:" + txtActiviationCode.Text);
            ClipData clipdata = ClipData.NewPlainText("激活码", strbcontent.ToString());
            clip.PrimaryClip = clipdata;
            Toast.MakeText(this, "激活码已复制到剪贴板", ToastLength.Short).Show();
        }

        private async void Btnscanqrcode_Click(object sender, EventArgs e)
        {
            var scanner = new ZXing.Mobile.MobileBarcodeScanner();
            var result = await scanner.Scan();
            if (result == null)
                return;
            txtMachineCode.Text = result.Text.Trim();
            Btngetactiviationcode_Click(this, null);
        }

        private void Btngetactiviationcode_Click(object sender, EventArgs e)
        {
            string strerr = ValidateFormat(txtMachineCode.Text);
            if (strerr != string.Empty)
            {
                var dlg = new AlertDialog.Builder(this).SetTitle("警告")
                    .SetMessage("输入的机器码格式不正确!\n" + strerr);
                dlg.Show();
                Btnclear_Click(this, null);
                return;
            }
            txtActiviationCode.Text = GetActiveCode(txtMachineCode.Text);
        }

        private void Btnclear_Click(object sender, EventArgs e)
        {
            txtMachineCode.Text = txtActiviationCode.Text = string.Empty;
        }

        private string GetActiveCode(string machinecode)
        {
            string guid = "cd89e66c-b897-4ed8-a19f-ef5a30846f0a";
            return MD5(machinecode + MD5(guid, false, false), false, false);
        }

        private string MD5(string str, bool clearsplitter = true, bool islower = true)
        {
            var md5 = MD5CryptoServiceProvider.Create();
            var output = md5.ComputeHash(Encoding.GetEncoding("gb2312").GetBytes(str));
            StringBuilder strbvalue = new StringBuilder(BitConverter.ToString(output).Replace("-", string.Empty).Substring(8, 16));
            if (!clearsplitter)
                strbvalue.Insert(12, '-').Insert(8, '-').Insert(4, '-');
            return islower ? strbvalue.ToString().ToLower() : strbvalue.ToString().ToUpper();
        }
    }
}

完整代码