Xamarin.Android 简介






4.92/5 (33投票s)
Xamarin.Android 简介
引言
本文将概述 Xamarin.Android
,它是一个适用于 Visual Studio 的 MonoDroid
插件。它允许 .NET 开发者使用他们现有的 IDE 和语言构建面向 Android 平台的应用程序,从而无需学习 Java / Eclipse。本文将介绍创建 Xamarin.Android
项目的基础知识,并解释关键元素和项目结构。
背景
Xamarin 是一家总部位于加州的软件公司,创建了 Mono
、MonoTouch
和 Mono for Android
,它们都是跨平台实现的 Common Language Infrastructure (CLI) 和 Common Language Specifications(通常称为 Microsoft.NET)。
“Monodroid 是 Visual Studio 2010 的一个插件。该插件允许开发人员将应用程序部署到本地运行的 Android 模拟器,以及通过 USB 电缆和 Wi-Fi 连接的物理设备。该插件允许 MonoDroid 与 Visual Studio 生态系统的其余部分积极协作,并与开发人员已使用的工具集成。”
使用 C# 语言,开发人员可以构建可面向 iOS、Android 和 Windows 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
项目模板列表,类似于下图所示。
正如您所见,有几个已安装的 Xamarin.Android
项目模板。在本篇文章中,我将使用 **Android 应用程序** 模板。给你的项目起一个有意义的名称(我将其命名为 _MyDemo_,但你可以随意命名)。点击 **确定** 继续创建你的项目。
项目结构
项目创建完成后,您将看到以下项目结构。
展开 **_Resources_** 文件夹及其子文件夹,如下图所示。
让我们逐一介绍项目的各个部分,并解释它们是什么以及它们的作用。我将不深入探讨标准的 .NET 元素,而是专注于 Xamarin.Android
特有的元素。
_Activity1.cs_ - 这是创建的默认 Android Activity
。Activity
是 Android 特有的实体。它们定义了用户可以执行的一个单一、集中的操作,例如添加新客户、查看商品列表等。它们有自己的生命周期和事件。它们是 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
处于前台,那么它就是 **活动** 或 **正在运行**。一次只能运行一个Activity
。Android 以堆栈结构保存所有打开的 Activity,以便根据需要弹出或压入。 - 如果一个
Activity
失去了焦点但仍然可见,那么它就被认为是 **暂停** 的。 - 在低内存等场景中,可以杀死一个已经暂停的
Activity
,因为 Android 需要释放资源。 - 如果一个
Activity
被另一个Activity
完全遮挡,那么它就被认为是 **已停止** 的。 - 如果一个
Activity
被暂停或停止,那么 Android 系统可以从内存中移除该Activity
。它可以通过请求Activity
完成来做到这一点,或者它可以简单地杀死Activity
。当Activity
再次显示给用户时,需要重新启动并恢复到其先前状态。
下图(摘自 Android Developers)显示了 Activity
的生命周期。
这是默认 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 中创建布局时,您将添加 Views
(View
是一个 Android 术语,用于指代任何 UI 元素,如按钮、文本框、复选框等)。您将为这些 UI 元素赋予一个有意义的名称(例如 btnAddNewCustomer
、txtCustomerSurname
)。当您需要在应用程序代码中引用这些 UI 元素时,您将通过其生成的 ID 来确定其名称。
以下代码片段显示了 Id
和 Layout
类的类定义。每个 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
添加/删除 Fragment
。Fragment
可以动态地添加到不同的 Activity 和 Fragment 中,从而实现重用。
下图(摘自 Android Developers)显示了 Fragment
的生命周期。
在以下代码片段中,点击记事本按钮会调用记事本 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
类。虽然可以将信息传递到 / 从 Activity
和 Fragment
类,但如果您需要维护全局状态(例如当前登录的用户名),则可以使用 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 类的子类,即 ActivityBase
、FragmentBase
、AdapterBase
等。然后用所需的通用行为填充这些类。创建新的 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,您可以自由地进一步研究。
单元测试你的应用程序
如果您正在考虑对应用程序进行单元测试,那么您需要以一种支持它的方式来架构您的应用程序。您需要从一开始就进行这些更改,因为事后尝试修补它们将需要大量工作。此外,如果您将来考虑针对其他移动平台(iOS、Windows Phone),那么在架构时考虑单元测试将确保这一点是可行的。
NUnit 不适用于 Xamarin.Android
,其他单元测试框架也不适用。Monodroid
和 Monotouch
的目的是提供一个 .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
应用程序提供了足够的信息。如果您希望我进一步阐述本文中的任何内容,请随时留下评论。