65.9K
CodeProject 正在变化。 阅读更多。
Home

Xamarin.Android 简介

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (33投票s)

2014 年 8 月 25 日

CPOL

14分钟阅读

viewsIcon

116390

downloadIcon

11

Xamarin.Android 简介

引言

本文将概述 Xamarin.Android,它是一个适用于 Visual Studio 的 MonoDroid 插件。它允许 .NET 开发者使用他们现有的 IDE 和语言构建面向 Android 平台的应用程序,从而无需学习 Java / Eclipse。本文将介绍创建 Xamarin.Android 项目的基础知识,并解释关键元素和项目结构。

背景

Xamarin 是一家总部位于加州的软件公司,创建了 MonoMonoTouchMono for Android,它们都是跨平台实现的 Common Language Infrastructure (CLI) 和 Common Language Specifications(通常称为 Microsoft.NET)。

摘自 Visual Studio Magazine

“Monodroid 是 Visual Studio 2010 的一个插件。该插件允许开发人员将应用程序部署到本地运行的 Android 模拟器,以及通过 USB 电缆和 Wi-Fi 连接的物理设备。该插件允许 MonoDroid 与 Visual Studio 生态系统的其余部分积极协作,并与开发人员已使用的工具集成。”

使用 C# 语言,开发人员可以构建可面向 iOSAndroidWindows Phone 的原生应用。他们可以使用 Xamarin Studio(一个作为 Xamarin.Android SDK 一部分下载的 IDE),也可以使用 Visual Studio。我个人更倾向于使用 Visual Studio,因为它我最熟悉。在本篇文章中,我将使用 Visual Studio。

本文假定读者熟悉 C#、Visual Studio 和 .NET Framework。还假定您已从 Xamarin 网站下载了 Xamarin.Android SDK。如果尚未下载,请立即下载

创建你的第一个 Xamarin.Android 项目

要创建你的第一个 Xamarin.Android 项目,请打开 Visual Studio,然后选择 **文件** -> **新建** -> **项目**。在左侧已安装模板列表中,选择 **Visual C#**。在此下方,您应该会看到 **Android**。选择此项后,您应该会看到一个 Xamarin.Android 项目模板列表,类似于下图所示。

visual studio android template projects

正如您所见,有几个已安装的 Xamarin.Android 项目模板。在本篇文章中,我将使用 **Android 应用程序** 模板。给你的项目起一个有意义的名称(我将其命名为 _MyDemo_,但你可以随意命名)。点击 **确定** 继续创建你的项目。

项目结构

项目创建完成后,您将看到以下项目结构。

visual studio android template projects

展开 **_Resources_** 文件夹及其子文件夹,如下图所示。

visual studio android template projects

让我们逐一介绍项目的各个部分,并解释它们是什么以及它们的作用。我将不深入探讨标准的 .NET 元素,而是专注于 Xamarin.Android 特有的元素。

_Activity1.cs_ - 这是创建的默认 Android ActivityActivityAndroid 特有的实体。它们定义了用户可以执行的一个单一、集中的操作,例如添加新客户、查看商品列表等。它们有自己的生命周期和事件。它们是 Android 应用程序的关键,因此理解它们很重要。

展开 **_Properties_** 文件夹。

  • _AndroidManifest.xml_ - 此文件向 Android 系统定义了应用程序的必需信息。每个 Android 应用程序都有一个这样的文件。此文件是应用程序运行所必需的,因为它定义了诸如应用程序在 Android 系统中的名称、应用程序组件的描述、权限声明以及最低 Android API 级别等信息。

