安卓第十夜 亞當的誕生

Vamei發表於2014-09-01

作者:Vamei 出處:http://www.cnblogs.com/vamei 歡迎轉載,也請保留這段宣告。謝謝!

 

上一講介紹了用WebView來抓取一個網頁內容。這一講我將介紹如何在安卓內部直接進行HTTP通訊。

《亞當的誕生》,西斯廷禮拜堂的吊頂畫,米開朗基羅之作。當時的教皇強迫沉迷於雕塑的米開朗基羅畫巨幅壁畫。米開朗基羅認為這是在浪費自己的才華,充滿憤怒的作畫。當然,他又成功了。

 

描述

這一講中,我將使用JSON,將資料庫備份到遠端的資料庫中。反過來我也將從遠端資料庫中抓取條目,並放入安卓的資料庫。相關的安卓知識點包括:

  • 執行緒
  • HTTP通訊
  • JSON

 

增加ActionBar

我首先在首頁上增加一選單,用於觸發下載和上傳功能。這是通過ActionBar實現的。ActionBar在頁面的頂端增加一個橫幅。這個橫幅上可以有應用的圖示、文字資訊和選項選單(OptionMenu)。

 

我在佈局檔案res/menu/main.xml中定義ActionBar的選單:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" >

    <item
        android:id="@+id/action_upload"
        android:orderInCategory="100"
        android:title="Upload"
        app:showAsAction="never"/>  
    <item
        android:id="@+id/action_download"
        android:orderInCategory="100"
        android:title="Download"
        app:showAsAction="never"/>
</menu>

 

現在,修改之前的MainActivity.java。MainActivity將繼承ActionBarActivity。這樣,MainActivity頁面的頂端將增加一條ActionBar。接下來,我要覆蓋ActionBarActivity的兩個方法。一個是onCreateOptionsMenu()方法。在這個方法中,我繫結上面的檢視檔案到ActionBar上。另一個方法onOptionsItemSelected(),主要用於說明選單各個選項被點選後的動作。程式碼如下:

package me.vamei.vamei;import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;public class MainActivity extends ActionBarActivity implements OnClickListener {
    private SharedPreferences sharedPref;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        sharedPref = this.getSharedPreferences("me.vamei.vamei", 
                Context.MODE_PRIVATE);
        
        Button btn1 = (Button) findViewById(R.id.author);
        btn1.setOnClickListener(this);
        Button btn2 = (Button) findViewById(R.id.category);
        btn2.setOnClickListener(this);        
    }
    
    @Override
    protected void onResume() {
        super.onResume();
        TextView nameView = (TextView) findViewById(R.id.welcome);
        
        // retrieve content from shared preference, with key "name"
        String   welcome  = "Welcome, " + sharedPref.getString("name", "unknown") + "!";
        nameView.setText(welcome);
    }

    // method for interface OnClickListener
    @Override
    public void onClick(View v) {
        Intent intent;
        // Routing to different view elements
        switch(v.getId()) {
            case R.id.author:
                intent = new Intent(this, 
                        SelfEditActivity.class);
                startActivity(intent);
                break;
            case R.id.category:
                intent = new Intent(this,
                        CategoryActivity.class);
                startActivity(intent);
                break;
        }
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {

        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        switch (id) {
          case R.id.action_download:
                    return true;
                  case R.id.action_upload:
                    return true;
                }
        return super.onOptionsItemSelected(item);
    }
}

在上面,我的onOptionsItemSelected()方法還沒有包含具體的功能。我將在下一部分為該方法增加功能。 執行應用後效果如下:

 

HTTP獲得資料

下一步,我將增加"Download"按鈕點選後的功能。按鈕點選後,應用將訪問網際網路,並獲得URL指向的.js檔案。獲得.js檔案後,我從該檔案中提取JSON物件,這個物件中包含一些新的Category名字。最後,我在資料庫中增加Category條目:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();
    switch (id) {
      case R.id.action_download:
            Thread thread = new Thread() {
                @Override
                public void run(){
                    try{
                        // Http Get
                        InputStream content;
                        HttpClient httpclient = new DefaultHttpClient();
                        HttpResponse response = httpclient.execute(
                                new HttpGet("http://files.cnblogs.com/vamei/android_contact.js"));
                        content = response.getEntity().getContent();
                        BufferedReader reader = new BufferedReader(new InputStreamReader(content));
                        final StringBuilder sb = new StringBuilder();
                        String line = null;
                        
                        while ((line = reader.readLine()) != null) {
                            sb.append(line);
                        }
                        content.close();
                        
                        // Parse JSON Object and Save to DB
                        JSONObject receivedObject = new JSONObject(sb.toString());
                        JSONArray categoryObjects  = receivedObject.getJSONArray("category");

                        ContactsManager cm        = new ContactsManager(getApplicationContext());
                        JSONObject categoryObject;
                        for (int i=0; i< categoryObjects.length(); i++) {
                            categoryObject = categoryObjects.getJSONObject(i);
                            String name = categoryObject.getString("name");
                            Category category = new Category(name);
                            cm.createCategory(category); 
                        }
                      } catch (Exception e) {
                        Log.i("Http Error", e.getMessage().toString());
                      }
                }
            };
            thread.start();
break; }
return super.onOptionsItemSelected(item); }

