1. 程式人生 > >面向大眾的移動技術:簽名,封裝和釋出Android app

面向大眾的移動技術:簽名,封裝和釋出Android app

作者: Andrew Glover 原文地址 譯者:Ahaha  校對:趙峰

面向大從的移動打樁其它四篇文章地址(校對新增):

新增一個多選擇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

fig1

我們將啟動正定義的一個新的佈局來使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!

fig2

試一試,啟動你的模擬器例項,當顯示一個單詞時候向上滑。你應該看到一個新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

fig3

還有注意在螢幕底部有個靜態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!

fig4

這樣,我認為我們已經成功親手做了個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

fig5

這個想到會指導你通過對話方塊一步步說明你個人資訊。如果你曾買過網站ssl證書,你會通過相似的過程。還有Android中沒有證書授權的過程。因此App簽名比SSL證書更容易,但是它欺詐的可能性也高;如,沒人能阻止我說我是來自美國銀行或可口可樂的人員。

Figure 6. Identifying information for the certificate

fig6

在你完成建立證書和私鑰儲存(儲存你的私人金鑰),你就能匯出一個.apk副檔名的app包,如下所示Figure 7.

Figure 7. OverheardWord.apk

fig7

一旦你的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的樂趣和約束。