原文:zh.annas-archive.org/md5/40c894f611f940eae635275b8cae4f95

译者:飞龙

协议:CC BY-NC-SA 4.0

第七章。地图、位置和 Google 服务

我们至今使用的标准 SDK API 提供了一套强大的工具,用于开发各种应用程序。然而,Google 还提供了一系列移动服务,如 Gmail、翻译和地图。作为开发者,我们都可以使用这些服务,以及 Google 提供的 API,以便与它们交互并将它们集成到我们的应用程序中。

小贴士

所有 Google 服务 API 的完整和最新列表可以在以下位置找到:developers.google.com/apis-explorer/#p/

由于连接到 Google 服务的应用程序使用 Google 的数据和服务器,因此需要一个简单的身份验证过程,这被称为 API 密钥。在本章中,我们将通过构建一个简单的基于地图的应用程序来展示如何进行此操作,该应用程序显示我们选择的位置。完成此操作后,我们将使用 LocationListener 来跟踪用户移动时的应用程序。

在本章中,您将:

  • 获取 API 密钥以访问 Android 的 Google Maps

  • 理解权限及其应用方法

  • 使用 GoogleApiClient 访问 LocationServices

  • 获取设备最后已知的位置

  • 使用 LocationListener 更新位置

  • 优化位置更新间隔

  • 添加 Google Maps UI 功能

  • 设置模拟位置

  • 使用 MapClickListener 获取位置

使用 Google Maps 构建位置感知应用程序

要在我们的应用程序中添加一个非常基本的 Google 地图,需要两个不同的步骤。首先,我们需要将我们的应用程序注册到 Google 并获取一个 API 密钥以唯一标识我们的应用程序;一旦地图运行起来,我们可以使用 GPS 定位我们的位置,然后放大到该位置或任何其他位置。我们首先从这些步骤中的第一个开始。

获取 API 密钥

没有任何东西可以阻止我们立即开始,尽管您需要首先检查,当我们很久以前在 第一章 中安装 SDK 的所有组件时,设置开发环境,我们包括了以下包:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_07_01.jpg

在我们开始之前需要注意的另一件事是,如果您打算使用模拟器测试此应用程序,那么您需要构建一个新的模拟器,其中系统镜像目标是 Google APIs (Google Inc.)google_apis [Google APIs],而不是 Android 5.x。第三方虚拟设备可能需要自己的配置才能运行 Play Services。完成这些后,我们就准备好创建基于位置的应用程序了:

  1. 开始一个新的 Android Studio 项目。

  2. 在适当的屏幕上选择 Google Maps Activity,之前我们选择了 Blank Activity

  3. 将其他所有内容按照向导的建议进行设置。

  4. 编辑器应打开 google_maps_api.xml 文件;如果没有,请从 res/values 目录中打开它。

  5. 检查代码。Google 将提供一个以 https://console.developers.google.com/flows/ 开头并以您的包名结尾的链接,例如 com.example.kyle.distancefinder

  6. 点击此链接,您将被带到 Google 开发者控制台。获取 API 密钥

  7. 如有需要,请注册。

  8. 您将被提示创建一个新项目。您可以随意命名,因为您将能够再次使用它来创建其他应用。

  9. 项目仪表板 下的 APIs & auth 侧边栏中,选择 APIs

  10. 启用 Google Maps Android API v2 API。

  11. 再次,在 仪表板 下的 APIs & auth 中,选择凭证并点击 创建新密钥 按钮。

  12. 在结果屏幕上,复制 API 密钥并将其粘贴到 google_maps_api.xml 文件中,在 YOUR KEY HERE 处,确保两端没有多余的空格。

  13. 现在在手机或 Google APIs 模拟器上测试应用。结果将类似于以下截图:获取 API 密钥

在我们的应用中集成基本地图非常简单。然而,通过使用 Maps Activity 向导,我们已经为我们做了很多基本工作。而且,在能够在应用中选择的位置包含地图之前,理解这一点至关重要。

查看一下 build.gradle 文件,注意依赖关系是如何为我们修改的:

dependencies {
  compile fileTree(dir: 'libs', include: ['*.jar'])
  compile 'com.google.android.gms:play-services:6.5.87'
  compile 'com.android.support:appcompat-v7:22.0.0'
}

当在其他项目中包含地图时,我们始终需要在此构建的 gms:play-services 库。support:appcompat 库可能不那么明显。它用于使应用向后兼容。在这里它不是必需的,我们将在最后一章介绍如何吸引最多用户时再回到它。

现在打开项目的 Manifest 文件。您会注意到与之前项目的一些不同,首先是以下几行:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

任何下载过 Android 应用的用户都会熟悉用户在安装前必须授予使用各种设备功能(如网络访问)权限的方式。这些标签在清单中就是这样实现的,每次您包含需要用户权限的功能时都需要应用它们。幸运的是,它们都有非常直观的参考,完整列表可以在 developer.android.com/reference/android/Manifest.permission.html 找到。

在未来的项目中还需要在清单中包含的其他元素是以下应用程序元素的两个元数据子元素。第一个会自动使我们的应用运行最新的 play 服务版本,第二个是之前获取的 API 密钥应用的地方:

<meta-data
  android:name="com.google.android.gms.version"
  android:value="@integer/google_play_services_version" />
<meta-data
  android:name="com.google.android.maps.v2.API_KEY"
  android:value="@string/google_maps_key" />

总体来说,设置 API 密钥非常简单。我们只需要注册一次,个人项目就可以为需要类似功能的 app 重复使用;说到功能,我们可能该给我们的 app 添加一些功能了。最广泛使用,并且可以说是最有用的 Google Map API 是位置服务,它允许用户使用 GPS、WiFi 和网络信号强度来定位他们的设备地理位置。

获取最后已知位置

将位置感知技术集成到我们的 app 中的第一步是确定用户的最后已知位置。这就像许多基于位置的工作一样,需要借助 GoogleApiClient 和一个作为这些服务主要入口点的接口来完成。

在添加 Java 代码之前,我们将编辑布局本身,这样我们就可以看到发生了什么。按照以下步骤获取我们设备的最后已知位置:

  1. 打开距离查找器项目。

  2. 打开activity_maps.xml布局文件。

  3. 编辑内容如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout 
    
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:orientation="vertical">
    
      <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="No last location" />
    
      <fragment
        android:id="@+id/map"
        class="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MapsActivity" />
    
    </LinearLayout>
    
  4. 打开MapsActivity.java文件。

  5. 除了为我们声明的GoogleMap之外,还包括以下字段:

    private static final String DEBUG_TAG = "tag";
    private TextView textView;
    private GoogleApiClient googleApiClient;
    
  6. 将以下代码添加到onCreate()方法中:

    textView = (TextView) findViewById(R.id.text_view);
    
    googleApiClient = new GoogleApiClient.Builder(this)
      .addApi(LocationServices.API)
      .addConnectionCallbacks(this)
      .addOnConnectionFailedListener(this)
      .build();
    
  7. 现在,更改类声明本身,使其实现以下接口:

    public class MapsActivity extends FragmentActivity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
    
  8. 这将生成一个错误。使用快速修复来实现以下方法:https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_07_04.jpg

  9. 按照以下方式完成onConnected()回调,以显示我们的位置:

    @Override
    public void onConnected(Bundle bundle) {
      Location loc = LocationServices.FusedLocationApi.getLastLocation(googleApiClient);
      Log.d(DEBUG_TAG, "Connected");
      if (loc != null) {
        textView.setText(loc.toString());
      }
    }
    
  10. 要将连接状态报告给 LogCat,编辑另外两个新方法如下:

    @Override
    public void onConnectionSuspended(int i) {
      Log.d(DEBUG_TAG, "Connection suspended");
    }
    
    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
      Log.d(DEBUG_TAG, "Connection  failed");
    }
    
  11. 按照以下方式编辑onResume()方法:

    @Override
    protected void onResume() {
      super.onResume();
      setUpMapIfNeeded();
      googleApiClient.connect();
      Log.d(DEBUG_TAG, "onResume() called - connected");
    }
    
  12. 添加一个新的onPause()方法并按照以下方式完成它:

    @Override
    protected void onPause() {
      super.onPause();
      if (googleApiClient.isConnected()) {
        googleApiClient.disconnect();
        Log.d(DEBUG_TAG, "Disconnected");
      }
    }
    
  13. 最后,按照以下方式重写setUpMap()

    private void setUpMap() {
      mMap.addMarker(new MarkerOptions()
        .position(new LatLng(51.178844, -1.826189))
        .title("Stonehenge"));
    }
    
  14. 在手机或 AVD 上运行项目。除非你在你刚刚创建的模拟器上运行 app,否则你的位置将以以下格式显示在文本视图中:

    位置[fused 51.507350,-0.127757 acc=4 et=+5m16s558ms alt=19.809023 vel=0.0]。

在这个练习的开始,我们稍微改变了布局以包括一个TextView。然而,我们仍然将片段的类设置为SupportMapFragment,并希望这能更加清晰。SupportMapFragment是我们可能在 app 中想要的所有地图容器的明显选择。它简单且几乎自动处理大多数地图过程。使用这个容器,我们几乎可以用GoogleApiClient完成所有其他需要的事情。从代码中可以看出,它允许我们设置监听器和回调来管理地图和连接活动。一旦 API 客户端连接了我们,找到我们设备的最后已知位置只需要调用单个函数getLastLocation()

确保在我们用户可能不再需要我们使用的服务时,我们断开与任何服务的连接至关重要。未能这样做会导致应用程序无谓地消耗设备的电力和数据。通过使用 onPause() 回调在 Activity 失去焦点时断开客户端,然后使用 onResume() 重新连接,我们可以确保当 Activity 可见时我们的地图保持连接,同时当不可见时我们不会浪费用户的电池和数据。

getLastLocation() 方法非常有用;它不需要网络连接或 GPS,并且可以立即获取。然而,在许多情况下,我们需要在用户移动时更新我们应用程序的位置。这是通过 LocationListen3er 回调和 LocationRequest 对象来完成的,这些内容将在下一节中展开。

请求位置更新

LocationRequest 对象高度可配置,允许我们控制请求的频率和接收到的信息的准确性。这意味着我们可以设计出不需要更多资源的应用程序,同时当应用程序的目的需要时,仍然可以提供高度准确和频繁的位置数据。

在下一阶段,我们将实现一个 LocationListener 来跟踪我们应用程序的位置。我们还将添加一个或两个功能来展示地图 API 为我们提供的某些功能。这一部分只有几行代码,如下所示:

  1. 在我们的 FragmentActivity 类声明中包含一个 LocationListener,如下所示:

    public class MapsActivity extends FragmentActivity implements
      GoogleApiClient.ConnectionCallbacks,
      GoogleApiClient.OnConnectionFailedListener,
      LocationListener {
    
  2. 这将生成一个错误。使用快速修复导入 Google 版本的 LocationListener,如下所示:https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_07_05.jpg

  3. 创建以下类字段:

    private LocationRequest locationRequest;
    
  4. onCreate() 方法中,创建以下 LocationRequest

    locationRequest = LocationRequest.create()
      .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
      .setInterval(30000)
      .setFastestInterval(5000);
    
  5. 接下来完成 onLocationChanged() 方法如下:

    @Override
    public void onLocationChanged(Location location) {
      LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, this);
      textView.setText(location.toString());
    }
    
  6. 现在扩展 setUpMap() 方法如下:

    private void setUpMap() {
      mMap.addMarker(new MarkerOptions()
        .position(new LatLng(51.178844, -1.826189))
        .title("Stonehenge")
        .snippet("3000 BC")
        .icon(BitmapDescriptorFactory.fromResource(R.mipmap.ic_launcher)));
    
      mMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
    
      mMap.setMyLocationEnabled(true);
      mMap.getUiSettings().setMyLocationButtonEnabled(true);
      mMap.getUiSettings().setZoomControlsEnabled(true);
      mMap.getUiSettings().setZoomGesturesEnabled(true);
      mMap.getUiSettings().setCompassEnabled(true);
      mMap.getUiSettings().setRotateGesturesEnabled(true);
    }
    
  7. 在您的手机或模拟器上运行项目,进行短距离散步或使用 Android 设备监控器设置模拟位置:https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_07_06.jpg

