面向大从的移动打桩其它四篇文章地址(校对添加):
(一)、android简介;
(二)、轻轻一划,在android中为手势编码;
添加一个多选择quiz到你的Android手机app,然后用一个安全数字证书签名
用网络逻辑,内容为王。但是对与手机用户来说,交互规则才是王道。对移动app静态信息设计在减少,并且游戏化正在增多。这个月 Andrew Glover决定通过将一个多选择的quiz特性加入到一个示例app(Overheard Word,前一篇介绍的。) 中来介绍 Android移动开发。之后他将展示给你如何生成一个数字证书和如何发布和如何提升你的在Google Play或者Amazon Appstore上 已经签名的app。
目前为止在这个Mobil for the masses系列中,我们已经使用Android作为学习怎样做移动开发的一个实例,其中包括 《Android应用程序生命周期》教程,在你的Android apps中实现《手势滑动功能》。并且《与第三方库工作》来简化开发并且增强app功 能。当我不确定做Android时候,我对浏览其它的手机环境和技术感兴趣。所以这个月我们来 通过添加一个quiz特性到Overheard Word 示例app 总结我们的 Android-intensive(加强安卓)文章 并且准备部署它到两个流行的 Adroid app stores:Google Play和Amazon Appstore。所有的这些将是下一节的基础:HTML5侵袭移动开发!
游戏化我的app
在我们签名Overheard Word并且把它推送到Google Play和Amazon Appstore的Android市场同数百万的其 它apps竞争前,我想要确定它是***的Overheard Word app(对我们的Overheard Word app不熟悉么?回顾下介绍这 个 示例 的文章)。如你所知,游戏是当前推动移动生态系统的强劲动力,并且一系列的apps都被期望有高效的交互。移动apps即使当他们的目标是提供 信息价值的时候,点燃好奇心和获胜的欲望的移动应用也做的很好。那也是为什么Overheard Word 不仅仅只是一个页面上的单词列表;相反,它被 设计用来煽动读者来挖掘词汇量,接着奖励他们来坚持学习它!(顺便说下Gamification 游戏化 是一个正在开始流行的设计技术的术语)
在我们签名Overheard Word并且把它推送到Google Play和Amazon Appstore的Android市场同数百万的其它 apps竞争前,我想要确定它是***的Overheard Word app(对我们的Overheard Word app不熟悉么?回顾下介绍这 个 示例 的文章)。如你所知,游戏是当前推动移动生态系统的强劲动力,并且一系列的apps都被期望有高效的交互。移动apps即使当他们的目标是提供 信息价值的时候,点燃好奇心和获胜的欲望的移动应用也做的很好。那也是为什么Overheard Word 不仅仅只是一个页面上的单词列表;相反,它被 设计用来煽动读者来挖掘词汇量,接着奖励他们来坚持学习它!(顺便说下Gamification 游戏化 是一个正在开始流行的设计技术的术语)
在我们签名Overheard Word并且把它推送到Google Play和Amazon Appstore的Android市场同数百万的其它 apps竞争前,我想要确定它是***的Overheard Word app(对我们的Overheard Word app不熟悉么?回顾下介绍这 个 示例 的文章)。如你所知,游戏是当前推动移动生态系统的强劲动力,并且一系列的apps都被期望有高效的交互。移动apps即使当他们的目标是提供 信息价值的时候,点燃好奇心和获胜的欲望的移动应用也做的很好。那也是为什么Overheard Word 不仅仅只是一个页面上的单词列表;相反,它被 设计用来煽动读者来挖掘词汇量,接着奖励他们来坚持学习它!(顺便说下Gamification 游戏化 是一个正在开始流行的设计技术的术语)
Overheard Word探询
我们将启动正定义的一个新的布局来使Overheard Word的测试视图保持一致,接下来我们定义一个来展示布局的Activity。正如前文所说,我正使用Eclipse的ADT作为我的开发环境,我假设你也是使用它。
我们将启动正定义的一个新的布局来使Overheard Word的测试视图保持一致,接下来我们定义一个来展示布局的Activity。正如前文所说,我正使用Eclipse的ADT作为我的开发环境,我假设你也是使用它。
Figure 1. Creating a new layout in Eclipse
我们将启动正定义的一个新的布局来使Overheard Word的测试视图保持一致,接下来我们定义一个来展示布局的Activity。正如前文所说,我正使用Eclipse的ADT作为我的开发环境,我假设你也是使用它。
下一步,拷贝下面的XML到你的新文件。
Listing 1. Quiz layout for Overheard Word
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".OverheardWord" >
- <LinearLayout
- android:id="@+id/widget33"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:layout_marginLeft="20dp"
- android:orientation="vertical" >
- <TextView
- android:id="@+id/quiz_definition"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="20dp"
- android:layout_marginLeft="13dp"
- android:layout_marginRight="10dp"
- android:layout_marginTop="48dp"
- android:text="Definition"
- android:textSize="18sp" />
- <RadioGroup
- android:id="@+id/quiz_answers"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginLeft="17dp" >
- <RadioButton
- android:id="@+id/quiz_answer_1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Answer 1" />
- <RadioButton
- android:id="@+id/quiz_answer_2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Answer 2" />
- <RadioButton
- android:id="@+id/quiz_answer_3"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Answer 3" />
- </RadioGroup>
- <TextView
- android:id="@+id/quiz_result"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="10dp"
- android:layout_marginLeft="40dp"
- android:layout_marginTop="20dp"
- android:lines="2"
- android:text="Result"
- android:textSize="18sp" />
- <TextView
- android:id="@+id/quiz_number"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_marginRight="10dp"
- android:layout_marginTop="30dp"
- android:gravity="right"
- android:text="1/10" />
- </LinearLayout>
- </RelativeLayout>
Overheard Word的存在的布局文件定义了学习指南的UI,此布局定义了一系列UI元素样例文本,目的是你们能够得到一个想法 当你的 app上线时东西是怎么样的。这测试的布局包括一个保存详细定义的TextView和一个用来各种适合那个定义单词的RadioGroup。当用户选择一 个单词,提交一个新问题或再试一次的机会时候,app将立即通知一个事件并相应,也有通过这个小测试跟踪用户进程的计数器。
我们下一步是创建一个新的Activity类。这个类应该继承Activity类且提供一个onCreate方法,如Listing2所示。(注意, setContentView指定那个Listing1创建的新布局文件)
Listing 2. New Activity class: OverheardQuiz
- import android.app.Activity;
- import android.os.Bundle;
- public class OverheardQuiz extends Activity {
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_overheard_quiz);
- }
- }
我们下一步是实例一个新Activity,这将最终显示Overheard word的测试布局。不仅仅创建一个可选菜单项,让我们尝试用手势滑动功能来启动一个测试。稍后我们也会把app菜单加到测试中。
Intents编程
正如我们之前讨论的,一个Android设备极其受电池时间周期、执行能力和内存的约束,大多数个人设备也有许多像电话和文本特性的apps,所以 同时他们有多个激活的程序运行。Android平台通过给作为开发者的你在任何时间使你的app应用处于某些约束中来管理程序。例如,你不能强迫一个 Activity类启动并且显示它的视图。而是,你要通过给activity发送给平台一个intent来启动。平台会在能启动Activity的时候启 动它。
想象一个Intent是一个异步执行任务的机制。从用户角度看,你建议平台应该做一些事情并且它能做的时候它立刻做了。
所以不能仅仅启动我们的Activity,我们需要把它包装在一个Intent里。之后Android将为我们启动Activity。由于我们正在 用手势启动一个新的测试Activity,我们将把intent插到一个GestureDetector实例。(这系列中更多关于 GestureDetector看 前两篇文章)在isUpSwip条件中不是只能返回false,我们会发送那样的一个新Intent:
Listing 3. Adding the new Activity to GestureDetector
- //.....
- if (detector.isDownSwipe()) {
- return false;
- } elseif (detector.isUpSwipe()) {
- startActivity(new Intent(getApplicationContext(), OverheardQuiz.class));
- } elseif (detector.isLeftSwipe()) {
- //.....
注意 一个Intent接受一个上下文和一个你希望启动的类,startActivity 是Activity实例的方法。
试一试,启动你的模拟器实例,当显示一个单词时候向上滑。你应该看到一个新Activity,如Figure2。
Figure 2. A quiz is born!
试一试,启动你的模拟器实例,当显示一个单词时候向上滑。你应该看到一个新Activity出现在Figure2那样
上滑,下滑
我们首先要做的就是添加一个手势检测以便能够让用户离开这测试并回到学习单词。由于上滑是让用户进入到测验,凭直觉下滑是离开测验比较好。我们调用finish来退出测验Activity回到学习模式。
Listing 4. Calling finish inside a swipe gesture
- private GestureDetector initGestureDetector() {
- return new GestureDetector(new SimpleOnGestureListener() {
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- try {
- final SwipeDetector detector = new SwipeDetector(e1, e2, velocityX, velocityY);
- if (detector.isDownSwipe()) {
- finish();
- } else if (detector.isUpSwipe()) {
- return false;
- } else if (detector.isLeftSwipe()) {
- return false;
- } else if (detector.isRightSwipe()) {
- return false;
- }
- } catch (Exception e) {
- // nothing
- }
- return false;
- }
- });
- }
上篇文章说过,我们在Overheard Word里的学习Activity中实现了一个操作栏,用来我们退出app。这里我们重用那段代码来创建新的测验退出函数。
Listing 5. An action bar to quit the quiz
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.overheard_quiz, menu);
- return true;
- }
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.quit_quiz:
- this.finish();
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
注意 我们并不想退出我们的app,只是离开测验;所以我们更新了操作栏文本‘退出测试’而不是‘退出’。
单词和定义
下一步,我们设定一个有一个正确答案和两个错误答案的定义。由于我们使用Thingamejig来处理逻辑,我们唯一需要做的就是输入相关字段,如Listing6所示:
Listing 6. Setting up a testable word with definitions
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_overheard_quiz);
- initializeGestures();
- List<Word> words = buildWordList();
- if (engine == null) {
- engine = WordTestEngine.getInstance(words);
- }
- final TestableWord firstWord = engine.getTestableWord();
- final TextView testDefinition = (TextView)findViewById(R.id.quiz_definition);
- testDefinition.setText(formatDefinition(firstWord.getValidDefinition()));
- final List<String> possibleAnswers = possibleAnswersFrom(firstWord);
- final int[] radios = { R.id.quiz_answer_1, R.id.quiz_answer_2, R.id.quiz_answer_3 };
- for (int x = 0; x < radios.length; x++) {
- final RadioButton rButton = (RadioButton)findViewById(radios[x]);
- rButton.setText(possibleAnswers.get(x));
- }
- }
在Listing 5 中,onCreate方法先构建我们提供的基础视图。下一步,初始化手势。就像原来学习Activity那样工作,一个 WordTestEngine(而不是WordStudyEngine)被实例化且链接到Overheard Word的单词存储(这个例子中是JSON 文件)。possibleAnswersForm方法返回一个有三个定义其中一个正确定义的列表,为了使测验不是一直显示相同顺序 WordTestEngine实例会打乱定义顺序。
最终,循环输入匹配所给定义的单词到我们的RadioButtons。如果你启动app实例,你将会看到一个漂亮的 有着一个单词定义和三个单词选项的表单测验接口。
Figure 3. The quiz UI with words and a definition
还有注意在屏幕底部有个静态Result按钮和一个显示所有的测验问题数量的计数器,截图中10个问题已经使用了1个。
目前为止,我们使用Thingamejig和一些原来的Activity逻辑快速创建了测验的样本逻辑。接下来我们将手动处理测验的单选框的按钮部 分。我们回调到单选按钮组的行为并且监听什么时候选项被选,而不需要用户额外多点击按钮(类似提交)来表明选中选项。这种方式用户不需要额外操作就立即得 到反馈。
处理事件调度
我提到我想要基于他们的单词选项来提供立即反馈。还有,我还想程序Overheard Word 对于反馈可以短暂的暂停。如果用户给了一个正确的 回应,我想他或她在进入下一题前有几秒中来体会这种感觉。如果回答错误,我应该让他们再试或者保持一两秒后再显示正确答案。任何方式,用户都有机会了解他 们在测验中的操作。
但是记住,我们不仅做一些我们喜欢的Android app;没有强迫app随时睡眠或启动线程。(在Android app中启动线程是可能的, 当然,但是各种相关因素是不受控制的。)就像我们使用Intents来告诉Android app 我们意图让它有怎样的行为,我们用Handlers来 指示我们渴望推迟或立即执行;Android怎样处理请求是Android的事了。
请暂停
在Android中,你使用Handlers来处理事件调度,并且也通过线程间传递信息。这种情况,我们将一直用Handlers调度一个事件,它将是一个密封的Runnable实例。
在响应用户选中一个单词的事件中,我们将创建一个Handler实例,在Runnable内部传递某写逻辑(如展示一个新的Activity)并且设置几秒延迟。
我们实现onCheckedChangeListener监听附加到RadioGroup上来回调敲击单选按钮,如下所示:Listing 7
Listing 7. setOnCheckedChangeListener
- final RadioGroup group = (RadioGroup)findViewById(R.id.quiz_answers);
- group.setOnCheckedChangeListener(new OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(final RadioGroup group, final int checkedId) {
- final RadioButton selected = (RadioButton)findViewById(checkedId);
- final String answer = (String) selected.getText();
- if (answer.equals(firstWord.getSpelling())) {
- final TextView result = (TextView)findViewById(R.id.quiz_result);
- result.setTextColor(Color.parseColor("#228b22"));
- result.setText("Correct!");
- final Handler handler = new Handler();
- handler.postDelayed(new Runnable() {
- public void run() {
- final Intent nextQuiz = new Intent(getApplicationContext(), OverheardQuiz.class);
- startActivity(nextQuiz);
- result.setText("");
- finish();
- }
- }, 2500);
- } else {
- final TextView result = (TextView)findViewById(R.id.quiz_result);
- result.setTextColor(Color.parseColor("#ff0000"));
- result.setText("Nope, that's not it! Try again.");
- final Handler handler = new Handler();
- handler.postDelayed(new Runnable() {
- public void run() {
- selected.setChecked(false);
- result.setText("");
- }
- }, 2000);
- }
- }
- });
正如JavaAWT或Swing的传统GUI编程,你能添加事件监听。例子中,我们就正在添加一 个 OnCheckedChangeListener 到RadioGroup。当用户选择一个RadioButton时,监听代码会执行。如果用户选择 正确,新实例OverheardQuizActivity会显示一个新单词。用户也可以在TextView中得到友好的‘正确’的绿色文本信息。
如果用户选择错误,信息文本是红色的,Activity的UI会重置让用户再次尝试。
也要注意两个Handler实例延迟启动线程,一个是因为要新建一个Intent启动,另一个是UI重置。
你也需要删除在Listing 1 中定义的测验布局的默认的文本。只需回到XML文件并且从ID为quiz_result的TextView中删除
android:text=”Result”。现在启动你的app并检测!
Android中的Bundles
接着我们要做的事情是给测验视图右下角的计数器添加一些逻辑(看Figure 3)。对于测验从用户的角度观察,我们想要展示他们的进度给他或她, 像还有多少问题。为了我们的计数器能工作,我们需要一个保持计数器的状态(例如它之前是2,且现在它应该是3),这就需要我们能从一个activity到 另一个之间传递数据,我们用Bundles来做这个任务。
如果你已经读了一段时间这个系列,那么你已经接触过Bundles了,因为一个Bundles实例是每个Activity的onCreate方法的长参数。
Bundles必须是一个键-值对的Maps。你能通过方法调用键的值检索一致的值类型。除了Activity实例外,Bundles也被包括在 Intents中,实际上,如果你放一个新Activity键-值对到Intent关联关系中,你就能通过那个Intent检索Bundle得到创建的那 个Activity。这些听起来可能很绕,但亲自做一次就理解了。
创建计数器
首先,我们更新创建新的OverheardQuizActivity的Handler,添加一个递增的计数器,如下Listing 8所示:
Listing 8. Adding an incremented counter
- final Handler handler = new Handler();
- handler.postDelayed(new Runnable() {
- public void run() {
- final Intent nextQuiz = new Intent(getApplicationContext(), OverheardQuiz.class);
- nextQuiz.putExtra(QUIZ_NUM, ++quizNumber);
- startActivity(nextQuiz);
- result.setText("");
- finish();
- }
- }, 2500);
putExtra 方法关联着quizNumber增加的键值QUIZ_NUM,两个都是OverheardQuiz类的成员变量。
下一步,我们更新OverheardQuiz类的onCreate方法从启动的那个Intent获取这个值。(如果onCreate没找到值,指定 它是1。原因是OverheardWordActivity***次被启动时候没有设置QUIZ_NUM的值。如果这个值比10大,序列会重置,并且我们会 发送给用户一个庆祝的信息(通过Toast)。
最终,我们将更新计数器的TextView为新的计数。全部代码如下所示Listing 9.
Listing 9. Updating a TextView with the current count
- final Bundle previous = getIntent().getExtras();
- quizNumber = (previous != null) ? previous.getInt(QUIZ_NUM) : 1;
- if (quizNumber > 10) {
- quizNumber = 1;
- CharSequence text = "Great Job! You made it through 10 questions. Here's another 10!";
- Toast.makeText(getApplicationContext(), text, Toast.LENGTH_LONG).show();
- }
- final TextView quizCounter = (TextView)findViewById(R.id.quiz_number);
- quizCounter.setText(quizNumber + " of 10");
现在 让我们启动最终版Overheard Word来看看它怎么样。
Figure 4. Overheard Word is munificent!
这样,我认为我们已经成功亲手做了个app:它能够提供有价值的服务(快速内部词汇量构建),直观的接口,甚至现在它有个能反复拉回用户的有趣的测验特性。UI可以更有趣(白底黑字比较讨厌),但现在到了我们签名并把它发到市场的时候了。
给你的Android app签名
如果你想让你的app能运行在所有人的设备上,你需要把它部署到一个公共的app 商店。然而有许多可选,其中Android两个***的app商店 是Google Play和Amazon Appstore(看 资料)。处于安全原因,两个商店规定它们的apps都必须被作者签名。
当你注册一个app,你创建一个你私有的证书。然后你用这个数字证书给你的代码签名。签名是一种发布由你签名而非某未知的、潜在恶意的一方的特别二 进制的方法,Android设备不会安装没有签名的app应用,所以如果你想要成为一个Android开发者,你需要知道如何给你app签名。
我之前展示的几篇文章在Android设备上《如何安装一个测试app》,实际上你用测试证书签名。测试证书对测试来说很好用,但公共的app商店不接受这个。这时你需要学习如何通过Eclipse生产一个真正的证书。
为了签名这个app,去Eclipse ADT的项目菜单中,选择‘Android Tools’ -> ‘Export Signed Application Package’。会弹出生成新证书向导。
Figure 5. The Eclipse ADT certificate wizard
这个想到会指导你通过对话框一步步说明你个人信息。如果你曾买过网站ssl证书,你会通过相似的过程。还有Android中没有证书授权的过程。因此App签名比SSL证书更容易,但是它欺诈的可能性也高;如,没人能阻止我说我是来自美国银行或可口可乐的人员。
Figure 6. Identifying information for the certificate
在你完成创建证书和私钥存储(存储你的私人密钥),你就能导出一个.apk扩展名的app包,如下所示Figure 7.
Figure 7. OverheardWord.apk
一旦你的app有了数字签名,至少从技术角度你可以自由的分发它到全世界。然而要发到公共的app商店只有签名还不够。你也需要提升它。
提升你的App
公共app商店只是提供一个全球市场的平台,但app市场中聪明的客户不会下载旧的东西。就像你需要知道在构建apps中包如何提供有价值的功能和 乐趣,你能诱惑用户让他尝试。当你上传(或发布)一个Google Play或Amazon Appstore的Android app时,你可以做一下 这些:
- 定义你的app的主要特性
- 长短适中且带有标记的描述你app
- 提供关键字来帮助提升被搜索几率
- 上传不同尺寸的你的app截图
- 提供你的app操作视频
当然,你也需要决定是否你的应用要免费还是付费;并且你需要设置一个适当的价格、市场利率。的确,你可能发现构建和签名你的应用是容易的部分,而在这个蓬勃发展、竞争激烈的市场中推销移动app是一个很难的工作。
总结
正如你所知,本地的Android开发只是产生移动appe的一个点。这个系列下一篇首先看看如何用HTML5来构建移动网络app。以网络为基础 的移动app提供一些比本地apps优势–像实际上你能发布它们到任何地方!但是基于网络的apps不能像本地app那样华丽而且未必能达到本地app相 同的表现。当我们深入到HTML5中,我们会发现移动Web app的乐趣和约束。