展开 **_Resources_** 文件夹。

  • Drawable - 此文件夹包含应用程序的图像。如果您的应用程序需要不同大小的图像来适应不同的屏幕尺寸,则在此下方为每种不同的分辨率创建一个子文件夹。
  • Layout - 此文件夹包含项目的 Android 布局(_.AXML_)文件。Android 布局文件类似于 HTML 或 ASPX 文件。它使用 XML 语法定义单个 Android 屏幕。为每个屏幕创建一个新的布局。如果您的应用程序需要不同的横向或纵向布局,则需要分别创建 **_layout-land_** 和 **_layout-port_** 子文件夹。默认情况下,会自动创建一个名为 _Main.axml_ 的布局。这是前面提到的默认 Activity 类 _Activity1.cs_ 所调用的 UI 布局。
  • _Values_ - 此文件夹包含应用程序使用的资源定义,例如 string 值。例如,您可以在 _Strings.xml_ 文件中存储应用程序的名称或下拉列表项。
  • _Resource.Designer.cs_ - 此文件包含应用程序中包含的生成 ID 的类定义。这使得应用程序资源可以通过名称检索,从而开发人员无需使用 ID。因此,该文件将应用程序资源名称映射到其生成的 ID。每次构建项目时都会更新此文件。开发人员不应手动更新此文件!

您可能在 _Values_ 文件夹下拥有的其他文件包括

  • Arrays.xml
  • Colors.xml
  • Dimensions.xml
  • Styles.xml

以下是 _Strings.xml_ 的默认条目

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="Hello">Hello World, Click Me!</string>
    <string name="ApplicationName">MyDemo</string>
</resources>

要从 Android 布局(.AXML)文件中使用 _Strings.xml_ 条目。

<TextView
        android:text="@string/ApplicationName"
        android:id="@+id/txtApplicationName" />

要从您的应用程序代码中使用 _Strings.xml_ 条目。

string applicationName = GetString(Resource.String.ApplicationName);

_Arrays.xml_ 的示例条目

<?xml version="1.0" encoding="utf-8" ?>
<resources>
  <string-array name="payment_types">
    <item>Cash</item>
    <item>Debit Card</item>
    <item>Credit Card</item>
    <item>Cheque</item>
  </string-array>
</resources>

然后,您可以在代码中像这样使用 payment_types 数组列表

string[] paymentTypes = Resources.GetStringArray(Resource.Array.payment_types);

随着应用程序复杂性的增长,您可能添加到项目结构中的其他文件夹包括

  • Fragments
  • 适配器

Activity 类

花一些时间更详细地了解 Android Activity 类是值得的,因为它们是构建 Xamarin.Android 应用程序的基础。

如前所述,Activity 定义了用户可以执行的一个单一、集中的操作。通常,Activity 会启动一个 Android 布局(或屏幕)。用户与布局(_.AXML_)文件交互,但功能在 Activity 中定义。

本质上,Activity 有四种状态。

  • 如果一个 Activity 处于前台,那么它就是 **活动** 或 **正在运行**。一次只能运行一个 ActivityAndroid 以堆栈结构保存所有打开的 Activity,以便根据需要弹出或压入。
  • 如果一个 Activity 失去了焦点但仍然可见,那么它就被认为是 **暂停** 的。
  • 在低内存等场景中,可以杀死一个已经暂停的 Activity,因为 Android 需要释放资源。
  • 如果一个 Activity 被另一个 Activity 完全遮挡,那么它就被认为是 **已停止** 的。
  • 如果一个 Activity 被暂停或停止,那么 Android 系统可以从内存中移除该 Activity。它可以通过请求 Activity 完成来做到这一点,或者它可以简单地杀死 Activity。当 Activity 再次显示给用户时,需要重新启动并恢复到其先前状态。

下图(摘自 Android Developers)显示了 Activity 的生命周期。

activity lifecycle

这是默认 Activity _Activity1.cs_ 的代码。

using System;

using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;

namespace MyDemo
{
    [Activity(Label = "MyDemo", MainLauncher = true, Icon = "@drawable/icon")]
    public class Activity1 : Activity
    {
        int count = 1;

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

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

            // Get our button from the layout resource,
            // and attach an event to it
            Button button = FindViewById<Button>(Resource.Id.MyButton);

            button.Click += delegate { button.Text = string.Format("{0} clicks!", count++); };
        }
    }
}

请注意 Activity 如何设置要使用的布局(或屏幕)。它通过调用 SetContentView() 函数来实现。请注意,布局名称作为参数传递给函数,而不是其 ID。Main 布局的资源定义包含在前面提到的 _Resource.Designer.cs_ 文件中。此文件将布局名称(在本例中为 Main)映射到其 ID。ID 将是一个唯一的整数值。