就像其他地图编码方面一样,我们在这里所做的添加非常直接。尽管有 Android 版本的 LocationListener,但 Google 的版本更容易使用,功能更强大,并且被 Google 本身强烈推荐。我们在这里使用了内置图标,但当然任何图像都可以。我们不太可能使用我们在这里设置的所有的 UI 控件,它们更多地是为了演示目的而不是实际使用。

我们设置间隔的方式很有趣。单位是毫秒,因此我们已将应用程序设置为每 30 秒请求一次新的位置。然而,使用 setFastestInterval() 允许我们利用可能请求位置的其他应用程序,因此我们可以以每 5 秒的频率更新。

我们将精度设置为尽可能高,使用 PRIORITY_HIGH_ACCURACY 常量。这显然可能会消耗用户的电量。许多应用只需要精确到几百英尺,在这种情况下,人们会使用 PRIORITY_BALANCED_POWER_ACCURACY,或者如果城市级别足够,可以使用 PRIORITY_LOW_POWER。还有 PRIORITY_NO_POWER,它将尝试在不消耗额外电量的情况下提供最佳精度。

我们还将地图设置为卫星类型。我们也可以使用其他类型,例如 MAP_TYPE_TERRAINMAP_TYPE_HYBRIDMAP_TYPE_NORMAL,具体取决于我们应用的目的。

Google 提供了一种非常简单的方法,使用 MyLocationButton 来放大我们的位置。然而,可能会有时候我们想要放大到另一个位置,或者根本不放大,甚至缩小。接下来的部分将展示如何做到这一点,以及如何从地图上的点击确定位置。

移动和动画 Google 地图

本章的最后部分在编码方面要求很少。Google 为地图提供了一个专门的点击监听器以及我们非常熟悉的回调方法。在这里,我们将使用它来放大到地图上点击的任何点。

  1. 打开 MapsActivity 文件。

  2. 在声明中,实现此接口:

    GoogleMap.OnMapClickListener
    
  3. 接下来,将以下行添加到 setUpMap() 方法中:

    mMap.setOnMapClickListener(this);
    
  4. 创建一个名为 onMapClick() 的方法,并按照以下方式完成:

    public void onMapClick(LatLng latLng) {
      textView.setText("Clicked, position = " + latLng);
      CameraPosition position = new CameraPosition.Builder()
        .target(latLng)
        .zoom(6)
        .build();
      mMap.animateCamera(CameraUpdateFactory.newCameraPosition(position));
    }
    
  5. 这就是全部内容。现在你可以运行应用了,它会在点击的任何点上放大。https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_07_07.jpg

使用 GoogleMap.OnMapClickListener 大概是显而易见的,并且它以 LatLng 对象的形式提供位置,而无需我们做任何额外的工作。缩放级别是唯一需要解释的部分,这也非常简单。有 12 个级别,其中 12 表示街道级别,1 显示整个地球。以这种方式获取位置的方式为我们打开了在应用中包含各种有用和有趣功能的大门。

摘要

随着移动设备的日益普及,能够在我们的应用中包含地图变得至关重要。Google Maps API 使得这项任务变得非常简单,可能唯一复杂的部分就是获取 API 密钥和在清单文件中设置权限。

通过能够为真实和模拟设备设置模拟位置,以及包括用户已经习惯的 UI 组件,使用地图进行开发变得更加简单。

在下一章中,我们将深入探讨 Android 5 中可能最令人兴奋的方面之一:为可穿戴设备、电视和汽车开发的能力。

第八章. 适用于电视、汽车和可穿戴设备的 App

近期 Android 发展中最令人兴奋的新方向之一,是将平台从手机和平板电脑扩展到电视、汽车仪表盘以及如手表这样的可穿戴设备。这些新设备使我们能够为现有的应用提供附加功能,同时还能创建专门为这些新环境设计的全新应用。

我们已经掌握了开发此类应用所需的技能,而本章的主要内容是解释每个平台的独特之处以及谷歌希望我们遵循的指南。当涉及到开发人们在驾驶时使用的应用时,这一点尤为重要,因为安全必须是首要考虑的因素。在开发可穿戴应用时,还需要解决某些技术问题,例如将设备与手机配对以及完全不同的 UI 和使用方法。

在本章中,您将:

  • 创建可穿戴 AVD

  • 使用 adb 命令将可穿戴模拟器连接到手机

  • 将可穿戴模拟器连接到手机模拟器

  • 创建包含移动和可穿戴模块的项目

  • 使用可穿戴 UI 库

  • 创建形状感知布局

  • 创建和自定义可穿戴设备的卡片

  • 理解可穿戴设计原则

  • 访问可穿戴传感器

  • 使应用可在 Google TV 上使用

  • 包含 Leanback 支持

  • 理解 Android Auto 安全指南

  • 配置 Auto 项目

  • 安装 Google 模拟器

  • 使用 Android 设备监控器发送短信

Android Wear

创建或修改适用于可穿戴设备的应用可能是本章中处理的三种形式因素中最复杂的一种,并且需要比其他项目更多的设置。然而,可穿戴设备通常使我们能够访问一些更有趣的新传感器,如心率监测器。通过了解它是如何工作的,我们还可以了解如何管理传感器。

如果您无法访问 Android 可穿戴设备,请不要担心,因为我们将会构建 AVD。如果您希望将实际 Android 5 手机与 AVD 配对,理想情况下您应该拥有这样一部手机。如果没有,仍然可以使用两个模拟器进行工作,但设置起来会稍微复杂一些。考虑到这一点,我们现在可以准备我们的第一个可穿戴应用。

构建和连接到可穿戴 AVD

在模拟器上独立开发和测试可穿戴应用是完全可能的,但如果我们想测试所有可穿戴功能,我们需要将其与手机或平板电脑配对。下一个练习假设您拥有实际设备。如果您没有,仍然完成任务 1 至 4,我们稍后会介绍如何使用模拟器完成剩余部分。

  1. 打开 Android Studio。在此阶段,您不需要启动项目。https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_08_01.jpg

    启动 SDK 管理器并确保已安装相关包。

  2. 打开 AVD 管理器。

  3. 创建两个新的 Android Wear AVD,一个圆形和一个方形,如下所示:https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_08_02.jpg

  4. 确保在手机上选中了 USB 调试。

  5. 从 Play Store 在此 URL 安装 Android Wear 应用程序:play.google.com/store/apps/details?id=com.google.android.wearable.app。将其连接到计算机并启动我们刚刚创建的其中一个 AVD。

  6. 定位并打开包含 adb.exe 文件的文件夹。它可能类似于 user\AppData\Local\Android\sdk\platform-tools\

  7. 使用 Shift + 右键单击,选择 在此处打开命令窗口

  8. 在命令窗口中,输入以下命令:

    • adb -d forward tcp:5601 tcp:5601
  9. 启动配套应用程序,按照说明将两个设备配对。

能够将真实世界设备连接到 AVD 是一种很好的开发形式,而不必拥有这些设备。可穿戴配套应用程序简化了连接两个设备的过程。如果你已经运行仿真器一段时间,你会注意到许多操作,如通知,会自动发送到可穿戴设备。这意味着我们的应用程序通常可以无缝地与可穿戴设备链接,而无需我们编写代码来预先处理这种情况。

adb.exeAndroid 调试桥)是我们开发工具包的重要组成部分。大多数时候,Android Studio 会为我们管理它。然而,了解它的存在以及如何与之交互是有用的。我们在这里使用它来手动在我们的可穿戴 AVD 和手机之间打开一个端口。

有许多可以从命令提示符发出的 adb 命令,其中最有用的是 adb devices,它列出了所有当前可调试的设备和仿真器,当事情不正常时非常方便,可以查看是否需要重新启动仿真器。通过使用 adb kill-serveradb start-server 分别关闭和启动 ADB。使用 adb help 将列出所有可用的命令。

小贴士

在步骤 10 中使用的端口转发命令需要在手机从计算机断开连接时每次发出。

在不编写任何代码的情况下,我们已经看到了一些内置在 Android Wear 设备中的功能以及 Wear UI 与大多数其他 Android 设备的不同之处。

即使你通常使用最新的 Android 硬件进行开发,使用仿真器通常仍然是一个好主意,特别是用于测试最新的 SDK 更新和预发布版本。如果你没有真实设备,接下来的小节将展示如何将你的可穿戴 AVD 连接到手机 AVD。

将可穿戴 AVD 与另一个仿真器连接

配对两个仿真器与配对真实设备非常相似。主要区别是我们如何在没有访问 Play Store 的情况下安装配套应用程序。按照以下步骤查看如何操作:

  1. 启动一个 AVD。这需要针对 Google API,如这里所示:https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_08_03.jpg

  2. 下载com.google.android.wearable.app-2.apk。在网上有很多地方可以通过简单的搜索找到它,我使用了www.file-upload.net/download

  3. 将文件放置在您的sdk/platform-tools目录中。

  4. 在此文件夹中Shift + 右键单击,然后选择在此处打开命令窗口

  5. 输入以下命令:

    adb install com.google.android.wearable.app-2.apk.
    
    
  6. 启动您的可穿戴 AVD。

  7. 在命令提示符中输入adb devices,确保两个模拟器都可见,输出类似于以下内容:

    List of devices attached
    emulator-5554   device
    emulator-5555   device
    
    
  8. 在命令提示符中输入adb telnet localhost 5554,其中5554是手机模拟器。

  9. 接下来,输入adb redir add tcp:5601:5601

  10. 您现在可以使用手持 AVD 上的可穿戴应用程序连接到手表。

正如我们所看到的,设置可穿戴项目比我们之前执行的一些其他练习要花更长的时间。一旦设置完成,过程与为其他形式因素开发的过程非常相似,我们现在可以继续进行。

创建可穿戴项目

我们迄今为止开发的所有应用程序都只需要一个模块,这是有道理的,因为我们只为单个设备构建。在接下来的步骤中,我们将跨两个设备进行开发,因此需要两个模块。这非常简单,您将在接下来的步骤中看到。

  1. 在 Android Studio 中创建一个新的项目,并将其命名为类似Wearable App的名称。

  2. 目标 Android 设备屏幕上,选择手机和平板电脑以及可穿戴,如下所示:https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_08_04.jpg

  3. 您将被要求添加两个活动。对于移动活动,选择空白活动,对于可穿戴设备,选择空白可穿戴活动

  4. 其他所有内容都可以保持不变。

  5. 在圆形和方形虚拟设备上运行应用程序。

您首先会注意到有两个模块,移动和可穿戴。第一个模块与我们之前看到的相同,但与可穿戴模块有一些细微的差别,值得稍微看一下。最重要的差别是WatchViewStub类。它在可穿戴模块的activity_main.xmlMainActivity.java文件中的使用方式可以查看。

