发布于2022年11月4日3年前 1、简介 注:本文代码参考 我们将开发本系列第一个应用,并借此学习一些Android基本概念以及构成应用的UI组件。 初学习,如果没能全部理解,也不必担心,后续还会涉及这些内容并有更加详细的讲解。 马上要开发的应用名叫GeoQuiz,它能给出一道道地理知识问题。用户点击TRUE或FALSE 按钮来回答屏幕上的问题,GeoQuiz即时作出反馈。 下图显示了用户点击TRUE按钮的结果: 用户点击TRUE按钮图,图1 1.1、Activity和布局文件 GeoQuiz应用由一个activity和一个布局(layout)组成。 activity是Android SDK中 Activity类的一个实例,负责管理用户与应用界面的交互。 应用的功能是通过编写 Activity 子类来实现的。对于简单的应用来说,一个 Activity 子 类可能就够了,而复杂的应用则会有多个。 GeoQuiz是个简单应用,因此它只有一个名叫 QuizActivity 的 Activity 子类。 QuizActivity它所对应的 用户界面。 布局定义了一系列用户界面对象以及它们显示在屏幕上的位置。组成布局的定义保存在 XML文件中。每个定义用来创建屏幕上的一个对象,如按钮或文本信息。 GeoQuiz应用包含一个名叫activity_quiz.xml的布局文件。该布局文件中的XML标签定义了 具体的用户界面。 QuizActivity 与activity_quiz.xml文件的关系如下图2所示: Activity java文件与布局xml文件对应关系图: 1.2、用户界面设计 Android Studio如何创建项目以及Android Studio的界面介绍这里不赘述,我们通过Android Studio(以后简称“AS”)创建好GeoQuiz应用后,首先打开app/res/layout/activity_quiz.xml文件。如果看到的是布局预览界面,请点击底部的 Text页切换显示XML代码。 当前,activity_quiz.xml文件定义了默认的activity布局。 应用activity的默认布局定义了两个组件(widget): RelativeLayout 和 TextView 。 组件是用户界面的构造模块。组件可以显示文字或图像,与用户交互,甚至布置屏幕上的其 他组件。按钮、文本输入控件和选择框等都是组件。 Android SDK内置了多种组件,通过配置各种组件可获得所需的用户界面及行为。每一个组 件都是 View 类或其子类(如 TextView 或 Button )的一个具体实例。 RelativeLayout 和 TextView 是在屏幕上的显示图 图3:http://peterboazxu.com.cn/blog/20220611/Rm5fC9tzPd9n.png?imageslim QuizActivity 的用户界面需要下列组件: 一个垂直 LinearLayout 组件; 一个 TextView 组件; 一个水平 LinearLayout 组件; 两个 Button组件。 组件是如何构成 QuizActivity 用户界面图 图4:http://peterboazxu.com.cn/blog/20220611/wSuhDNmfrEz0.png?imageslim 下面我们在activity_quiz.xml文件中定义这些组件。 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="24dp" android:text="@string/question_text" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/true_button" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/false_button" /> </LinearLayout> </LinearLayout> 上述代码暂时不理解也没关系,后续学习中逐渐弄明白的。 注意,开发工具无法校验布局XML内容,拼写错误早晚会出问题,应尽量避免。 可以看到,有三行以 android:text 开头的代码出现了错误信息。暂时忽略它们,稍后会 处理。 对照上图用户界面查看XML文件,可以看出组件与XML元素一一对应。元素的名 称就是组件的类型。 各元素均有一组XML属性。属性可以看作如何配置组件的指令。 为方便理解元素与属性的工作原理,接下来我们将以层级视角来研究布局。 1.2.1、视图层级结构 组件包含在视图(View)对象的层级结构中,这种结构又称作视图层级结构(view hierarchy)。 上述代码所示的XML布局对应的视图层级结构图 图5: 从布局的视图层级结构可以看到,其根元素是一个 LinearLayout 组件。作为根元素, LinearLayout 组件必须指定Android XML资源文件的命名空间属性,这里是http://schemas. android.com/apk/res/android。 LinearLayout 组件继承自 ViewGroup 组件(也是个 View 子类)。 ViewGroup 组件是包含并配 置其他组件的特殊组件。想要以一列或一排的样式布置组件,就可以使用 LinearLayout 组件。 其他 ViewGroup 子类还有 FrameLayout 、 TableLayout 和 RelativeLayout 。 若某个组件包含在一个 ViewGroup 中,该组件与 ViewGroup 即构成父子关系。根 Linear- Layout 有两个子组件: TextView 和另一个 LinearLayout 。作为子组件的 LinearLayout 自己还 有两个 Button 子组件。 1.2.2、组件属性 看看配置组件时常用的一些属性。 android:layout_width 和 android:layout_height 属性 几乎每类组件都需要 android:layout_width 和 android:layout_height 属性。以下是它 们的两个常见属性值(二选一)。 match_parent :视图与其父视图大小相同。 wrap_content :视图将根据其显示内容自动调整大小。 (以前还有个 fill_parent 属性值,等同于 match_parent ,现已废弃不用。) 根 LinearLayout 组件的高度与宽度属性值均为 match_parent 。 LinearLayout 虽然是根元 素,但它也有父视图——Android提供该父视图来容纳应用的整个视图层级结构。 其他包含在界面布局中的组件,其高度与宽度属性值均被设置为 wrap_content 。 TextView 组件比其包含的文字内容区域稍大一些,这主要是 android:padding="24dp" 属性 的作用。该属性告诉组件在决定大小时,除内容本身外,还需增加额外指定量的空间。这样屏幕 上显示的问题与按钮之间便会留有一定的空间,使整体显得更为美观。(不理解dp的意思?dp即 density-independent pixel,指与密度无关的像素。) 2. android:orientation 属性 android:orientation 属性是两个 LinearLayout 组件都具有的属性,它决定两者的子组件 是水平放置还是垂直放置。根 LinearLayout 是垂直的,子 LinearLayout 是水平的。 子组件的定义顺序决定其在屏幕上显示的顺序。在垂直的 LinearLayout 中,第一个定义的 子组件出现在屏幕的最上端;而在水平的 LinearLayout 中,第一个定义的子组件出现在屏幕的 最左端。(如果设备文字从右至左显示,如阿拉伯语或者希伯来语,第一个定义的子组件则出现 在屏幕的最右端。) 3. android:text 属性 TextView 与 Button 组件具有 android:text 属性。该属性指定组件要显示的文字内容。 请注意, android:text 属性值不是字符串值,而是对字符串资源(string resource)的引用。 字符串资源包含在一个独立的名叫strings的XML文件中(strings.xml),虽然可以硬编码设置 组件的文本属性值,如 android:text="True" ,但这通常不是个好主意。 比较好的做法是:将文 字内容放置在独立的字符串资源XML文件中,然后引用它们。这样会方便应用的本地化(支持 多国语言)。 需要在activity_quiz.xml文件中引用的字符串资源还没添加。现在就来处理。 1.2.3、创建字符串资源 每个项目都包含一个默认字符串资源文件strings.xml。 在项目工具窗口中,找到app/res/values目录,展开目录,打开strings.xml文件。 可以看到,项目模板已经添加了一些字符串资源。如下代码所示,添加应用布局需要 的三个新的字符串。 <resources> <string name="app_name">GeoQuiz</string> <string name="question_text">Canberra is the capital of Australia.</string> <string name="true_button">True</string> <string name="false_button">False</string> </resources> (某些版本的Android Studio的strings.xml默认带有其他字符串,请勿删除它们,否则会引发与 其他文件的联动错误。) 现在,在GeoQuiz项目的任何XML文件中,只要引用到 @string/false_button ,应用运行 时,就会得到文本“False”。 保存strings.xml文件。这时,activity_quiz.xml布局缺少字符串资源的提示信息应该就消失了。 (如仍有错误提示,请检查一下这两个文件,确认没有拼写错误。) 默认的字符串文件虽然已命名为strings.xml,仍可以按个人喜好重命名。一个项目也可以 有多个字符串文件。只要这些文件都放在res/values/目录下,含有一个 resources 根元素,以及多 个 string 子元素,应用就能找到并正确使用它们。 1.3、从布局 XML 到视图对象 知道activity_quiz.xml中的XML元素是如何转换为视图对象的吗?答案就在于 QuizActivity 类。 在创建GeoQuiz项目的同时,向导也创建了一个名叫 QuizActivity 的 Activity 子类。 QuizActivity 类文件存放在项目的app/java目录下。java目录是项目全部Java源代码的存放处。 在项目工具窗口中,依次展开app/java目录与com.bignerdranch.android.geoquiz包。找到并打 开QuizActivity.java文件,查看其中的代码,如下所示: import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class QuizActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_quiz); } } 是不是不明白 AppCompatActivity 的作用?它实际就是一个 Activity 子类,能为Android 旧版本系统提供兼容支持。 如果无法看到全部类包导入语句,请单击第一行导入语句左边的⊕符号来显示它们。 该Java类文件有一个 Activity 方法: onCreate(Bundle) 。 (如果你的文件还包含 onCreateOptionsMenu(Menu) 和 onOptionsItemSelected(MenuItem) 方法,暂时不用理会) activity子类的实例创建后, onCreate(Bundle) 方法会被调用。activity创建后,它需要获取 并管理用户界面。要获取activity的用户界面,可调用以下 Activity 方法: public void setContentView(int layoutResID) 根据传入的布局资源ID参数,该方法生成指定布局的视图并将其放置在屏幕上。布局视图生 成后,布局文件包含的组件也随之以各自的属性定义完成实例化。 1.3.1、资源与资源 ID 布局是一种资源。资源是应用非代码形式的内容,如图像文件、音频文件以及XML文件等。 项目的所有资源文件都存放在目录app/res的子目录下。在项目工具窗口中可以看到, activity_quiz.xml布局资源文件存放在res/layout/目录下。strings.xml字符串资源文件存放在 res/values/目录下。 可以使用资源ID在代码中获取相应的资源。activity_quiz.xml布局的资源ID为R.layout. activity_quiz。 查看GeoQuiz应用的资源ID需要切换项目视图。Android Studio默认使用Android项目视图, 图6所示。为让开发者专注于最常用的文件和目录,默认视图隐藏了Android项目的真实文件 目录结构。 图6:http://peterboazxu.com.cn/blog/20220612/h3OaemWhbsFs.png?imageslim 在项目工具窗口的最上部找到下拉菜单,从Android项目视图切换至Project视图。Project视图 会显示出当前项目的所有文件和目录。 展开目录app/build/generated/source/r/debug,找到项目包名称并打开其中的R.java文件,即可 看到GeoQuiz应用当前所有的资源。R.java文件在Android项目编译过程中自动生成,如该文件头 部的警示所述,请不要修改该文件的内容。 修改布局或字符串等资源后,R.java文件不会实时刷新。Android Studio另外还存有一份代码 编译用的R.java隐藏文件。当前代码编辑区打开的R.java文件仅在应用安装至设备或模拟器前产 生,因此只有在Android Studio中点击运行应用时,它才会得到更新。 R.java文件通常比较大,如下所示: /* AUTO-GENERATED FILE. DO NOT MODIFY. * * This class was automatically generated by the * aapt tool from the resource data it found. It * should not be modified by hand. */ public final class R { public static final class anim { ... } ... public static final class id { ... } public static final class layout { ... public static final int activity_quiz=0x7f030017; } public static final class mipmap { public static final int ic_launcher=0x7f030000; } public static final class string { ... public static final int app_name=0x7f0a0010; public static final int false_button=0x7f0a0012; public static final int question_text=0x7f0a0014; public static final int true_button=0x7f0a0015; } } 可以看到R.layout.activity_quiz即来自该文件。 activity_quiz 是 R 的内部类 layout 里的一个 整型常量名。 GeoQuiz应用需要的字符串同样具有资源ID。目前为止,我们还未在代码中引用过字符串, 如果需要,可以使用以下方法: setTitle(R.string.app_name); Android为整个布局文件以及各个字符串生成资源ID,但activity_quiz.xml布局文件中的组件 除外,因为不是所有组件都需要资源ID。在本章中,我们要在代码里与两个按钮交互,因此只需 为它们生成资源ID即可。 这里主要使用Android项目视图,生成资源ID之前,记得切回。当然,如果你就喜欢使用Project 视图,也没啥问题。 要为组件生成资源ID,请在定义组件时为其添加 android:id 属性。在activity_quiz.xml文件 中,分别为两个按钮添加上 android:id 属性,如下代码所示: <LinearLayout ... > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="24dp" android:text="@string/question_text" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:id="@+id/true_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/true_button" /> <Button android:id="@+id/false_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/false_button" /> </LinearLayout> </LinearLayout> 注意, android:id 属性值前面有一个+标志,而 android:text 属性值则没有。这是因为我 们在创建资源ID,而对字符串资源只是做引用。 1.4、组件的实际应用 按钮有了资源ID,就可以在 QuizActivity 中直接获取它们。首先,在QuizActivity.java文件 中增加两个成员变量。 在QuizActivity.java文件中输入代码: public class QuizActivity extends AppCompatActivity { private Button mTrueButton; private Button mFalseButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_quiz); } } 文件保存后,会看到两个错误提示。没关系,稍后会处理。请注意新增的两个成员(实例) 变量名称的 m 前缀。该前缀是Android编程应遵循的命名约定。 现在,将鼠标移至代码左边的错误提示处时,会看到两条同样的错误信息:Cannot resolve symbol 'Button'。 这告诉我们,需要在QuizActivity.java文件中导入 android.widget.Button 类包。可在文件 头部手动输入以下代码: import android.widget.Button; 或者使用Option+Return(Alt+Enter)组合键,让Android Studio自动为你导入。代码有误时, 也可以使用该组合键来修正。记得要常用。 类包导入后,刚才的错误提示应该就消失了。(如果仍然存在,请检查Java代码以及XML文 件,确认是否存在输入或拼写错误。) 接下来,我们来编码使用按钮组件,这需要以下两个步骤: 引用生成的视图对象; 为对象设置监听器,以响应用户操作。 1.4.1 引用组件 在activity中,可调用以下 Activity 方法引用已生成的组件: public View findViewById(int id) 该方法以组件的资源ID作为参数,返回一个视图对象。 在QuizActivity.java文件中,使用按钮的资源ID获取视图对象,赋值给对应的成员变量。注意,赋值前,必须先将返回的 View 类型转换为 Button 。 public class QuizActivity extends AppCompatActivity { private Button mTrueButton; private Button mFalseButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_quiz); mTrueButton = (Button) findViewById(R.id.true_button); mFalseButton = (Button) findViewById(R.id.false_button); } } 1.4.2 设置监听器 Android应用属于典型的事件驱动类型。不像命令行或脚本程序,事件驱动型应用启动后, 即开始等待行为事件的发生,如用户点击某个按钮。(事件也可以由操作系统或其他应用触发, 但用户触发的事件更直观,如点击按钮。) 应用等待某个特定事件的发生,也可以说应用正在“监听”特定事件。为响应某个事件而创 建的对象叫作监听器(listener)。监听器会实现特定事件的监听器接口(listener interface)。 无需自己动手,Android SDK已经为各种事件内置了很多监听器接口。当前应用需要监听用 户的按钮“点击”事件,因此监听器需实现 View.OnClickListener 接口。 首先处理TRUE按钮。在QuizActivity.java文件中,在 onCreate(Bundle) 方法的变量赋值语 句后输入下列代码 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_quiz); mTrueButton = (Button) findViewById(R.id.true_button); mTrueButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // Does nothing yet, but soon! } }); mFalseButton = (Button) findViewById(R.id.false_button); } (如果遇到View cannot be resolved to a type的错误提示,请使用Option+Return(Alt+Enter)快 捷键导入 View 类。) 在上面代码中,我们设置了一个监听器。按钮 mTrueButton 被点击后,监听器会立即通 知我们。传入 setOnClickListener(OnClickListener) 方法的参数是一个监听器。它是一个实 现了 OnClickListener 接口的对象。 使用匿名内部类 这里,一个匿名内部类(anonymous inner class)实现了 OnClickListener 接口。语法看上去 稍显复杂,不过有个助记小技巧:最外层一对括号内的全部代码就是传入 setOnClickListener (OnClickListener) 方法的参数。 mTrueButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // Does nothing yet, but soon! } }); 后续所有的监听器都以匿名内部类来实现。这样做有两大好处: 第一,使用匿名内部类,可 以相对集中地实现监听器方法,一眼可见; 第二,事件监听器一般只在一个地方使用,使用匿名 内部类,就不用去创建繁琐的命名类了。 匿名内部类实现了 OnClickListener 接口,因此它也必须实现该接口唯一的 onClick(View) 方法。 onClick(View) 现在是个空方法。虽然必须实现 onClick(View) 方法,但具体如何实现取 决于使用者,因此即使是个空方法,编译器也可以编译通过。 (如果匿名内部类、监听器、接口等概念已忘得差不多了,现在就该去复习一下,或找本参 考手册备查。) 1.5、创建提示消息 接下来要实现的是,分别点击两个按钮,弹出我们称之为toast的提示消息。Android的toast 是用来通知用户的简短弹出消息,用户无需输入什么,也不用做任何干预操作。这里,我们要用 toast来反馈答案,如图7所示:http://peterboazxu.com.cn/blog/20220611/hNIwodY2Nofv.png?imageslim 首先回到strings.xml文件,如下代码所示,为toast添加消息显示用的字符串资源。 <resources> <string name="app_name">GeoQuiz</string> <string name="question_text">Canberra is the capital of Australia.</string> <string name="true_button">True</string> <string name="false_button">False</string> <string name="correct_toast">Correct!</string> <string name="incorrect_toast">Incorrect!</string> </resources> 调用 Toast 类的以下方法,可创建toast: public static Toast makeText(Context context, int resId, int duration) 该方法的 Context 参数通常是 Activity 的一个实例( Activity 本身就是 Context 的子类)。 第二个参数是toast要显示字符串消息的资源ID。 Toast 类必须借助 Context 才能找到并使用字符 串资源ID。第三个参数通常是两个 Toast 常量中的一个,用来指定toast消息的停留时间。 创建toast后,可调用 Toast.show() 方法在屏幕上显示toast消息。 在 QuizActivity 代码里,分别调用 makeText(...) 方法。在添加 makeText(...) 时,可利用Android Studio的代码自动补全功能,让代码输入更轻松。 使用代码自动补全 代码自动补全功能可以节约大量开发时间,越早掌握受益越多。 依次输入下面代码。当输入到 Toast 类后的点号时,Android Studio会弹出一 个窗口,窗口内显示了建议使用的 Toast 类的常量与方法。 要选择需要的建议方法,使用上下键。(如果不想使用代码自动补全功能,请不要按Tab键、 Return/Enter键,或使用鼠标点击弹出窗口,只管继续输入代码直至完成。) 在建议列表里,选择 makeText(Context context, int resID, int duration) 方法,代 码自动补全功能会自动添加完整的方法调用。 完成 makeText 方法的全部参数设置,完成后的代码如下代码所示: mTrueButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(QuizActivity.this, R.string.correct_toast, Toast.LENGTH_SHORT).show(); // Does nothing yet, but soon! } }); mFalseButton = (Button) find ViewById(R.id.false_button); mFalseButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(QuizActivity.this, R.string.incorrect_toast,Toast.LENGTH_SHORT).show(); } }); 在 makeText(...) 里,传入 QuizActivity 实例作为 Context 的参数值。注意此处应输入的 参数是 QuizActivity.this ,不要想当然地直接输入 this 。因为匿名类的使用,这里的 this 指 的是监听器 View.OnClickListener 。 使用代码自动补全功能,自己也就不用导入 Toast 类了,因为Android Studio会自动导入相 关类。 好了,现在可以运行应用了。 1.6、 使用模拟器运行应用 运行Android应用需使用硬件设备或虚拟设备(virtual device)。包含在开发工具中的Android 设备模拟器可提供多种虚拟设备。 要创建Android虚拟设备(AVD),在Android Studio中,选择Tools → Android → AVD Manager 菜单项。AVD管理器窗口弹出时,点击窗口左下角的+Create Virtual Device…按钮。一路next,详细的创建不赘述了。 创建完成后即可启动运行。 AVD创建成功后,我们用它运行GeoQuiz应用。点击Android Studio工具栏上的Run按钮,或 者使用Control+R快捷键。Android Studio会自动找到新建的虚拟设备,安装应用包(APK),然后 启动并运行应用。 模拟器的启动过程比较耗时,请耐心等待。等设备启动完成,应用运行后,就可以在应用界 面点击按钮,让toast告诉你答案了。 假如启动时或在点击按钮时,GeoQuiz应用崩溃,可以在Android DDMS工具窗口的LogCat 视图中看到有用的诊断信息。(如果LogCat没有自动打开,可点击Android Studio窗口底部的 Android Monitor按钮打开它。),查看日志。 最好不要关掉模拟器,这样就不必在反复运行调试应用时,浪费时间等待AVD启动了。 单击AVD模拟器上的后退按钮可以停止应用。这个后退按钮的形状像一个指向左侧的三角形 (在较早版本的Android中,它像一个U型箭头)。需要调试变更时,再通过Android Studio重新运 行应用。 模拟器虽然好用,但在实体设备上测试应用能获得更准确的结果。 1.7、深入学习:Android 编译过程 学习到这里,你可能迫切想知道Android是如何编译的。 你已经知道在项目文件发生变化时, Android Studio无需指示便会自动进行编译。 在整个编译过程中,Android开发工具将资源文件、 代码以及AndroidManifest.xml文件(包含应用的元数据)编译生成.apk文件。.apk文件要在模拟器上 运行,还需以debug key签名。(分发.apk应用给用户时,应用必须以release key签名。更多有关编译 过程的信息,可参考Android开发文档网页developer.android.com/tools/publishing/preparing.html。) 那么,应用的activity_quiz.xml布局文件的内容该如何转变为 View 对象呢?作为编译过程的 一部分,aapt(Android Asset Packaging Tool)将布局文件资源编译压缩紧凑后,打包到.apk文件 中。 然后,在 QuizActivity 类的 onCreate(Bundle) 方法调用 setContentView(...) 方法时, QuizActivity 使用 LayoutInflater 类实例化布局文件中定义的每一个 View 对象,如图8所示:http://peterboazxu.com.cn/blog/20220611/6UdpR7BDGBza.png?imageslim 1.7.1、Android 编译工具 当前,我们看到的项目编译都是在Android Studio里执行的。编译功能已整合到IDE中,IDE 负责调用aapt等Android标准编译工具,但编译过程本身仍由Android Studio管理。 有时,出于某种原因,可能需要脱离Android Studio编译代码。最简单的方法是使用命令行 编译工具。现代Android编译系统使用Gradle编译工具 要从命令行使用Gradle,请切换到项目目录并执行以下命令: $ ./gradlew tasks 如果是Windows系统,执行以下命令: gradlew.bat tasks 执行以上命令会显示一系列可用任务。你需要的任务是installDebug,因此,再执行以下命令: $ ./gradlew installDebug 如果是Windows系统,执行以下命令: gradlew.bat installDebug 以上命令将把应用安装到当前连接的设备上,但不会运行它。要运行应用,需要在设备上手 动启动。
创建帐户或登录后发表意见