请注意,如何使用 Android 函数 FindViewById() 来确定 Button UI 元素的生成 ID。所有 Android UI 元素都有一个关联的 ID。此 ID 在 _Resource.Designer.cs_ 文件中定义。

资源

我们已经简要提到了 _Resource.Designer.cs_ 文件以及它如何将资源名称映射到其生成的 ID。

Android 中创建布局时,您将添加 ViewsView 是一个 Android 术语,用于指代任何 UI 元素,如按钮、文本框、复选框等)。您将为这些 UI 元素赋予一个有意义的名称(例如 btnAddNewCustomertxtCustomerSurname)。当您需要在应用程序代码中引用这些 UI 元素时,您将通过其生成的 ID 来确定其名称。

以下代码片段显示了 IdLayout 类的类定义。每个 ID 都定义为 const int。这清楚地展示了 UI 元素(布局、按钮等)如何映射到其生成的 ID。每当您向布局添加/删除 UI 元素或添加/删除布局本身时,_Resource.Designer.cs_ 文件会在您编译应用程序时自动更新。因此,无需手动编辑此文件!

namespace MyDemo
{
    public partial class Resource
    {
        public partial class Id
        {
            // aapt resource value: 0x7f050000
            public const int MyButton = 2131034112;

            private Id()
            {
            }
        }

        public partial class Layout
        {
            // aapt resource value: 0x7f030000
            public const int Main = 2130903040;

            private Layout()
            {
            }
        }
    }
}

Fragment 类

Fragment 类是构建 Xamarin.Android 应用程序时需要理解的另一个重要的 Android 元素。它们代表 Activity 中用户界面的一部分。您可以将 Fragment 视为 Activity 的模块化部分。它们拥有自己的生命周期和事件,类似于 Activity。一个 Activity 可以关联零个或多个 Fragments

Activity 一样,Fragment 可以接收自己的输入事件。您可以在运行时从 Activity 甚至从另一个 Fragment 添加/删除 FragmentFragment 可以动态地添加到不同的 Activity 和 Fragment 中,从而实现重用。

下图(摘自 Android Developers)显示了 Fragment 的生命周期。

activity lifecycle

在以下代码片段中,点击记事本按钮会调用记事本 Fragment(它是一个允许用户在应用程序中输入笔记的屏幕)。

public class MyActivity : Activity
{
    protected override void OnCreate(Bundle bundle)
    {
        var notePadButton = FindViewById<Button>(Resource.Id.btnNotepad);

        notePadButton.Click += (o, e) =>
                                       {
                                           var ft1 = FragmentManager.BeginTransaction();
                                           var current = FragmentManager.FindFragmentById(Resource.Id.layoutFrame);
                                           ft1.Remove(current);
                                           ft1.Add(Frame, new Notepad());
                                           ft1.Commit();
                                       };
    }
}

相同的代码也可以用于从应用程序的任何其他部分启动记事本 Fragment,无论是从 Activity 还是从另一个 Fragment。Fragment 支持功能重用。

Intent 类

Intent 是一个消息对象,可用于表示来自另一个组件的操作。Intent 有三个基本用途。

  • 启动一个 Activity
  • 启动一个 Service
  • 启动一个 Broadcast

在以下示例中,Admin 页面 Activity 包含一个用于启动用户组 Activity 屏幕的按钮,该屏幕负责将用户分配给用户组。这是通过使用 Intent 来实现的。

public class AdminPage : Activity
{
    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);
        SetContentView(Resource.Layout.AdminScreen);

        var assignUserButton = FindViewById<Button>(Resource.Id.btnAssignUser);
        assignUserButton.Click += (o, e) =>
        {
            var intent = new Intent(this, typeof(UserGroupActivity));
            StartActivity(intent);
            Finish();
        };
    }
}

Application 类

如果您需要在整个应用程序中维护状态,那么您将需要一个 Application 类。虽然可以将信息传递到 / 从 ActivityFragment 类,但如果您需要维护全局状态(例如当前登录的用户名),则可以使用 Application 类来实现。