这个帧布局扩展专门为可穿戴设备设计,可以检测设备的形状,以便填充适当的布局。利用WatchViewStub并不像想象中那么简单,因为适当的布局只有在WatchViewStub完成其操作后才会被填充。这意味着,为了访问布局中的任何视图,我们需要使用一个特殊的监听器,该监听器在布局被填充后会被调用。如何通过打开可穿戴模块中的MainActivity.java文件并检查onCreate()方法来了解OnLayoutInflatedListener()的工作方式,该onCreate()方法看起来如下:

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
  stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
    @Override
    public void onLayoutInflated(WatchViewStub stub) {
      mTextView = (TextView) stub.findViewById(R.id.text);
    }
  });
}

除了可穿戴应用程序和设备在开发中的设置方式外,另一个显著的不同之处在于 UI。我们用于手机和平板电脑的小部件和布局在大多数情况下不适用于手表屏幕较小的尺寸。Android 提供了一套全新的 UI 组件,我们可以使用,这就是我们接下来要探讨的内容。

设计可穿戴设备的 UI

除了在设计布局时必须考虑可穿戴设备的小尺寸外,我们还有形状问题。为圆形屏幕设计会带来自己的挑战,但幸运的是,Wearable UI 库使这一点变得非常简单。除了我们在上一节中遇到的WatchViewStub,它能够填充正确的布局外,还有一种方法可以设计一个布局,使其既适用于方形屏幕也适用于圆形屏幕。

设计布局

项目设置向导自动在build.gradle (Module: wear)文件中将此库作为依赖项包含:

compile 'com.google.android.support:wearable:1.1.0'

以下步骤演示了如何使用BoxInsetLayout创建一个形状感知布局:

  1. 打开上一节中创建的项目。

  2. 您需要三个图像,必须放置在可穿戴模块的drawable文件夹中:一个名为background_image的图像,大小约为 320 x 320 px,以及两个大小约为 50 x 50 px 的图像,分别命名为right_iconleft_icon

  3. 在可穿戴模块中打开activity_main.xml文件。

  4. 用以下代码替换其内容:

    <android.support.wearable.view.BoxInsetLayout
    
      android:background="@drawable/background_image"
      android:layout_height="match_parent"
      android:layout_width="match_parent"
      android:padding="15dp">
    
    </android.support.wearable.view.BoxInsetLayout>
    
  5. BoxInsetLayout内部添加以下FrameLayout

    <FrameLayout
      android:id="@+id/wearable_layout"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:padding="5dp"
      app:layout_box="all">
    
    </FrameLayout>
    
  6. 在其中添加以下三个视图:

    <TextView
      android:gravity="center"
      android:layout_height="wrap_content"
      android:layout_width="match_parent"
      android:text="Weather warning"
      android:textColor="@color/black" />
    
    <ImageView
      android:layout_gravity="bottom|left"
      android:layout_height="60dp"
      android:layout_width="60dp"
      android:src="img/left_icon" />
    
    <ImageView
      android:layout_gravity="bottom|right"
      android:layout_height="60dp"
      android:layout_width="60dp"
      android:src="img/right_icon" />
    
  7. 在可穿戴模块中打开MainActivity.java文件。

  8. onCreate()方法中,删除setContentView(R.layout.activity_main);行之后的所有行。

  9. 现在,在方形和圆形模拟器上运行应用程序。https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_08_05.jpg

如我们所见,BoxInsetLayout在无论屏幕形状如何的情况下都能很好地填充我们的布局。其工作原理非常简单。BoxInsetLayout创建一个正方形区域,其大小可以适应圆形屏幕的圆形。这是通过app:layout_box="all"指令设置的,该指令也可以用于定位组件,正如我们将在下一分钟看到的那样。

我们还将BoxInsetLayout的填充设置为 15 dp,而FrameLayout的填充设置为 5 dp。这会在圆形屏幕上产生 5 dp 的边距,在方形屏幕上产生 15 dp 的边距。

无论您是使用WatchViewStub并为每种屏幕形状创建单独的布局,还是使用BoxInsetLayout并仅创建一个布局文件,这完全取决于您的偏好以及您应用程序的目的和设计。无论您选择哪种方法,您无疑都希望将 Material Design 元素添加到您的可穿戴应用程序中,其中最常见且最灵活的是卡片。在下一节中,我们将探讨两种实现方式,即CardScrollViewCardFragment

添加卡片

CardFragment 类提供了一个默认的卡片视图,包含两个文本视图和一个图像。它设置起来非常简单,具有所有 Material Design 特性,如圆角和阴影,几乎适用于所有用途。它可以进行定制,正如我们将看到的,尽管 CardScrollView 通常是一个更好的选择。首先,让我们看看如何为可穿戴设备实现默认卡片:

  1. 打开当前项目中 wear 模块中的 activity_main.xml 文件。

  2. 删除或注释掉文本视图和两个图像视图。

  3. 打开 wear 模块中的 MainActivity.java 文件。

  4. onCreate() 方法中,添加以下代码:

    FragmentManager fragmentManager = getFragmentManager();
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    CardFragment cardFragment = CardFragment.create("Short title", "with a longer description");
    fragmentTransaction.add(R.id.wearable_layout, cardFragment);
    fragmentTransaction.commit();
    
  5. 在可穿戴设备模拟器中运行应用,看看默认卡片的外观。https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_08_06.jpg

我们在 第六章 中遇到了 FragmentManager通知和动作栏,在这里它以非常相似的方式运行,并且需要很少的解释。我们创建 CardFragment 的方式也非常直接。我们在这里使用了两个字符串参数,但还有一个第三个参数,即可绘制参数,如果将行更改为 CardFragment cardFragment = CardFragment.create("TITLE", "with description and drawable", R.drawable.left_icon);,那么我们将得到以下输出:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_08_07.jpg

对于可穿戴设备上的卡片,这个默认实现对于大多数用途来说都很好,并且可以通过重写其 onCreateContentView() 方法进行定制。然而,CardScrollView 是一个非常方便的替代方案,这是我们接下来要探讨的。

定制卡片

CardScrollView 在我们的布局内部定义,并且它能够检测屏幕形状并调整边距以适应每个形状。要了解这是如何实现的,请按照以下步骤操作:

  1. 打开 wear 模块中的 activity_main.xml 文件。

  2. 删除或注释掉每个元素,除了根 BoxInsetLayout

  3. BoxInsetLayout 内放置以下 CardScrollView

    <android.support.wearable.view.CardScrollView
      android:id="@+id/card_scroll_view"
      android:layout_height="match_parent"
      android:layout_width="match_parent"
      app:layout_box="bottom">
    
    </android.support.wearable.view.CardScrollView>
    
  4. 在其中添加以下 CardFrame

    <android.support.wearable.view.CardFrame
      android:layout_width="match_parent"
      android:layout_height="wrap_content">
    
    </android.support.wearable.view.CardFrame>
    
  5. CardFrame 内部添加一个 LinearLayout

  6. 向其中添加一些视图,以便预览与这里的布局相似:https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_08_08.jpg

  7. 打开 MainActivity.java 文件。

  8. 将我们添加到 onCreate() 方法中的代码替换为以下内容:

    CardScrollView cardScrollView = (CardScrollView) findViewById(R.id.card_scroll_view);
    cardScrollView.setCardGravity(Gravity.BOTTOM);
    
  9. 现在,你可以在模拟器上测试应用,这将产生以下结果:https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_08_09.jpg

小贴士

如前图所示,Android Studio 为可穿戴设备形状提供了预览屏幕。像一些其他预览一样,这些并不总是你将在设备上看到的样子,但它们允许我们通过拖放小部件来快速组合布局。

如我们所见,CardScrollViewCardFrame 的实现甚至比 CardFragment 更简单,而且更加灵活,因为我们几乎可以设计出任何我们想象的布局。我们在这里再次分配了 app:layout_box,但这次使用 bottom,使得卡片尽可能低地放置在屏幕上。

在为如此小的屏幕设计时,保持我们的布局尽可能干净和简单是非常重要的。谷歌的设计原则指出,可穿戴应用应该是可一目了然的。这意味着,就像传统的手表一样,用户应该能够快速查看我们的应用并立即获取信息,然后返回他们之前正在做的事情。

另一个谷歌的设计原则——零到低交互——用户只需要单次点击或滑动就能与我们的应用交互。带着这些原则,让我们创建一个小型应用,其中包含一些实际的功能。在下一节中,我们将利用许多可穿戴设备中找到的新心率传感器,并在显示屏上显示每分钟的当前心跳次数。

访问传感器数据

Android Wear 设备在用户手腕上的位置,使其成为健身应用的完美硬件,而且不出所料,这些应用非常受欢迎。与 SDK 的大多数功能一样,访问传感器非常简单,使用管理器和监听器类,并且只需要几行代码,正如您通过以下步骤将看到的那样:

  1. 打开本章中我们一直在工作的项目。

  2. 将背景图片替换为可能适合健身应用的图片。我使用了一个简单的爱心图片。

  3. 打开 activity_main.xml 文件。

  4. 删除除根 BoxInsetLayout 之外的所有内容。

  5. 将此 TextView 放置在其中:

      <TextView
        android:id="@+id/text_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:gravity="center"
        android:text="BPM"
        android:textColor="@color/black"
        android:textSize="42sp" />
    
  6. 打开可穿戴模块中的 Manifest 文件。

  7. 在根 Manifest 节点内添加以下权限:

    <uses-permission android:name="android.permission.BODY_SENSORS" />
    
  8. 打开可穿戴模块中的 MainActivity.java 文件。

  9. 添加以下字段:

    private TextView textView;
    private SensorManager sensorManager;
    private Sensor sensor;
    
  10. 在 Activity 中实现 SensorEventListener

    public class MainActivity extends Activity implements SensorEventListener {
    
  11. 实现监听器所需的两个方法。

  12. 编辑 onCreate() 方法,如下所示:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
    
      textView = (TextView) findViewById(R.id.text_view);
    
      sensorManager = ((SensorManager) getSystemService(SENSOR_SERVICE));
      sensor = sensorManager.getDefaultSensor(Sensor.TYPE_HEART_RATE);
    }
    
  13. 添加这个 onResume() 方法:

    protected void onResume() {
      super.onResume();
    
      sensorManager.registerListener(this, this.sensor, 3);
    }
    
  14. 以及这个 onPause() 方法:

    @Override
    protected void onPause() {
      super.onPause();
    
      sensorManager.unregisterListener(this);
    }
    
  15. 编辑 onSensorChanged() 回调,如下所示:

    @Override
    public void onSensorChanged(SensorEvent event) {
      textView.setText("" + (int) event.values[0]);
    }
    
  16. 如果您无法访问真实设备,您可以从这里下载传感器模拟器:

    code.google.com/p/openintents/wiki/SensorSimulator

  17. 应用现在已准备好测试。https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_08_10.jpg

我们首先在 AndroidManifest.xml 文件中适当的模块中添加了一个权限;这是我们之前做过的事情,每次我们使用需要用户在安装前给予权限的功能时都需要这样做。

包含背景图片可能看起来是必要的,但合适的背景确实有助于提高可一目了然的特性,因为用户可以立即知道他们正在查看哪个应用。