注意到,上面的網路訪問部分啟動了一個新執行緒Thread。為了確保介面的流暢,安卓規定網路訪問不能在負責圖畫介面的主執行緒中進行。所以,我們必須把網路訪問放在一個新的執行緒中。我們通過非同步的方式進行網路訪問,將在下一部分介紹。

程式中的JSONObject和JSONArray用於解析接收到的JSON字串。

 

使用AsyncTask

AsyncTask在背景程式中工作。AsyncTask分為工作準備、工作進行和工作完成三個部分。AsyncTask有三個方法,onPreExecute(), doInBackground(), onPostExecute()分別代表這三個部分的任務。其中,doInBackground在背景程式中進行,因此可以把網路訪問放入其中。此外,在doInBackground中,可以通過呼叫publishProgress(),來更新任務的進度。進度更新後,AsyncTask將呼叫onProgressUpdate()方法。

 

AsyncTask有三個型別<X, Y, Z>。它們分別是doInBackground(X), onProgressUpdate(Y)和onPostExecute(Z)的引數型別。此外,doInBackground()方法的返回值將成為onPostExecute()的引數,因此doInBackground()方法的返回值型別也是Z。

當工作完成,AsyncTask會通知主執行緒。AsyncTask與Thread的目的相同,但它非同步的呼叫方式更方便編寫,也更容易實現主執行緒和背景執行緒之間的資訊傳遞。我下面會實現Upload的對應功能,即把Category錶轉成JSON,再把該JSON字串傳送到特定的URL。

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();
    switch (id) {
            case R.id.action_download:
              Thread thread = new Thread() {
                  @Override
                public void run(){
                    try{
                        InputStream content;
                        
                        // Http Get
                        HttpClient httpclient = new DefaultHttpClient();
                        HttpResponse response = httpclient.execute(
                                new HttpGet("http://files.cnblogs.com/vamei/android_contact.js"));
                        content = response.getEntity().getContent();
                        BufferedReader reader = new BufferedReader(new InputStreamReader(content));
                        StringBuilder sb = new StringBuilder();
                        String line = null;
                        
                        while ((line = reader.readLine()) != null) {
                            sb.append(line);
                        }
                        content.close();
                        
                        // Parse JSON Object and Save to DB
                        JSONObject receivedObject = new JSONObject(sb.toString());
                        JSONArray categoryObjects = receivedObject.getJSONArray("category");

                        ContactsManager cm        = new ContactsManager(getApplicationContext());
                        JSONObject categoryObject;
                        for (int i=0; i< categoryObjects.length(); i++) {
                            categoryObject = categoryObjects.getJSONObject(i);
                            String name = categoryObject.getString("name");
                            Category category = new Category(name);
                            cm.createCategory(category); 
                        }
                      } catch (Exception e) {
                        Log.i("Http Error", e.getMessage().toString());
                      }
                }
            };
            thread.start();
            break;
          // upload action
          case R.id.action_upload:
        UploadTask newTask = new UploadTask();
        newTask.execute("http://files.cnblogs.com/");
        break;
    }      
    return super.onOptionsItemSelected(item);
}
    
private class UploadTask extends AsyncTask <String, String, String> {
    /*main worker*/
    @Override
    protected String doInBackground(String...params) {
        ContactsManager cm  = new             ContactsManager(getApplicationContext());
        List<Category> categories = cm.getAllCategories();
        JSONObject sendObject = new JSONObject();
        JSONArray categoryObjects = new JSONArray();
        try {
            for (int i=0; i<categories.size(); i++) {
            JSONObject categoryObject = new JSONObject();
            categoryObject.put("name",
            categories.get(i).getName());
            categoryObjects.put(categoryObject);
                }
            sendObject.put("category", categoryObjects);
                    // update progress once
                publishProgress("JSON DONE");

            // posting to URL   
                HttpClient httpClient = new DefaultHttpClient();
            HttpPost httpPost = new HttpPost(params[0]);
            StringEntity se   = new StringEntity(sendObject.toString());
                se.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, 
                        "application/json"));
            httpPost.setEntity(se);
            HttpResponse httpResponse = httpClient.execute(httpPost);
                    // update progress again
            publishProgress("NETWORK DONE");
                
            return httpResponse.getStatusLine().toString();
         } catch (Exception e) {
            e.printStackTrace();
            return "Crashed";
        }
    }

    /*after background work is done*/
    @Override
    protected void onPostExecute(String result) {
        Toast.makeText(MainActivity.this, 
                result, Toast.LENGTH_LONG).show();
    }
    
    /*when progress is updated*/
    @Override
    protected void onProgressUpdate(String...params) {
        Toast.makeText(MainActivity.this, 
                params[0], Toast.LENGTH_SHORT).show();
    }
}

這裡的URL並不能處理POST方法。如果有興趣,可以使用上一部分的Play框架,自制接受POST的伺服器,並處理這裡上傳的JSON。

 

總結

ActionBarActivity

Thread, AsyncTask

JSONObject, JSONArray

Http, get and post

相關文章