[Application]
public class MyApplication : Application
{
    public string CurrentUser = "";
}

请注意,该类带有 [Application] 属性。这样就可以生成必要的元素并将其添加到 _AndroidManifest.xml_ 文件中。这确保了在创建应用程序/包时实例化该类。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
android:installLocation="auto" package="com.mydemo.apk" android:versionCode="1">
    <!--This line ensures the application class is instantiated automatically-->
    <application android:label="MyDemo" android:icon="@drawable/Icon"></application>
</manifest>

要在您自己的代码中使用 Application 类,您还需要确定当前的 Context。在 Android 中,Context 指的是应用程序/对象的当前状态。Context 类也是以下(但不限于)类的基类:

  • Activity
  • Service

有几种方法可以确定应用程序 Context。一种方法是向您的 Application 类定义添加一个返回 Context 的方法,如下面的代码所示。

[Application]
public class MyApplication : Application
{
    public Context GetContext()
    {
        return ((this).ApplicationContext);
    }
}

然后,在您的应用程序代码中,您可以像这样调用它。

MyApplication application = (MyApplication)GetContext();
String username = application.GetCurrentUser();
String password = application.GetPassword();

Android Manifest

您的 Xamarin.Android 应用程序没有此文件将无法运行。它向 Android 系统定义了有关您应用程序的重要信息,并且必须命名为 _AndroidManifest.xml_。不能使用其他名称。它与 Web 应用程序的 _web.config_ 文件大致相似。

Manifest 文件定义了必需的信息,包括(但不限于)

  • 应用程序的名称
  • 描述应用程序的组件
  • 声明权限
  • 定义最低 Android API 级别

下图显示了 manifest 文件的基本结构以及它可以包含的各种元素。

<!--?xml version="1.0" encoding="utf-8"?-->
<manifest>

    <uses-permission />
    <permission />
    <permission-tree />
    <permission-group />
    <instrumentation />
    <uses-sdk />
    <uses-configuration />  
    <uses-feature />  
    <supports-screens />  
    <compatible-screens />  
    <supports-gl-texture />  

    <application>

        <activity>
            <intent-filter>
                <action />
                <category />
                <data />
            </intent-filter>
            <meta-data />
        </activity>

        <activity-alias>
            <intent-filter> . . . </intent-filter>
            <meta-data />
        </activity-alias>

        <service>
            <intent-filter> . . . </intent-filter>
            <meta-data/>
        </service>

        <receiver>
            <intent-filter> . . . </intent-filter>
            <meta-data />
        </receiver>

        <provider>
            <grant-uri-permission />
            <meta-data />
            <path-permission />
        </provider>

        <uses-library />

    </application>

</manifest>

下面是一个 _AndroidManifest.xml_ 文件的示例。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
android:installLocation="internalOnly" android:icon="@drawable/mydemo" 
package="com.mydemo.app" android:versionCode="13" android:versionName="0">
    <uses-sdk android:targetSdkVersion="15" android:minSdkVersion="15" />
    <application android:label="MyDemo" 
    android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen" 
    android:icon="@drawable/mydemo"></application>
    <service android:enabled="true" android:name=".BackgroundService" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.BATTERY_STATS" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.CONTROL_LOCATION_UPDATES" />
    <uses-permission android:name="android.permission.DEVICE_POWER" />
    <uses-permission android:name="android.permission.FLASHLIGHT" />
    <uses-permission android:name="android.permission.FORCE_BACK" />
    <uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
    <uses-permission android:name="android.permission.GET_TASKS" />
    <uses-permission android:name="android.permission.INSTALL_LOCATION_PROVIDER" />
    <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
    <uses-permission android:name="android.permission.READ_CALENDAR" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.READ_INPUT_STATE" />
    <uses-permission android:name="android.permission.READ_LOGS" />
    <uses-permission android:name="android.permission.READ_OWNER_DATA" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.SET_ALARM" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.SET_ORIENTATION" />
    <uses-permission android:name="android.permission.SET_TIME" />
    <uses-permission android:name="android.permission.SET_TIME_ZONE" />
    <uses-permission android:name="android.permission.STATUS_BAR" />
    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_OWNER_DATA" />
    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