SensorManagerSensoronCreate()方法中的设置方式来看,很明显所有传感器都以相同的方式访问,并且可以通过不同的常量访问不同的传感器。在这里我们使用了TYPE_HEART_RATE,但任何其他传感器都可以使用适当的常量启动,所有传感器都可以使用我们在这里找到的相同的基本结构来管理,唯一的真正区别是每个传感器返回SensorEvent.values[]的方式。所有传感器的完整列表及其产生的值的描述可以在developer.android.com/reference/android/hardware/Sensor.html找到。

就像我们的应用在任何时候利用后台运行的功能一样,我们至关重要地需要在 Activity 的onPause()方法中注销我们的监听器,无论何时它们不再需要。在这里我们没有使用onAccuracyChanged()回调,但它的目的应该是清晰的,并且有许多可能的应用需要其使用。

这标志着我们对可穿戴应用及其构建方式的探索结束。这类设备继续变得越来越普遍,而且更多富有创意的使用方式的可能性是无限的。只要我们考虑人们为什么以及如何使用智能手表等设备,并通过编程实现需要最小交互性的可查看界面来利用这些设备的地理位置,Android Wear 似乎注定会越来越受欢迎和使用,开发者也将继续生产出更多创新的应用。

Android TV

与 Android Wear 相比,Android TV 位于大小谱的另一端。就像 Wear 一样,当我们为这种形态设计应用时,其大小至关重要。主要考虑因素是用户与屏幕的距离,这通常在 10 英尺左右。这意味着设计简单、干净的布局,并避免使用小而/或长的文本。

与 Wear 不同,我们为手机和平板电脑设计的许多应用也可以提供给电视。正如人们所想象的那样,这需要我们对清单进行一些调整,以便让我们的应用在 Google Play 商店中可见,供搜索特定电视应用的用户使用。此外,电视没有我们手机上的一些功能,如 GPS 和触摸屏,我们也需要考虑这一点。

Android Studio 中由项目向导生成的电视应用模板存在 bug,除非谷歌在你阅读此内容时已经修复了它,否则使用它来生成一个可工作的应用远非直截了当。尽管如此,它仍然值得一看,因为 Java 目录中包含十几个专为流媒体和电视节目等应用设计的类,这些类非常方便。

模板无法工作的事实,在我们的情况下实际上是一件好事,因为我们可以利用这个部分来了解如何从头开始构建一个兼容电视的应用程序,或者如果您愿意,如何将已经开发的应用程序转换为可以在 Android TV 上安装的形式。

以下练习可以使用空白活动模板进行,适用于手机和平板电脑或您已经开发的应用程序。

  1. 打开您项目的清单文件。

  2. 在根清单节点内部,添加以下权限:

    <uses-permission android:name="android.permission.INTERNET" />
    
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    
  3. 在同一节点中添加以下功能使用:

    <uses-feature
      android:name="android.hardware.touchscreen"
      android:required="false" />
    
    <uses-feature
      android:name="android.software.leanback"
      android:required="false" />
    
    <uses-feature
      android:name="android.hardware.microphone"
      android:required="false" />
    
    <uses-feature
      android:name="android.hardware.screen.portrait"
      android:required="false" />
    
  4. 找到一个或创建一个 320 x 180 px 的xhdpi横幅图像来代表您的应用程序。理想情况下,它应该包含文本以及一个可识别的图像,如下所示:https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_08_11.jpg

  5. 将图像放置在您的drawable文件夹中,并命名为banner

  6. 在应用程序节点内部添加以下行:

    android:banner="@drawable/banner"
    
  7. 打开您的build.gradle文件并添加以下依赖项:

    compile 'com.android.support:leanback-v17:22.0.0'
    
  8. 在清单文件中,将主题声明行更改为以下内容:

    android:theme="@style/Theme.Leanback"
    
  9. 创建一个电视 AVD 并测试应用程序。

虽然在这里我们并没有做很多,但还有很多东西需要解释。我们大部分的工作都是为了确保我们的应用程序在浏览 Play 商店的电视应用程序时是可见的。电视不支持纵向屏幕布局,因此如果需要此功能,它将简单地不会出现在 Play 商店中作为电视可用。我们希望包括支持此方向设备的特性,这就是我们如何做到这一点,同时仍然使我们的应用程序对电视用户可用。我们必须在所有电视应用程序中包含录制音频的权限,但电视通常不支持麦克风。

我们在第 7 步中添加的Leanback 支持库是开发电视应用程序的一个非常有用的工具。它提供了一个非常合适的主题Theme.Leanback,几个专为电视设计的实用小部件,并且以这种方式管理边距,以确保我们的布局不会被裁剪。如果没有它,我们可能需要设置大约 10%的宽边距来避免这种电视过度扫描。如果我们想专门为电视编写,我们也会更改主活动意图过滤器内的类别<category android:name="android.intent.category.LEANBACK_LAUNCHER" />

对于 Android TV 的编程,尤其是广播和流媒体方面,还有很多内容我们在这里无法涵盖。不过,一般来说,开发电视应用程序所需的技能与手持设备编程相同,许多应用程序在两种格式上都能运行得很好。只要我们考虑到用户与屏幕的距离以及有限的输入方式,我们就可以编写出能够在所有平台上提供满意体验的程序。

Android 操作系统是一个非常灵活的系统,非常适合各种形态。我们已经看到它可以在半英寸到家庭影院大小的屏幕上运行。Android Lollipop 预示着另一个新的令人兴奋的平台,允许用户在驾驶汽车时运行 Android 应用程序。

Android Auto

Android Auto 应用是在连接到兼容的车载仪表盘时,在驾驶员仪表盘上运行某些受限内容的应用。当为汽车开发时,主要关注的是安全,任何不符合这些严格标准的 Auto 应用都不会在 Play 商店发布。许多 Android 应用在驾驶时过于分散注意力,不适合使用。实际上,Android Auto 真正支持的功能只有两个:音频播放和文本到语音消息。本书的范围不涉及这一方面的全面探讨,但这是一个很好的时机来了解这类应用的设置方式以及如何使用 SDK 中提供的媒体和消息模拟器。首先,我们需要看看谷歌对 Auto 应用坚持的安全规则:

  • 在自动屏幕上不得有动画元素

  • 只允许音频广告

  • 应用必须支持语音控制

  • 所有按钮和可点击控件必须在两秒内响应用户操作

  • 文本必须超过 120 个字符,并且必须始终使用默认的 Roboto 字体

  • 图标必须是白色,以便系统可以控制对比度

  • 应用必须支持日间和夜间模式

  • 应用必须支持语音命令

  • 应用特定的按钮必须以不超过两秒的延迟响应用户操作

重要:这些以及其他一些规定,在发布前将由谷歌进行测试,因此您自己运行所有这些测试至关重要。完整列表可以在developer.android.com/distribute/essentials/quality/auto.html找到。

小贴士

设计适合日间和夜间模式的应用,并且可以通过系统自动在不同光照条件下保持可读性,这是一个相当详细的主题,谷歌为此提供了一份非常有用的指南,可以在commondatastorage.googleapis.com/androiddevelopers/shareables/auto/AndroidAuto-custom-colors.pdf找到。

尽管我们有这些限制,但 Auto 应用的开发方式与其他应用相同。不过有一个小差别,当为 Auto 开发时,我们需要定义我们的应用使用哪些车载功能。这是通过一个 XML 文件来完成的。按照以下简短的步骤,看看如何操作:

  1. 为手机和平板电脑启动一个新的 Android Studio 项目,或者打开一个现有的项目。

  2. res目录内创建一个新的Android 资源目录,并命名为xmlhttps://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_08_12.jpg

  3. 在这个xml文件夹内,创建一个新的 Android 资源文件,并命名为类似auto_config.xml

  4. 按照以下方式完成:

    <?xml version="1.0" encoding="utf-8"?>
    
      <automotiveApp>
        <uses name="media" />
        <uses name="notification" />
      </automotiveApp>
    
  5. 打开清单文件。

  6. application节点内添加以下行:

    <meta-data android:name="com.google.android.gms.car.application"
      android:resource="@xml/auto_config"/>
    
  7. 就这样,我们的应用现在将检测其宿主设备是否连接到车载仪表盘。

这里只需指出两点。这两个名称用途分别是用于运行音频播放应用和接收消息。只有当我们的应用被设计为同时具备这两个功能时,我们才需要两者。

Google 为测试媒体浏览和消息应用提供了模拟器,让我们可以在桌面上安全地测试项目。以下步骤演示了如何安装它们以及如何使用 telnet 连接发送模拟短信:

  1. 从 Android Studio 中打开 SDK 管理器。

  2. 确保你有最新的 Android Auto API 模拟器版本,它们位于 Extras 文件夹中。

  3. 连接一个设备或模拟器。

  4. 前往你的 sdk/extras/google/simulators 文件夹,并在此处使用 Shift + 右键点击打开命令窗口。

  5. 使用 adb devices 检查你的设备是否已连接。

  6. 输入以下两个命令来安装这两个模拟器:

    adb install media-browser-simulator.apk
    adb install messaging-simulator.apk
    
    

    小贴士

    如果你使用的是第三方虚拟设备,例如 Genymotion,你将能够通过将它们拖放到模拟器屏幕上来安装这些应用。

使用 adb 命令安装模拟器非常简单,当然,任何 .apk 文件都可以通过这种方式安装到已连接的设备上。媒体浏览器模拟器可以用大多数媒体服务进行测试,例如 Play Music。

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_08_13.jpg

可以通过从 Emulator Control 选项卡启动 Android Device Monitor 来向消息模拟器发送文本。

这就是我们在 Android Auto 中能涵盖的所有内容。该平台提供了 Lollipop 提供的最激动人心的新可能性之一,毫无疑问,Android 将在未来越来越多地出现在车辆中。

摘要

Android Wear、TV 和 Auto 与传统形态有根本性的不同,彼此之间也完全不同。这意味着我们在这里必须涵盖很多不同的领域。

尽管它们的体积和功能相对较小,但可穿戴设备为我们提供了巨大的可能性。我们现在知道如何创建和连接可穿戴 AVD,以及如何轻松地为方形和圆形设备开发。然后我们探讨了设置 TV 应用所需的条件,如何将现有应用转换为可在电视上使用,以及 Leanback 支持提供的有用库和功能。我们最后探讨了在开发过程中必须遵守的严格安全规则,以及用于测试 Auto on 的工具。

最大的改进之一,尽管可能对用户来说不太明显,涉及相机 API。这些 API 对 Lollipop 来说是全新的,而且添加多媒体到我们的应用也是我们在下一章将要讨论的内容。

第九章。相机、视频和多媒体

近年来,移动多媒体技术取得了巨大进步,许多人不仅在他们的移动设备上听音乐和看电影,而且使用它们来制作自己高质量的多媒体内容。SDK 提供了 API,允许我们包含媒体播放以及媒体捕获,并且随着相机 API 的完全重写,现在是开发 Android 多媒体应用程序的最好时机。

许多多媒体功能可以通过简单地利用系统的原生应用程序(如相机)非常容易地集成到我们的应用程序中。或者,我们可以直接与 API 合作,开发处理所有照片和视频捕获过程的应用程序,尽管这不是一个简单的任务。然而,简单实现的一件事是在我们的应用程序中包含多媒体的录制和播放,包括音频。

在本章中,你将:

  • 使用原生相机应用程序预览图像

  • 自动重构代码

  • 从原生相机保存图像到我们的应用程序

  • 处理 IO 异常

  • 创建一个唯一的文件名

  • 将图像添加到设备图库

  • 使图像私有

  • 捕获和播放视频

  • 添加视频控件

  • 处理视频中断而不会丢失位置

  • 将视频打包到应用程序中

  • 从内存中播放视频

  • 从网络流式传输视频

  • 使用 MediaRecorder 记录音频文件

  • 使用 MediaPlayer 播放音频文件

捕获图像

更多的时候,当在我们的应用程序中包含图像或视频捕获时,我们只需要利用系统已经为这些目的设计的应用程序,并且我们可以通过 Intent 调用它们,就像我们在自己的应用程序中调用 Activity 一样。我们甚至不需要知道调用的是哪个应用程序,因为系统会自动寻找最合适的,甚至在有选择的情况下为用户提供选择。

在这里,我们将在 Ancient Britain 应用程序中包含一个拍照功能,该功能利用原生相机应用程序捕获图像,将其显示在视图中并保存到特定目录。然后我们将我们的图像提供给设备图库和其他应用程序。这不是一个简短的练习,所以我们将它分成三个部分:准备和重构、预览相机拍摄,以及保存相机拍摄。

代码重构

为了节省时间,我们不会为我们的相机功能设置另一个按钮。相反,我们将重用当前用于将用户带到相关维基百科页面的 ImageView。我们还需要设置一些权限和功能使用,并添加一个新图形。按照以下步骤准备 Ancient Britain 应用程序,我们在第四章管理 RecyclerView 及其数据中开始,以包含对原生相机的调用:

  1. 在 Android Studio 中打开Ancient Britain项目并打开清单文件。

  2. 包含以下标签:

    <uses-feature android:name="android.hardware.camera"
      android:required="true" />
    
    <uses-permission android:name="android.permission.CAMERA" />
    
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
  3. 找到一个适合相机功能的图标大小的图片,例如以下所示:https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_09_01.jpg

  4. 在文件资源管理器视图中将其保存在res/drawable目录中,并用您刚刚制作的文件替换web_icon.png文件。

  5. 打开DetailActivity.java文件。

  6. 定位以下行,并在detailWebLink上右键单击:

    ImageView detailWebLink = (ImageView) findViewById(R.id.detail_web_link);
    
  7. Shift + F6重命名实例detailCameraButton

  8. 对于 XML 引用,也进行相同的操作,将其重命名为detail_camera_button

  9. 在项目资源管理器中的drawable文件夹中选择web_icon并将其重命名为camera_iconhttps://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_09_02.jpg

我们在这里做的第一件事是向清单中添加权限和功能,这里包含的功能是为了防止没有摄像头的设备在 Play 商店中找到它。

小贴士

如果您正在为 API 级别 17 或以下开发,您需要添加权限:android.permission.CAMERA

我们接下来所做的重构并非绝对必要,但它使代码更容易理解,并展示了使用F6键重命名事物是多么简单。这些效果会在整个项目中传播,例如当我们在一个 Java 中的 XML 引用中重命名时,相应的布局文件也会相应地编辑,并且通过重构菜单有许多方便的重构工具可用。

预览相机输出

为了预览相机,我们需要触发一个调用本地相机的 intent,以及当相机返回到我们的应用程序时的响应方式。这三个步骤实现了这一点:

  1. 将以下字段添加到detailActivity类中:

    private static final int PREVIEW_REQUEST_CODE = 1;
    private static final int SAVE_REQUEST_CODE = 2;
    private String photoPath;
    private File photoFile;
    
  2. 将现在detailCameraButton按钮的onClickListener中的onClick()方法替换为以下代码:

    @Override
    public void onClick(View v) {
      Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
      if (intent.resolveActivity(getPackageManager()) != null) {
        startActivityForResult(intent, PREVIEW_REQUEST_CODE);
      }
    }
    
  3. 为该类提供以下onActivityResult()方法:

      @Override
      protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == PREVIEW_REQUEST_CODE && resultCode == RESULT_OK) {
          Bundle extras = data.getExtras();
          Bitmap imageBitmap = (Bitmap) extras.get("data");
          detailImage.setImageBitmap(imageBitmap);
        } else if (requestCode == SAVE_REQUEST_CODE && resultCode == RESULT_OK) {
          // To complete
        }
      }
    

现在可以运行应用程序了。点击相机图标将允许您拍照,照片将在布局中的其他ImageView中显示。

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_09_03.jpg

我们在这里创建的 intent 是在MediaStore类上调用的,使用一个打开本地相机的常量。注意相机活动是如何通过 intent 的resolveActivity()方法受到保护的。如果没有适合请求的应用程序在设备上,并且触发了 intent,那么应用程序将会崩溃。如果没有找到合适的应用程序,PacketManager()将不包含任何内容。

当控制权交回我们的应用程序时,会调用onActivityResult()方法。requestCode用于检查相机活动是从哪里被调用的,而resultCode用于测试它是否成功。我们使用数据值对data从相机返回的 Bundle 中提取位图。这个特定的图像只是一个缩略图。完整图像是可用的,接下来我们将看到如何将其存储在 SD 卡上。

保存相机输出

为了节省时间和额外的编码,我们将使用现有的小部件作为按钮来触发保存图片以及拍照的 Intent。我们将替换主图像视图的 onTouchListeneronClickListener 并从那里调用所需的函数。按照以下步骤查看如何操作:

  1. detailActivity 类中,将 onCreate() 方法中的 detailImage.setOnTouchListener(listener); 行替换为以下代码:

    detailImage.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            takePhoto();
        }
    });
    
  2. 创建 takePhoto() 方法,如下所示:

    private void takePhoto() {
      Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
      if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        File photoFile = null;
        try {
          photoFile = filename();
        } catch (IOException ex) {
          Toast toast = Toast.makeText(getApplicationContext(),
            "No SD card",
            Toast.LENGTH_SHORT);
          toast.show();
        }
        if (photoFile != null) {
          takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
          startActivityForResult(takePictureIntent, SAVE_REQUEST_CODE);
        }
      }
    }
    
  3. 包含 filename() 方法,如下所示:

    private File filename() throws IOException {
      String time = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
      String file = MainData.nameArray[MainActivity.currentItem] + "_" + time + "_";
      File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
      File image = File.createTempFile(file, ".jpg", dir);
      photoPath = "file:" + image.getAbsolutePath();
      return image;
    }
    
  4. onActivityResult() 方法中,替换掉注释的 // To complete line with this code

    else if (requestCode == SAVE_REQUEST_CODE && resultCode == RESULT_OK) {
      Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
      Uri contentUri = Uri.fromFile(photoFile);
      intent.setData(contentUri);
      this.sendBroadcast(intent);
    }
    
  5. 您现在可以运行并测试应用。点击相机图标将替换主图像为刚刚拍摄的图像,点击图像本身将允许您将图片保存到设备的 SD 卡上的图片目录中。https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_09_04.jpg

显然,这个示例处理用户输入的方式有点笨拙。理想情况下,我们会添加新的按钮或甚至另一个 Activity 来处理预览和保存图片。我们采取这种方法是为了简洁并突出显示这些过程本身。这两个方法本身也值得仔细检查。

takePhoto() 方法触发与相机按钮的 onClick() 方法相同的 intent。使用不同的请求代码来展示我们如何调用相同的外部 Activity,但根据调用位置的不同而做出不同的响应。Android 通常管理异常相当好,但我们不能保证 SD 卡的存在,并且尝试捕获这个异常是有意义的。我们可以创建一个消息。如果 photoFile 文件的创建成功(这很少不成功),它可以通过以下行包含在我们的 Intent 中 takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));

当创建文件名时,我们需要小心不要与其他文件冲突。在没有太多预防性代码的情况下,可以通过设置一个唯一的文件名来实现,这里是通过附加时间戳来完成的。这个方法在我们尝试捕获系统异常时被调用,因此需要抛出 IOException 声明。

最后,我们在 onActivityResult() 方法的 else 子句中添加了一些代码,该子句在图片保存并控制返回到我们的应用后被调用。ACTION_MEDIA_SCANNER_SCAN_FILE Intent 是请求媒体扫描器在下次运行时将其添加到媒体数据库。这意味着我们的图片将出现在原生图库应用中,并且可供任何使用媒体数据库的其他应用使用,例如壁纸选择器。

如果你只想让你的图像在应用内部可用,仅仅省略这些行是不够的,因为图像仍然可以通过任何文件浏览软件访问。为了防止这种情况,使用Environment.getExternalFilesDir()而不是Environment.getExternalStoragePublicDirectory()。这也会在应用卸载时删除这些文件。

小贴士

媒体扫描器不一定在可预测的时间运行,在测试时,你可能需要重新启动你的设备或模拟器来强制它包含你的文件。

以这种方式控制平台的摄像头是一个非常方便的方法,可以在最少的编码下整合其功能。当然,从头开始重新创建一个摄像头或视频应用是完全可能的,我们很快就会看看如何做到这一点。首先,让我们看看如何以与这里使用摄像头相同的方式录制和播放视频。

捕获和播放视频

使用原生应用从我们自己的应用中捕获视频内容的方式几乎与我们刚刚应用的方式相同。主要区别在于,在处理视频内容时,许多功能都是由专门设计的控件VideoView提供的。我们还将添加带有MediaController的视频控制按钮,并看看如何在应用发送到后台时暂停视频。按照以下步骤构建一个简单的视频应用:

  1. 开始一个新的 Android Studio 项目。

  2. 将我们在上一个练习中包含的功能使用和权限添加到清单中。

  3. 打开activity_main.xml文件,并用以下VideoView替换TextView

    <VideoView
      android:id="@+id/video_view"
      android:layout_width="match_parent"
      android:layout_height="match_parent" />
    
  4. 打开MainActivity.java并添加以下字段:

    private static final int VIDEO_REQUEST_CODE = 1;
    private android.widget.VideoView videoView;
    private int position = 0;
    private MediaController mediaController;
    
  5. onCreate()方法中包含以下代码:

    videoView = (VideoView) findViewById(R.id.video_view);
    
    if (mediaController == null) {
      mediaController = new MediaController(this);
    }
    videoView.setMediaController(mediaController);
    
    takeVideo();.
    Add the takeVideo() method, like so:
    private void takeVideo() {
      Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
      if (takeVideoIntent.resolveActivity(getPackageManager()) != null) {
        startActivityForResult(takeVideoIntent, VIDEO_REQUEST_CODE);
      }
    }
    
  6. 然后是onActivityResult()方法:

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      if (requestCode == VIDEO_REQUEST_CODE && resultCode == RESULT_OK) {
        Uri videoUri = data.getData();
        videoView.setVideoURI(videoUri);
      }
    }
    
  7. 如果你现在测试项目,你将能够录制和播放视频。然而,如果 Activity 失去焦点并重新启动,视频也将从头开始播放。

    为了纠正这个问题,添加这两个方法:

    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
      super.onSaveInstanceState(savedInstanceState);
      savedInstanceState.putInt("Position", videoView.getCurrentPosition());
      videoView.pause();
    }
    
    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState) {
      super.onRestoreInstanceState(savedInstanceState);
      position = savedInstanceState.getInt("Position");
      videoView.seekTo(position);
    }
    
  8. 如果你现在测试应用并使用另一个应用中断播放,然后返回到 Activity,它将从上次停止的地方继续播放。https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_09_05.jpg