</manifest>

设计和架构你的应用程序

Xamarin.Android 应用程序的设计和架构主题本身就值得写一篇文章,所以我不会详细介绍。由于事后向应用程序添加设计元素更加困难,因此最好从一开始就将其牢固确立。

关于如何设计和架构 Xamarin.Android 应用程序,没有一成不变的规则,但有一些指导方针供您参考。

首先,我会创建关键 Android 类的子类,即 ActivityBaseFragmentBaseAdapterBase 等。然后用所需的通用行为填充这些类。创建新的 Android 类时,请确保它继承自您自己的派生类。这实现了重用,并使维护更容易,因为您可以在一个地方更改基本行为。

确保您的应用程序是松耦合的,并且应用程序的各个部分之间有清晰的关注点分离,这是值得的。因此,N 层架构值得探索。

  • 用户界面层
  • 业务规则层
  • 数据层

在以下示例中,登录按钮调用 Activity 的一个方法,该方法又调用一个业务规则来确定用户的凭据是否正确。该函数调用 Web 服务还是数据层函数(然后从本地数据库查找用户)无关紧要。重要的是应用程序的各个部分之间有清晰的关注点分离。应用程序的实际架构细节由开发人员决定。

public class LoginPage : Activity
{
    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);

        var userNameText = FindViewById<EditText>(Resource.Id.txtUserName);
        var passwordText = FindViewById<EditText>(Resource.Id.txtPassword);
        var loginButton = FindViewById<Button>(Resource.Id.btnLogin);
        loginButton.Click += (o, e) => PressLoginButton(userNameText.Text, passwordText.Text);
    }

    private void PressLoginButton(string username, string password)
    {
        return BizRules.IsUserAuthenticated(username, password);
    }

关于 Android 应用程序是否适合 MVC 设计模式,似乎没有达成一致。反对使用它的论点包括 Activity 同时充当 View 和 Controller,并且 Activity 有自己的事件和生命周期,这与 MVC 不符。我不会详细介绍这一点,只是让您意识到该主题缺乏普遍共识,如果您希望实现 MVC,您可以自由地进一步研究。

单元测试你的应用程序

如果您正在考虑对应用程序进行单元测试,那么您需要以一种支持它的方式来架构您的应用程序。您需要从一开始就进行这些更改,因为事后尝试修补它们将需要大量工作。此外,如果您将来考虑针对其他移动平台(iOSWindows Phone),那么在架构时考虑单元测试将确保这一点是可行的。

NUnit 不适用于 Xamarin.Android,其他单元测试框架也不适用。MonodroidMonotouch 的目的是提供一个 .NET 开发环境,允许您在不同环境之间轻松移植业务逻辑。因此,您无法真正测试 Android / iOS 特定的代码,但您可以测试通用的 .NET 业务逻辑代码。在我编写的 Monodroid 项目中,我在解决方案中创建了三个项目。一个项目是 Android 项目,另一个是包含我所有非 Android 业务逻辑的 .NET 项目,最后一个项目是 NUnit 测试项目,用于测试上述 .NET 业务逻辑项目的 T 功能。Monodroid 项目文件无法测试,但链接到 Monodroid 项目的 .NET 代码文件可以使用您选择的任何单元测试框架进行测试。

因此,您的解决方案应包含以下三个项目

  • 一个包含 Xamarin.Android 特定功能并已从 Xamarin.Android 项目模板创建的项目
  • 一个包含您的业务逻辑的项目(如有必要,您可以为数据层添加另一个项目)
  • 一个包含您的单元测试的项目

因此,对 Xamarin.Android 应用程序进行单元测试的关键是将所有 Xamarin.Android 代码保留在单独的项目中。因此,在上面描述的解决方案的三个项目中,只有一个将是 Xamarin.Android 项目。通过以这种方式创建解决方案,您将能够针对其他移动平台,因为您可以重用您的业务逻辑和测试项目。

摘要

希望本文为您开始创建自己的 Xamarin.Android 应用程序提供了足够的信息。如果您希望我进一步阐述本文中的任何内容,请随时留下评论。

© . All rights reserved.