到第 6 步之前,我们派发意图以捕获视频的方法几乎与用于静态图像的方法相同,唯一的区别是MediaController,它添加了我们所有人都与视频播放相关联的熟悉控件。在处理视频时,尤其是处理较长的视频时,用户可能希望暂停播放并与其他应用交互。为了确保用户返回时视频从上次停止的位置继续播放,我们不得不在应用发送到后台之前使用onSaveInstanceState()方法拦截 Activity 生命周期,并在它返回时再次使用onRestoreInstanceState()。我们在这里使用了VideoView.pause()VideoView.seekTo()。以下是在VideoView中可用于控制视频播放的方法:

  • VideoView.start()

  • VideoView.pause()

  • VideoView.resume()

  • VideoView.seekTo(position)

尽管能够在我们的应用中提供视频录制功能非常有用,但很多时候我们可能想要播放应用内打包的视频或从外部源(如设备 SD 卡或甚至从互联网流式传输)的视频。上面的示例只需要进行一些小的调整,下一节将展示如何将其调整为从除相机本身以外的其他来源播放视频。

从内存和互联网播放视频

我们可能希望在应用中包含视频内容或播放其他应用产生的视频的原因不计其数,在本节中,我们将看到如何打包视频到我们的应用中,以及如何从设备的存储和互联网上播放视频。以下练习将指导您如何完成这些操作:

  1. 打开我们刚刚工作的项目。

  2. res目录下创建一个名为raw的新文件夹。https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_09_06.jpg

  3. 找到一个格式为以下之一、命名为movie的短视频文件,并将其粘贴到res/raw文件夹中:.webm.3gp.mp4.mkv

  4. 打开MainActivity文件。

  5. onCreate()方法中,注释掉对takePhoto()的调用,并添加以下两行:

    videoView.setVideoURI(Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.movie));
    videoView.start();
    
  6. 如果您想要在应用内播放视频,可以在这里停止并运行应用。

  7. 要播放存储在设备 SD 卡上的视频,将您刚才输入的行替换为以下内容:

    videoView.setVideoPath("/sdcard/some_directory/some_movie.mp4");
    videoView.start();
    
  8. 如果运行,应用现在将播放 SD 卡上的指定文件。要流式传输视频,请使用以下代码:

    videoView.setVideoPath("http://www.your_site.com/movies/movie.mp4");
    videoView.start();
    

就这些了。我们将应用内的视频存储在res/raw目录下。虽然未包含在内,但当我们创建项目时,raw是一个已识别的资源文件夹,可以用于存储我们不希望在项目构建和/或打包时编译的任何文件。

关于此代码的其他注意事项是,当从内部存储或互联网加载时,我们使用VideoView.setVideoPath()而不是VideoView.setVideoURI()

调用其他应用,例如相机应用,是一种非常方便的方式,可以在不进行大量编码的情况下集成这些功能。当然,有时我们可能希望更深入地集成相机 API。这需要从头开始构建相机,但这超出了本章的范围。然而,Android 5 确实引入了一套全新的相机 API,即android.hardware.camera2,它取代了android.hardware.Camera API。Camera2允许一些令人兴奋的新特性,例如控制单个相机和改进的存储能力,尽管这里没有足够的空间从头开始构建 camera2 应用,但 SDK 中包含了一个非常有信息量的示例,我们将现在查看它。

探索 camera2 API

camera2 API 比它们的 predecessors 复杂得多,但它们也复杂得多。从头开始构建相机 app 远非简单。幸运的是,Android SDK 中包含了许多示例 app,并且有一个合适的 camera2 样本,我们可以查看。

样本可以直接从启动窗口的快速启动面板中加载到 Android Studio,在 IDE 内部选择文件 | 导入样本…

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_09_07.jpg

大多数 camera2 过程都是从CameraManager开始的。这个类允许我们识别和连接到设备上连接的任何相机,以及确定它们的属性。在示例中,Camera2BasicFragment类是大多数有趣工作的地方,你可以看到如何在openCamera()方法中使用CameraManager打开相机,以及在setUpCameraOutputs()中使用CameraCharacteristics类获取相机 ID 以及它是否是前置相机。这个类在setUpCameraOutputs()方法中也可以看到,它被用来排除前置相机。

CameraDevice类用于在应用中表示单个相机,并用于设置CaptureRequestCaptureRequestSession,以便进行拍照的实际过程。这也使我们能够控制诸如自动对焦和白平衡等功能。《CameraCaptureSession》是提供 camera2 功能的地方,例如能够连续拍摄多张图片。

探索和实验Camer2Basic样本是非常值得的,还有一个Camera2Video样本。如果你对使用 Android 5 功能从头开始构建相机 app 感兴趣,那么官方文档developer.android.com/reference/android/hardware/camera2/package-summary.html是值得查看的。

尽管 camera2 API 非常复杂,但它们有一个严重的缺点:它们是 Android 5 API 中唯一一组难以实现向后兼容的 API。任何主要依赖相机和视频功能的 app 都需要一定数量的替代代码来使其适用于旧平台。Jelly Bean 和 KitKat 目前占据了超过四分之三的市场份额,并且看起来很可能会在未来一段时间内占据你目标受众的很大一部分。除非你计划利用 camera2 特定的功能,例如以 RAW 格式捕获图像或连续拍摄多张照片,否则你应该认真考虑使用原始的 Camera API,尽管它们已经过时,但仍然完全可用。

录制和播放音频

在本章中,我们之前看到了如何使用原生应用和 VideoView 捕获和播放多媒体内容。还有一个非常实用的工具用于录制和播放媒体文件,特别是音频:MediaRecorder 类。MediaRecorder 允许我们简单地设置音频源、输出位置和格式,同时让我们控制播放和录制功能。在这个练习中,我们将开发一个小型应用,用于录制和播放设备内置麦克风捕获的音频:

  1. 开始一个新的 Android Studio 项目。

  2. 找到三个与按钮大小相似的媒体图像,如下所示,并将它们放置在您的 drawable 文件夹中。https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_09_08.jpg

  3. 将它们命名为播放、录制和停止。

  4. 打开清单文件并包含这些权限:

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    
  5. 创建一个类似于下面的布局:https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_09_09.jpg

  6. 使用 ImageViews 作为按钮,并给它们分配 IDs record_buttonstop_buttonplay_button。将 TextView 命名为 text_view

  7. 打开 MainActivity 并包含这两个字段:

    private MediaRecorder recorder;
    private String filename;
    
  8. onCreate() 方法中添加这个 TextView

    final TextView textView = (TextView) findViewById(R.id.text_view);
    
  9. 添加这个文件路径:

    filename = Environment.getExternalStorageDirectory().getAbsolutePath() + "/recording.3gp";
    
  10. 然后添加这些 MediaRecorder 配置:

    recorder = new MediaRecorder();
    recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
    recorder.setAudioEncoder(MediaRecorder.OutputFormat.AMR_NB);
    recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
    recorder.setOutputFile(filename);
    
  11. 在布局中创建的每个 ImageView 都需要在 onCreate() 方法中添加一个 OnClickListener,如下所示:

    • ImageView recordButton:

      ImageView recordButton = (ImageView) findViewById(R.id.record_button);
      recordButton.setOnClickListener(new View.OnClickListener() {
      
        @Override
        public void onClick(View view) {
          try {
            recorder.prepare();
          } catch (IOException e) {
          }
          recorder.start();
          textView.setText("Recording...");
        }
      });
      
    • ImageView stopButton:

      ImageView stopButton = (ImageView) findViewById(R.id.stop_button);
      stopButton.setOnClickListener(new View.OnClickListener() {
      
        @Override
        public void onClick(View view) {
          recorder.stop();
          recorder.release();
          textView.setText("Recording complete");
        }
      });
      
    • ImageView playButton:

      ImageView playButton = (ImageView) findViewById(R.id.play_button);
      playButton.setOnClickListener(new View.OnClickListener() {
      
        @Override
        public void onClick(View view) {
          try {
            play();
          } catch (IOException e) {
          }
          textView.setText("Playing...");
        }
      });
      
  12. 最后,添加 play() 方法,其外观如下:

    public void play() throws IllegalArgumentException, SecurityException, IllegalStateException, IOException {
      MediaPlayer player = new MediaPlayer();
      player.setDataSource(filename);
      player.prepare();
      player.start();
    }
    
  13. 您现在可以在手机上运行该应用(因为库存模拟器还没有麦克风功能),录制和播放音频。文件 recording.3gp 可以在 SD 卡的根目录中找到。

MediaRecorder 类简化了音频录制工作,也可以用来录制视频。就像我们用来播放音频的 MediaPlayer 类一样,这个类同样易于使用。使用 MediaRecorder.release() 很重要,因为没有它,系统会继续使用资源。我们在这里只准备和播放了文件,但 MediaPlayer 可以做更多的事情,因此查看其文档是值得的,文档可以在 developer.android.com/reference/android/media/MediaPlayer.html 找到。

再次强调,我们使用了 Environment.getExternalStorageDirectory() 来自动选择用户首选的外部存储设备,尽管我们与本章早期管理多媒体的方式有所不同,但任何一种方法都可以应用于许多情况。MediaRecorderMediaPlayer 一起提供了一个简单但强大的方法,将音频整合到我们的应用中。

摘要

多媒体,如音频和视频,已成为我们使用移动设备方式的一个不可或缺的部分。无论是创建还是消费多媒体内容,在我们的应用中加入多媒体功能都能使它们更具吸引力和实用性。在本章中,我们学习了如何将原生应用,如相机,集成到我们自己的应用中,从而在过程中节省了大量编码工作。我们看到了如何捕捉、录制和回放相机图像、视频,以及最终音频。

这不仅结束了我们对 Android 5 多媒体的探索,而且也差不多结束了本书的编程部分,因为最后一章探讨了如何将我们的成品推向世界,以及如何将我们的辛勤工作转化为经济收益。本章中有一两个练习,我们将探讨如何使我们的应用向后兼容,以吸引更多的潜在用户,并且我们将最后一次回到 Ancient Britain 应用,使用 Google AdMob 服务为其添加移动广告。

第十章。发布和营销

覆盖了本书的所有主题后,如果你还没有这样做,你现在可以创建、开发和营销自己设计的应用了。当然,关于 Android 5 以及 Android 本身还有很多东西要学习,但现在我们已经了解了如何应用一些最常用的结构和对象,进一步探索 SDK 就变得相当简单。一旦我们了解了监听器接口的实现方式,那么查找文档以了解何时需要引入新的接口就变得简单了。

开发 Android 应用的整个目的就是为了分发它。尽管有众多方式可以让我们的工作对他人可用,但最明显的选择是通过 Android Play Store。本章将一步步指导你如何做到这一点。在这个过程中,我们将了解如何使我们的应用与早期版本兼容,保留 Android 5 API 的大部分功能以及许多编程到我们的 Lollipop UI 中的 Material Design 特性。

在本章中,你将:

  • 制作向后兼容的应用

  • 了解如何为旧系统创建替代布局

  • 将 Material 主题应用于旧版本

  • 用 Material Design 工具栏替换 ActionBar

  • 准备应用发布

  • 创建数字证书和私钥

  • 生成一个签名 APK 文件

  • 准备促销媒体

  • 完成商店列表

  • 发布应用

  • 学习如何通过电子邮件和网站分发应用

  • 许可应用

  • 提供产品或出版商的链接

  • 添加官方品牌

  • 构建一个用于应用内付费的模板项目

  • 包含一个 AdMob 横幅广告

制作向后兼容的应用

在整本书中,我们一直专注于为 Android 5 开发,尽管运行这个平台设备的数量注定会大幅增加,但它们仍然只占所有活跃 Android 设备的一小部分。实际上,Jelly BeanKitKat(API 16 至 19)仍然占据了访问 Google Play Store 的平台版本中的绝大多数。

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_10_01.jpg

小贴士

可以在developer.android.com/about/dashboards/index.html找到关于所有活跃设备上平台相对分布的最新报告。这个页面还包含了关于当前使用的屏幕尺寸和密度的类似信息,可以极大地帮助我们定位用户。

显然,我们希望我们的应用能够触及尽可能多的人,我们开发的许多应用只需做很少的调整就可以让运行早期版本的用户使用。幸运的是,Android 提供了支持库,如v7 AppCompat r21(或更高版本)来简化这个过程。

添加 v7 支持库

实际上,我们在开始开发之前应该仔细考虑我们希望我们的应用在哪些平台上可用;然而,为了演示目的,在接下来的简短练习中,我们将使我们在书中早期开发的应用对运行 API 16 及更高版本设备的设备可用。

  1. 打开我们之前开发的Ancient Britain应用。

  2. 打开manifest文件。

  3. 在根节点内部,包括以下标签:

    <uses-sdk android:minSdkVersion="16"
        android:targetSdkVersion="22" />
    
  4. 打开build.gradle文件并添加以下依赖项:

    compile 'com.android.support:cardview-v7:22.0.+'
    compile 'com.android.support:recyclerview-v7:22.0.+'
    compile "com.android.support:appcompat-v7:22.0.+"
    
  5. 编辑默认配置,如下所示:

    defaultConfig {
        applicationId "com.example.kyle.ancientbritain"
        minSdkVersion 16
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    
  6. 准备一个针对 API 级别 16 的 AVD 或手机:https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_10_02.jpg

  7. 在设备上运行应用。它看起来会正常工作,直到你尝试在 DetailActivity 屏幕上滑动图片,那时它会崩溃。

  8. 打开DetailActivity.java文件。

  9. onShowPress()onFling()方法中,有一个调用detailImage.setElevation()。对每个方法应用条件语句,如下所示:

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        detailImage.setElevation(4);
    }
    
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        detailImage.setElevation(0);
    }
    
  10. 再次运行应用以检查此修复是否已生效。

在清单中声明uses-sdk是必要的,因为这是 Play Store 决定哪些设备可以看到应用的方式。v7 AppCompat r21+库使得向后兼容成为可能。除此之外,它还提供了非常不错的 Material Design 小部件和其他 UI 组件。还有针对RecyclerViewCardView的库,尽管阴影不是动态的,但考虑到我们的应用现在可以触及的大量用户,这只是一个微不足道的代价。

更改最低 SDK 级别是我们使我们的应用对旧版本可用所需做的第一件事。正如我们所见,21 级或更高版本的 API 会在调用时导致应用崩溃,就像在这个任务中的setElevation()调用一样。通过能够在运行时查询设备的 API,我们有办法绕过这个限制,并且通常用户体验的质量损失很小。

另一种方便的方法来解决这个问题,是为不同的平台创建不同的布局。你可以为你的 Material Design 布局创建一个res/layout/v-21/目录,并在res/layout/中创建旧版替代方案。

要真正将Material Design的感觉带到旧平台上,我们可以利用这些库做更多的事情,比如使我们的自定义主题可用,这就是我们接下来要做的。

将 Material Design 应用于旧平台

通常在为 API 21 之前的平台开发应用时,我们在创建应用时将最低 SDK 设置为最低的目标级别,而不是像我们刚才做的那样逆向工程过程。在这里,我们将看到如何添加许多Material Design功能。以这种方式开发也是一个很好的方法来判断我们希望我们的应用向后兼容到什么程度,以及我们愿意放弃多少功能。

这个下一个练习演示了如何为 API 16 构建一个应用并应用 Material Design 到 UI。为此,请按照以下步骤操作:

  1. 在 Android Studio 中创建一个名为Material Jelly Bean的新项目。

  2. 不要在包名中使用com.example

  3. 选择手机和平板作为形态因子,并将API 16作为最小 SDK。注意支持你的应用设备数量。https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_10_03.jpg

  4. 选择空白活动并保持其他一切不变,或者选择你自己的值。

  5. 将以下内容添加到清单文件的根节点:

    <uses-sdk android:minSdkVersion="16"
            android:targetSdkVersion="22" />
    
  6. 打开res/values/styles.xml文件并按照以下方式填写:

    <resources>
        <style name="AppTheme" parent="Theme.AppCompat.Light">
            <item name="colorPrimary">#ff7d00</item>
            <item name="colorPrimaryDark">#d96a00</item>
            <item name="colorAccent">#b25900</item>
        </style>
    </resources>
    
  7. 打开activity_main.xml文件。

  8. 将根布局从相对布局更改为线性布局,并通过添加以下内容设置其方向:

    android:orientation="vertical"
    
  9. TextView转换为这个EditText

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="Hello ?"
        android:inputType="text" />
    
  10. 在此点运行应用,我们的 Material 主题调色板将被应用:https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_10_04.jpg

  11. 通过在styles.xml文件中设置主题来禁用 ActionBar:

    Theme.AppCompat.Light.NoActionBar
    
  12. EditText上方放置这个工具栏:

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"/>
    
  13. 你可能希望调整dimens.xml文件中布局的填充。

  14. 打开你的主活动文件,并将以下代码添加到onCreate()方法中:

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    if (toolbar != null) {
        setSupportActionBar(toolbar);
    }
    
  15. 这将生成一个导入错误。使用快速修复选择 v7 Toolbar。

  16. 在 API 16 设备或模拟器上运行应用,以查看 Material Design 风格的工具栏。https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_10_05.jpg

AppCompat主题系列提供了我们习惯的大多数 Material Design 功能。我们选择的代表我们应用的颜色仍然在应用中熟悉的地点出现,并着色各种小部件,使我们的应用具有一致和可识别的感觉。然而,某些元素仍然丢失,如果你在 Android 5 设备上运行应用,你会得到以下输出。

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_10_06.jpg

关于我们实现Material Toolbar的方式有两个重要的事情需要注意。首先,注意我们的MainActivity类扩展了ActionBarActivity,并且对于任何使用 AppCompat 构建的应用来说,如果它要有工具栏,这必须是这样。其次,注意我们通过setSupportActionBar()方式填充它,这与大多数视图不同。这是我们习惯于管理工具栏的方式之间的唯一两个真正差异;除此之外,其他一切都可以使用我们熟悉的类和方法来完成。

为了将 Material Design 引入早期版本,可以做一些其他的事情,但到目前为止,这已经足够让我们开始将我们的应用带给尽可能多的人。接下来,我们将转向一个更严肃的主题,即向世界发布我们的应用。

发布应用

不言而喻,你已经在各种手机和模拟器上彻底测试了你的应用,可能已经准备好了你的推广材料,并检查了Google Play 政策和协议。在发布之前有许多事情需要考虑,例如内容评级国家分布。从编程的角度来看,在我们继续之前,我们只需要检查三件事情。

  • 从项目中移除所有日志,例如:

    private static final String DEBUG_TAG = "tag";
    Log.d(DEBUG_TAG, "some info");
    
  • 确保你在清单文件中声明了应用标签图标,例如:

    android:icon="@mipmap/my_app_icon"
    android:label="@string/my_app_name"
    
  • 确保你在清单文件中声明了所有必要的权限,例如:

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

现在,我们只需再走三个步骤就能在 Google Play 商店看到我们的应用。我们所需做的就是生成一个签名发布 APK,注册为Google Play 开发者,最后将我们的应用上传到商店或在我们自己的网站上发布。还有一两种发布应用的方法,我们将在本节末尾看到它们是如何操作的。不过,首先,我们将开始生成一个准备好上传到 Google Play 商店的 APK。

生成签名 APK

所有发布的 Android 应用都需要一个数字签名的证书。这用于证明应用的真伪。与许多其他数字证书不同,这里没有权威机构,你持有签名密钥,这显然需要得到安全保护。为此,我们需要生成一个私钥,然后使用它来生成签名 APK。所有这些都可以在 Android Studio 的“生成签名 APK 向导”中完成。这些步骤将引导你完成。

  1. 打开你想要发布的应用。

  2. 从**构建 | 生成签名 APK…**菜单启动“生成签名 APK 向导”。

  3. 在第一个屏幕上选择创建新…

  4. 在下一个屏幕上,为你的密钥库提供路径和名称,并设置一个强密码。

  5. 对于别名也做同样的操作。

  6. 选择一个大于 27 年的有效期,如下所示:https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_10_07.jpg

  7. 至少填写一个证书字段。点击确定,你将被带回到向导界面。

  8. 选择发布作为构建变体,然后点击完成

  9. 现在,你已经准备好了一个可用于发布的签名 APK。

密钥库(.jks文件)可以用来存储任意数量的密钥(别名)。通常,你会为每个发布的应用使用不同的密钥,并且在生成应用的更新时必须使用相同的密钥。Google 要求证书至少有效到 2033 年 10 月 22 日,任何超过这个日期的数字都适用。

小贴士

重要提示:至少保留一个密钥的安全备份。如果你丢失了它们,你将无法开发那些应用的未来版本。

大多数安卓应用都是以这种方式打包的,只有一个例外:谷歌穿戴。如果你正在发布一个穿戴应用,你首先需要访问 developer.android.com/training/wearables/apps/packaging.html。在我们的数字证书签发并就绪后,我们现在只需两个步骤就可以发布。如果你还没有这么做,现在是时候注册成为谷歌 Play 开发者了。

注册为开发者

与签署 APK 类似,注册开发者账号同样简单。请注意,谷歌会收取一次性费用 25 美元以及你应用可能产生的收入的 30%。以下说明假设你已经拥有一个谷歌账号。

  1. 在以下位置查看支持位置

    support.google.com/googleplay/android-developer/table/3541286?hl=en&rd=1

  2. 前往开发者 Play 控制台:

    play.google.com/apps/publish/

  3. 使用你的谷歌账号登录并输入以下信息:https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_10_08.jpg

  4. 阅读并接受谷歌 Play 开发者分发协议

  5. 使用谷歌 Checkout 支付 25 美元,如果需要则创建一个账号,这样你就成为了一名注册的谷歌开发者。

如果你打算让应用在全球范围内可用,那么检查支持位置页面总是值得的,因为它会定期变化。剩下要做的就是上传我们的应用,我们现在就要这么做。

在谷歌 Play 商店发布应用

将我们的应用上传并发布到 Play 商店是通过开发者控制台完成的。正如你将看到的,在这个过程中,我们可以提供大量关于我们应用的信息和推广材料。如果你已经遵循了本章前面的步骤并且有一个准备发布的已签名的 .apk 文件,请完成以下说明以发布它。或者,你可能只是想看看目前涉及的内容以及推广材料的形式。在这种情况下,确保你拥有以下四张图像和一个已签名的 APK,并在最后选择保存草稿而不是发布应用

  • 至少两张应用截图。这些截图的任何一边都不能短于 320 像素或长于 3840 像素。

  • 如果你希望你的应用在 Play 商店对搜索平板电脑应用的用户可见,那么你应该准备至少一张 7 英寸和一张 10 英寸的截图。

  • 一张 512 x 512 像素的高分辨率图标图像。

  • 一个 1024 x 500 像素的特色图形,例如;准备好这些图像和已签名的 .apk 文件,我们就需要开始。决定你希望为应用收取多少费用(如果有的话),然后按照以下说明操作:

    1. 打开你的开发者控制台

    2. 提供一个标题并点击上传 APK按钮。

    3. 点击上传第一个生产版 APK

    4. 定位您的已签名 app-release.apk 文件。它将在 AndroidStudioProjects\YourApp\app 中。

    5. 将其拖放到建议的空间中。

    6. 当此操作完成后,您将被带到应用页面。

    7. 逐步完成前四个部分:https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_10_09.jpg

    8. 完成所有必填字段,直到 发布应用 按钮变为可点击状态。

    9. 如果您需要帮助,按钮上方的 为什么我不能发布? 链接将列出未完成的必填字段。

    10. 当所有必填字段都填写完毕后,点击页面顶部的 发布应用(或保存草稿)按钮。

    11. 恭喜!您现在是一名已发布的 Android 开发者。

我们现在知道如何将我们的应用发布到 Play 商店。当然,还有许多其他的应用市场,它们都有自己的上传流程。然而,Google Play 提供了最广泛的受众群体,是发布应用的明显选择。

在我们继续之前,还有两种其他分发方法我们将要探讨:在网站上发布和通过电子邮件分发。

通过电子邮件和网站分发

这两种方法中的第一种与听起来一样简单。如果您将 APK 附件附加到电子邮件中,并在 Android 设备上打开,当附件打开时,用户将有机会安装应用。在较新的设备上,他们可以直接从电子邮件中点击安装按钮。

小贴士

对于这两种方法,您的用户必须在设备的设置中允许安装 未知来源

从您的网站分发应用几乎与通过电子邮件发送一样简单。您需要做的只是将 APK 文件托管在您的网站上某个位置,并提供类似 <a href="download_button.jpg" download="your_apk"> 的下载链接。当从 Android 设备浏览您的网站时,点击您的链接将在他们的设备上安装您的应用。

小贴士

通过电子邮件分发无法提供对盗版的保护,因此应仅在此前提下使用。其他方法与我们期望的同样安全,但如果您想采取额外措施,Google 提供了可以在 developer.android.com/google/play/licensing 找到的 许可服务

无论我们发布的是付费应用还是免费应用,我们都希望能够触及尽可能多的用户。Google 提供了几个工具来帮助我们做到这一点,以及我们将要看到的应用盈利方式。

推广和盈利应用

很少有应用在没有先进行良好推广的情况下取得成功。有无数种方法可以做到这一点,毫无疑问,您在如何推广您的产品方面将领先一步。为了帮助您触及更广泛的受众,Google 提供了一些实用的工具来协助推广。

在查看推广工具之后,我们将探讨两种从我们的应用中赚钱的方法:应用内支付和广告。

推广应用

Google 提供了两种非常简单的方法来帮助引导人们访问 Play Store 上的我们的产品;来自两个网站和我们的应用的链接以及 Google Play 徽章,它为我们提供了官方的品牌标识。

我们可以添加指向单个应用和我们的出版商页面的链接,在那里可以浏览我们所有的应用。我们可以在我们的应用和网站上包含这些链接。

  • 要在 Play Store 中包含指向特定应用的页面的链接,请使用在 Manifest 中找到的完整包名,格式如下:

    http://play.google.com/store/apps/details?id=com.full.package.name
    
    
  • 要在 Android 应用中包含此链接,请使用:

    market://details?id= com.my.full.package.name
    
    
  • 如果您想要一个指向您的出版商页面以及所有产品的列表的链接,请使用:

    http://play.google.com/store/search?q=pub:my publisher name
    
    
  • 当从应用中链接时,请进行与之前相同的更改:

    Market://search?q=pub:my publisher name
    
    
  • 要链接到特定的搜索结果,请使用:

    search?q=my search query&c=apps.
    
  • 要使用官方 Google 徽章作为链接,请将上述元素之一替换为以下突出显示的 HTML:

    <a href="https://play.google.com/store/search?q=pub:my publisher name">
      <img alt="Get it on Google Play"
           src="img/en_generic_rgb_wo_60.png" />
    </a>
    

徽章有两种尺寸,60.png45.png,以及两种样式,"Android app on Google Play""Get it on Google Play"。只需更改相关代码以选择最适合您目的的徽章。

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_10_10.jpg

在我们的应用发布并放置了指向 Play Store 页面的链接后,现在是时候考虑如何从不可避免的下载中获利了,因此我们来到了如何货币化我们的 Android 应用。

应用货币化

从应用中赚钱有很多方法,但最受欢迎和有效的方法有两种:内购IAB)和广告。内购可能相当复杂,也许值得单独一章来介绍。在这里,我们将看到如何构建一个有效的模板,您可以用它作为您可能开发的应用内产品的基石。它将包括所有需要的库和包,以及一些非常有用的辅助类。

相比之下,在我们的应用中包含 Google AdMob 广告对我们来说现在是一个非常熟悉的过程。广告实际上只是另一个 View,可以像任何其他 Android 小部件一样被识别和引用。本章和本书的最终练习将是构建一个简单的 AdMob 示例。不过,首先,让我们看看内购。

内购

用户可以在应用内购买大量产品,从升级和可解锁内容到游戏内对象和货币。无论用户购买什么,Google 结账流程都确保他们将以与其他 Play Store 产品相同的方式支付。从开发者的角度来看,每次购买都将归结为对按钮点击的响应。我们需要安装 Google Play Billing Library,并在我们的项目中添加一个 AIDL 文件和一些辅助类。以下是方法:

  1. 开始一个新的 Android 项目或打开一个你想添加内购功能的项目。

  2. 打开 SDK 管理器。

  3. Extras 下,确保已安装 Google Play Billing Library

  4. 打开 Manifest 并应用以下权限:

    <uses-permission android:name="com.android.vending.BILLING" />
    
  5. 在 Studio 的项目面板中,右键单击 app 并选择 新建 | 文件夹 | AIDL 文件夹

  6. 从此文件夹作为 aidl,创建一个 新建 | ,并按照如下填写结果对话框:https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_10_11.jpg

  7. sdk\extras\google\play_billing 目录中找到并复制 IinAppBillingService.aidl 文件。

  8. 将文件粘贴到 com.android.vending.billing 包中。

  9. 在 Java 文件夹中创建一个 新建 | 包,从对话框中选择 ...\app\src\main\java

  10. 将包命名为 com.你的包名.util 并点击 完成

  11. play_billing 目录中,找到并打开 TrivialDrive\src\com\example\android\trivialdrivesample\util 文件夹。

  12. 将九个 Java 文件复制到您刚刚创建的 util 包中。

现在,您已经为任何希望包含应用内购买的应用有了工作模板。或者,您可以在已经开发好应用内产品的项目中完成上述步骤。无论哪种方式,您无疑都将利用 IabHelper 类,该类极大地简化了编码,为购买过程的每个步骤提供了监听器。有关 IAB 的文档可以在 developer.android.com/google/play/billing/billing_reference.html 找到。

小贴士

在您开始实现应用内购买之前,您需要为您的应用获取一个 许可证密钥。这可以在您的开发者控制台中的应用详情中找到。

付费应用和在应用内产品只是从应用中赚钱的两种方式,许多人选择另一种,通常是更有利可图的,通过广告来货币化他们的工作。Google AdMob 提供了很大的灵活性以及熟悉的编程接口,正如我们接下来将要看到的。

包含广告

我们可以从广告中赚取很多钱的方法,但 AdMob 提供了其中最简单的一种。该服务不仅允许您选择您希望广告的产品类型,而且还提供了出色的分析工具和无缝的付款到您的 Checkout 账户。

此外,AdView 可以以与我们习惯和熟悉的方法几乎相同的方式进行编程处理,正如我们将在最后的练习中看到的那样,我们将开发一个带有演示横幅 AdMob 广告的 Hello World 应用。

在开始这个练习之前,您需要先在 www.google.com/admob/ 上注册一个 AdMob 账户。

  1. 打开一个您想要测试广告的项目或开始一个新的 Android 项目。

  2. 确保您已通过 SDK 管理器安装了 Google 仓库。

  3. build.gradle 文件中,添加以下依赖项:

    compile 'com.google.android.gms:play-services:7.0.+'
    
  4. 重新构建项目。

  5. 在清单中设置这两个权限:

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    
  6. application 节点内,添加以下 meta-data 标签:

    <meta-data
        android:name="com.google.android.gms.version"
        android:value="@integer/google_play_services_version" />
    
  7. 将此第二个 Activity 包含到清单中:

    <activity
        android:name="com.google.android.gms.ads.AdActivity"
        android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
        android:theme="@android:style/Theme.Translucent" />
    
  8. 将以下字符串添加到 res/values/strings.xml 文件中:

    <string name="ad_id">ca-app-pub-3940256099942544/6300978111</string>
    
  9. 打开main_activity.xml布局文件。

  10. 将此第二个命名空间添加到根布局中:

  11. TextView下添加这个AdView

    <com.google.android.gms.ads.AdView
        android:id="@+id/ad_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        ads:adSize="BANNER"
        ads:adUnitId="@string/ad_id"></com.google.android.gms.ads.AdView>
    
  12. MainActivityonCreate()方法中插入以下行:

    AdView adView = (AdView) findViewById(R.id.ad_view);
    AdRequest adRequest = new AdRequest.Builder()
            .addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
            .build();
    
    adView.loadAd(adRequest);
    
  13. 现在在设备上测试应用。https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/andr5-prog-ex/img/B04321_10_12.jpg

大多数我们在这里做的事情都类似于我们编程其他元素的方式,只有一个或两个例外。使用ACCESS_NETWORK_STATE权限并不是严格必要的;在这里使用它是为了在请求广告之前检查连接。

任何显示广告的活动都需要一个单独的 ID,并在清单中声明。这里提供的 ID 仅用于测试目的,并且禁止使用实时 ID 进行测试。android.gms.ads包中只有六个类,所有这些类的文档都可以在developers.google.com/android/reference/com/google/android/gms/ads/package-summary找到。

AdMob 广告有两种类型,这里我们看到的是横幅广告和插屏广告,或全屏广告。我们在这里只处理了横幅广告,但插屏广告的处理方式非常相似。掌握了如何实现付费应用、应用内购买和 AdMob 的知识,我们现在可以收获我们辛勤工作的回报,并最大限度地发挥我们应用的优势。

摘要

在本章中,我们涵盖了应用开发过程的最后一个方面:打包和部署。我们首先使我们的应用向后兼容,包括我们最初为 Android 5 设计的许多功能;通过这样做,我们能够触及更广泛的受众。然后我们准备并在 Google Play Store 发布我们的应用。一旦发布,我们就看到了推广和货币化 Android 应用是多么容易。

这标志着我们进入 Android 开发世界的旅程结束。我们从安装到发布,并希望覆盖了你计划中的应用所需的大部分组件。如果你是开发新手或 IDE,如 Android Studio,并且已经通读了这本书,那么之前令人生畏的工具集现在将显得熟悉且富有成效的工作场所。

毫无疑问,Android 平台将继续以新的和意想不到的方式繁荣发展。Android 5 是一个完美的入门点;以 Material Design 为核心,拥有最强大的移动 API 集合,对于 Android 开发者来说,事情只会变得更好、更光明。

Logo

为武汉地区的开发者提供学习、交流和合作的平台。社区聚集了众多技术爱好者和专业人士,涵盖了多个领域,包括人工智能、大数据、云计算、区块链等。社区定期举办技术分享、培训和活动,为开发者提供更多的学习和交流机会。

更多推荐