[Android] 全自動覆蓋安裝APK

好吧,連自己都覺得這個Script很方便
簡單來說這可以
1. 如果機器有曾經有安裝過,會自動執行解除安裝
2. 自動安裝新版APK
3. 自動打開剛裝好的APK (如果有需要)

使用方式很簡單

./ReInstallAPK.sh myApp.apk

如果需要自動打開,就加上 start 字樣

./ReInstallAPK.sh myApp.apk start

就這樣

需要設定 ANDROID_HOME 環境變數,目前只能用USB接一台Android devices
有空可以把它改成一次發佈多台的

https://gist.github.com/j796160836/df2dbf05d9f408739c06

[Android] 如何做到多重螢幕支援 (Multi Screen Support) (1/2)

這個主題不是很好寫,也不是很好解釋
我儘量把我所知道的解釋出來,如果有誤請糾正

在Android的世界裡,螢幕非常多

4" 5" 6" 7" 8" 9" ….各種寸數都有
其中也有各種解析度

 

但在使用者的眼中,只有 手機、平板 這二種類別

至少在iOS的世界是這樣子分的沒錯(只分iPhone / iPad二種版)

(正確應該說是三種版,因為iPhone5之後它變長了)


有些名詞要先懂

Density

到時候一定會圍繞這個單字,字面意思是 密度

這裡要表示螢幕上的密度

 

PPI (Pixel per inch)

字面翻譯爲 一英寸多少像素

算數公式如下:

tumblr_lxiqrxwFxp1qjgx8i  

實際算法可以從畢氏定理求得知

螢幕寬平方 與 螢幕高平方 相加後 開根號  得到一個值
然後將這個值除以螢幕對角線實際尺寸的值

跟我們常聽到的DPI (Dot per inch)想法很像

 

都是螢幕對角線的 虛擬尺寸(px)實際尺寸(inch) 的 比值
表示一種密度

 

小螢幕畫質很精密,這代表著他的密度很高

iPhone的Retina螢幕也是在表示這件事 


再來你需要知道Android對於像素的處理

Density-independent pixel (dp)

也簡稱為 dip 或 dp

Android他就發明了這個詞,這代表一個單位 (可以度量的單位,可以將它當做是layout使用的px)

這個單位的發明,只為了解決一件事情

不管螢幕密度多少,都要在實際畫面 (可以直接拿皮尺量螢幕的尺寸,例如:公分)  呈現一樣大的大小

在正確一點來說

不管螢幕密度為何,都以160dpi所看到螢幕的看到的差不多大(這句看不懂沒有關係)

 

 

放張大家有印象的圖

density-test-bad  

若是在螢幕尺寸不變(假設爲4"手機螢幕),由左至右 爲 低畫質 至 高畫質
螢幕就像640×480 演變成 1024×768 再演變成 1920×1080的感覺一樣

以前字很大,隨著解析度條高,字也就越小(畫面也就更細緻了)

因為密度變大了

density-test-good  

若使用了dp這個單位的話,就算是螢幕再細緻,畫面實際尺寸也不會有太大的差異

這就是Android想要達成的目的,就算是比較差的手機(畫質差),至比較高檔的手機(高畫質)

畫面也不要差太多

 

dp 轉 px 的公式如下

px = dp * (dpi / 160)

公式搬移一下變成

dp = px / dpi * 160

 

這時候就出現了ldpi、mdpi、hdpi、xhpi 這幾個詞,這個跟圖有關係,晚點再解釋


螢幕的分類

這個就是重點了

Android對於螢幕的分類有以下:

Small、Normal、Large、XLarge

這幾種

就跟你穿衣服一樣
不是有S號、M號、L號、XL號嗎?

這就是螢幕的大小

Android官方這裡有一張一般螢幕尺寸與密度的對照圖

screens-ranges  

這張對於螢幕尺寸(第一行)的部分,只有給區間而已

你不需要去拘泥與這張圖的分類  到底是幾吋到幾吋,也不需要急著要知道你手上的這支手機的型號 是 屬於哪個分類

這張圖的重點只有 一個

也就是

平板 都常都落在 large 以上


重點是你需要知道

螢幕分類dp 之間的關係

因為各家螢幕大小不一樣,這裡有列一個參考值

xlarge screens are at least 960dp x 720dp
large screens are at least 640dp x 480dp
normal screens are at least 470dp x 320dp
small screens are at least 426dp x 320dp

它幫我們算出,每個分類 至少 會有多少dp

當然參考值畢竟是參考值,多少會有例外
例外的部分就依照個別機種去調整版面

 

就我的經驗是

你必須要先考慮各種螢幕大小的版型是否會跑版,
再來考慮圖的畫質問題

 

怎麼測各種螢幕大小、機種?有幾種方法

  1.  儘可能的拿實機測

  2.  拿模擬器,指定特定的密度與螢幕大小來模擬

 

剛建立版型的時候,先固定一種螢幕大小去測試 (例如手上測試機)

然後先利用GenyMotion等模擬器,把所有螢幕大小分類都跑一遍
有問題的話先做修正

再來就是看各機種的問題做測試了


密度的分類

再來是畫質密度的問題,就是文章開頭很頭痛 Density

密度的分類剛剛看過了

ldpi、mdpi、hdpi、xhpi

screens-densities  

一樣有low、medium、high、extra high等分別

以mdpi為基準點

3:4:6:8比例做縮放

 

以48px x 48px的圖示來說

ldpi 為 36×36

mdpi 為 48×48 (因為它是基準點嘛) 

hdpi 為 72×72

xdpi 為 96×96

 

以Desire HD來說

device-2014-02-18-014336  

(不要再問我為何Desire HD有4.0.3了,因為我有刷機嘛)

以Desire HD來說,像素尺寸為 480×800

分類屬於 normal / hdpi 的螢幕

所以是換算成mdpi的話,將px全部除上1.5倍

480 / 1.5 = 320
800 / 1.5 = 533.33

再回頭查看原有程式的比例

在dp這一欄,剛好就是320×533

 

 

官方文件可以清楚看到

ldpi Resources for low-density (ldpi) screens (~120dpi).
mdpi Resources for medium-density (mdpi) screens (~160dpi). (This is the baseline density.)
hdpi Resources for high-density (hdpi) screens (~240dpi).
xhdpi Resources for extra high-density (xhdpi) screens (~320dpi).
tvdpi Resources for screens somewhere between mdpi and hdpi; approximately 213dpi. This is not considered a “primary” density group. It is mostly intended for televisions and most apps shouldn’t need it—providing mdpi and hdpi resources is sufficient for most apps and the system will scale them as appropriate. If you find it necessary to provide tvdpi resources, you should size them at a factor of 1.33*mdpi. For example, a 100px x 100px image for mdpi screens should be 133px x 133px for tvdpi.

ldpi 約爲 120dpi,mdpi 約為 160,hdpi 約為 240,xhdpi 約為 320

dpi的數字你不知道是多少也沒關係,可以直接記住 3:4:6:8 的比例(或是圖片上的 0.75x、1x、1.5x、2x 也行)


tvdpi

比較特別的是tvdpi,約為213dpi,這個專為電視所設計,因為電視現今螢幕不管多大尺寸通常都是 1920×1080

而且不管上述螢幕尺寸一致的問題,就是固定是倍率大小為1.33x

這跟使用體驗有關,畢竟電視不是像手機一樣般的操控

 

 


 

大部份的資料,參考自這份很難懂的原文資料

http://developer.android.com/guide/practices/screens_support.html

我很少把參考資料的字放大(代表它很重要),多看幾次才會知道它在寫什麼 

 


 

參考資料:

http://en.wikipedia.org/wiki/Pixel_density

http://shareandopen.tumblr.com/post/15559202965/android-support-multiple-screens

http://developer.android.com/training/multiscreen/screensizes.html

[Android] 安裝NDK與使用JNI呼叫系統底層native的C/C++程式 (Java call C)

因為工作需要,所以在這做個筆記
也順便學習一下

JNI全名叫Java Native Interface,意思就是說
這不是Android獨有的東西,純Java的應用程式也可以寫
(但本例還是以Android下的JNI為主)

以下是用Mac去搭建的,同樣的做法Linux也可以

 

環境準備

假設你已經有開發過Android的經驗

有Android  SDK+Eclipse最基礎的環境

這樣你需要加裝NDK(Native Development Kit) 和CDT(C/C++ Development Toolkit)

首先,先到Android官網下載NDK,解壓縮後存在一個路徑

下載網址:

http://developer.android.com/tools/sdk/ndk/index.html

像本例用Mac搭建,所以下載Mac版本

 

在Help > Install New Software 就像是你之前在安裝Android的環境一樣

Screen Shot 2013-01-15 at 5.54.38 PM  

但我們是要安裝CDT,這裡小小的不一樣

Work with:這欄打入

http://download.eclipse.org/tools/cdt/releases/indigo

然後勾選CDT Main Features安裝C/C++開發環境

Screen Shot 2013-01-15 at 6.03.51 PM  

接下來就是Next大法,沒啥好解釋的

Screen Shot 2013-01-15 at 6.04.54 PM  Screen Shot 2013-01-15 at 6.05.11 PM  

按下Finish,馬上就開始下載了

Screen Shot 2013-01-15 at 6.06.12 PM  

安裝完成的訊息問你要不要重開Eclipse,就照預設值Restart Now

Screen Shot 2013-01-15 at 6.11.25 PM  

但還沒完,

Eclipse重開了以後,再次Install New Software回到這個畫面

這次填上ADT的網址

https://dl-ssl.google.com/android/eclipse/

這次勾選NDK Plugins

Screen Shot 2013-01-15 at 6.14.40 PM  

剩下那些廢圖我就不重覆貼了

CDT和NDK安裝只多了一個警示框,按OK繼續安裝

Screen Shot 2013-01-15 at 6.17.38 PM  

Eclipse再次重開之後,在Eclipse > Preferences視窗中 

Screen Shot 2013-01-16 at 8.55.38 AM   

Android > NDK的頁籤中

選取你下載解壓後的NDK路徑
我是把它跟android sdk放在一起,有必要的話可以參考我的路徑

 Screen Shot 2013-01-16 at 8.55.24 AM  

環境設定完,就馬上開始寫程式摟!

新增專案

就像是老樣子,New > Android Project,開啟一個Android專案
因為這裡講到爛掉,所以我跳快一點

Screen Shot 2013-01-16 at 9.03.08 AM  

我開了一個專案

Application Name: HelloNDK
Package Name: com.J_Test.hellondk

Activity Name: MainActivity
Layout Name: activity_main

加入JNI支援

這裡就跟一般的Android專案不一樣

在你的專案按下右鍵 > Android Tools > Add Native Support…

Screen Shot 2013-01-16 at 9.07.37 AM  

出現一個視窗
提示你JNI編譯輸出的名稱

Screen Shot 2013-01-16 at 9.13.00 AM  

按下Finish之後,會發現你的專案目錄多了Includes和jni資料夾這二項

Screen Shot 2013-01-16 at 9.20.29 AM

 

設計JNI函數名稱 

HelloNDK.cpp和Android.mk是我們今天編輯的重點

首先,先將HelloNDK.cpp 重新命名為 HelloNDK.c

然後編輯Android.mk

LOCAL_PATH := $(call my-dir)

 

include$(CLEAR_VARS)

 

LOCAL_MODULE    := HelloNDK

LOCAL_SRC_FILES := HelloNDK.c

 

include$(BUILD_SHARED_LIBRARY)

這個HelloNDK和HelloNDK.cpp,就是先前填寫.so的名稱 
將檔名改成對應的名稱

 

我們在MainActivity加入以下:

static

{

     System.loadLibrary("HelloNDK");

} 

public native String helloString();

public native int plus(int a, int b);

public native int multiply(int a, int b);

 

把我們要呼叫C使用的方法界面,加個native修飾字
此例

helloString()  回傳一個測試字串

int plus(int a, int b)  做兩數相加

int multiply(int a, int b)  做兩數相乘

最後加上System.loadLibrary 載入我們即將要自定的JNI Module Name

 

產生Header檔案

我們用javah指令產生JNI對應的Header file
但它會讀取編譯後的*.class檔案

所以在這之前,請將專案整個Build過一遍

請先確定專案下的bin/classes+package名稱的對應之資料夾
是否有編譯後的*.class檔(這在Eclipse專案目錄下不會看見,用Finder去開)

本例是HelloNDK/bin/classes/com/J_Test/hellondk/MainActivity.class

 

接下來,打開Terminal

切換到專案的根目錄

使用這個指令

javah -d jni -classpath bin/classes <package名稱+class名稱

在本例就是

javah -d jni -classpath bin/classes com.J_Test.hellondk.MainActivity 

順利執行之後,回到Eclipse,專案視窗按F5重新整理

在jni資料夾會多出com_J_Test_hellondk_MainActivity.h這個檔,內容為以下

/* DO NOT EDIT THIS FILE – it is machine generated */

#include<jni.h>

/* Header for class com_J_Test_hellondk_MainActivity */

 

#ifndef _Included_com_J_Test_hellondk_MainActivity

#define _Included_com_J_Test_hellondk_MainActivity

#ifdef __cplusplus

extern“C” {

#endif

/*

 * Class:     com_J_Test_hellondk_MainActivity

 * Method:    helloString

 * Signature: ()Ljava/lang/String;

 */

JNIEXPORT jstring JNICALL Java_com_J_1Test_hellondk_MainActivity_helloString

  (JNIEnv *, jobject);

 

/*

 * Class:     com_J_Test_hellondk_MainActivity

 * Method:    multiply

 * Signature: (II)I

 */

JNIEXPORT jint JNICALL Java_com_J_1Test_hellondk_MainActivity_multiply

  (JNIEnv *, jobject, jint, jint);

 

#ifdef __cplusplus

}

#endif

#endif

好吧,我承認它是真的醜了點
但還是可以寫C程式的 

想想當初怎麼寫C程式,一個標頭檔…只宣告參數型別
但沒宣告參數名稱

 

實作JNI

編輯HelloNDK.c內容

#include<jni.h>

#include<string.h>

 

#include“com_J_Test_hellondk_MainActivity.h”

 

JNIEXPORT jstring JNICALL Java_com_J_1Test_hellondk_MainActivity_helloString(

JNIEnv *env, jobject thiz) {

   return (*env)->NewStringUTF(env, “Hello from JNI !”);

}

 

JNIEXPORT jint JNICALL Java_com_J_1Test_hellondk_MainActivity_multiply(

JNIEnv *env, jobject thiz, jint a, jint b) {

   jint total = 0;

   total = a * b;

   return total;

}

先不管JNIEXPORTJNICALL等關鍵字

jintjobjectjstring等型別,這些是Java裡給的型別

這裡有張對照表

Java型別 型別表示 字節大小(bit)
boolean jboolean 8, unsigned
byte jbyte 8
char jchar 16, unsigned
short jshort 16
int jint 32
long jlong 64
float jfloat 32
double jdouble 64
void void

至於String、物件、陣列…等,處理起來就較複雜些


疑難排解

在編譯時我有遇到

Method ‘NewStringUTF’ could not be resolved 

 

解決辦法,

在專案下按右鍵  Properties >C/C++ General > Paths and Symbols 的頁籤中

按下Add…選擇

<ndk路徑>/platforms/android-14/arch-arm/usr/include

/Users/johnny/android-sdks/android-ndk/platforms/android-14/arch-arm/usr/include

就可以解決

Screen Shot 2013-01-17 at 2.58.43 AM  

若在使用javah遇到類似的訊息

error: cannot access com.J_Test.hellondk.MainActivity
class file for com.J_Test.hellondk.MainActivity not found
javadoc: error – Class com.J_Test.hellondk.MainActivity not found.
Error: No classes were specified on the command line. Try -help.

請先確定*.class的位置

不含package的資料夾路徑

要從專案裡的bin資料夾,或是bin/classes資料夾尋找

若沒有,請重新編譯一次(輸出一次到模擬器是個不錯的選擇)


最後,感謝這些參考資料,都是來自世界各個角落的精華
讓我好好學了一課!

device-2013-01-17-023803  

對了,有了function name,要寫個測試程式應該不難吧
這種艱巨的任務,就交給你了!(笑)

 

 

參考資料: 

http://androidcookbook.com/Recipe.seam?recipeId=77

http://changyy.pixnet.net/blog/post/29469121
http://changyy.pixnet.net/blog/post/29437517

http://developer.android.com/training/articles/perf-jni.html

http://87showmin.blogspot.tw/2009/06/java-java-native-interfacejni.html

https://github.com/androidcook/Android-Cookbook-Examples/tree/master/NdkDemo

http://java.chinaitlab.com/JDK/36678.html

http://blog.csdn.net/ljlsunny/article/details/5753006

http://electrofriends.com/qna/jni-faq/convert-jstring-cstyle-string-vice-versa/

http://blog.csdn.net/gavinr/article/details/7343324

[Android] 要學會的關鍵技術—XML的讀取(3)

device-2012-03-23-142254

系列文章:

[Android] 要學會的關鍵技術—XML的讀取(1) 

[Android] 要學會的關鍵技術—XML的讀取(2) 

[Android] 要學會的關鍵技術—XML的讀取(3)

 


講完技術性的東西之後,最後就是把它設定起來

 

版面檔 main.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent"

    android:orientation="vertical" >

 

    <TextView

        android:id="@+id/textView1"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="讀取RSS新聞標題:" />

 

    <LinearLayout

        android:id="@+id/linearLayout1"

        android:layout_width="match_parent"

        android:layout_height="wrap_content" >

 

        <EditText

            android:id="@+id/trg_url"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_weight="1"

            android:hint="請輸入RSS網址"

            android:text="http://tw.news.yahoo.com/rss/travel"

            android:singleLine="true">

 

            <requestFocus />

        </EditText>

 

        <Button

            android:id="@+id/get_data"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:text="取得" />

    </LinearLayout>

 

    <ScrollView

        android:layout_width="match_parent"

        android:layout_height="match_parent" >

 

        <TextView

            android:id="@+id/result_txt"

            android:layout_width="fill_parent"

            android:layout_height="wrap_content" />

    </ScrollView>

 

</LinearLayout>

 

主程式 Main.java

 

package com.J_Test.XMLParserTest;

import java.io.IOException;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import com.J_Test.XMLParserTest.Module.SimpleXMLParser;
import com.J_Test.XMLParserTest.ParsingHandler.RssNewsXMLParsingHandler;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
 

public class Main extends Activity implements OnClickListener

{

     private static final String TAG = “XMLParserTest”;

     /** 「要更新版面」的訊息代碼 */

     protected static final intREFRESH_DATA = 0x00000001;

     /** Rss資料的網址 */

     String trgUrl;

     /** 新聞資料的物件陣列 */

     RssNews[] Arr_RssNews;

     private Button getData_btn;
     private TextView result_edtxt;
     private EditText trgUrl_txt;

     /** 建立UI Thread使用的Handler,來接收其他Thread來的訊息 */

     Handler mHandler = new Handler()

     {

           @Override

           public void handleMessage(Message msg)

           {

                switch (msg.what)

                {

                // 更新資料,將Rss新聞的標題用迴圈依序印出來

                case REFRESH_DATA:

                     result_edtxt.setText(“”);

                     for (int i = 0; i < Arr_RssNews.length; i++)

                     {

                           result_edtxt.append(Arr_RssNews[i].getTitle() + “n”);

                     }

                     break;

                }

           }

     };

 

     /** Called when the activity is first created. */

     @Override

     public void onCreate(Bundle savedInstanceState)

     {

           super.onCreate(savedInstanceState);

           setContentView(R.layout.main);

           // 版面初始化

 

           getData_btn = (Button) findViewById(R.id.get_data);
           result_edtxt = (TextView) findViewById(R.id.result_txt);
           trgUrl_txt = (EditText) findViewById(R.id.trg_url);
           getData_btn.setOnClickListener(this);

     }

     @Override

     public void onClick(View v)

     {

           // 若是按下getData_btn的按鈕

           if (v == getData_btn)

           {

                // 擷取trgUrl_txt文字框裡的內容

                trgUrl = trgUrl_txt.getEditableText().toString();

                // 開一個執行緒(Thread)

                new Thread()

                {

                     @Override

                     public void run()

                     {

                           Arr_RssNews = getRssNews();

                           if (Arr_RssNews != null)

                                 // Handler上發出「要更新版面」的訊息

                                 mHandler.sendEmptyMessage(REFRESH_DATA);

                     }

                 }.start();

            }

      }

 

     /**
      * 從網路擷取RSS的資料(需要搭配執行緒)
      * @return Rss新聞的物件陣列
      */

     public RssNews[] getRssNews()

     {

           if (trgUrl == null)

                return null;

           try

           {

                // 建立一個Parser物件,並指定擷取規則 (ParsingHandler)

                SimpleXMLParser dataXMLParser = new SimpleXMLParser(

                new RssNewsXMLParsingHandler());

                // 呼叫getData方法取得物件陣列

                Object[] data = (Object[]) dataXMLParser.getData(trgUrl);

                if (data != null)

                {

                     // 如果資料形態正確,就回傳

                     if (data[0] instanceof RssNews[])

                     {

                           return (RssNews[]) data[0];

                     }

                }

           } catch (SAXException e)

           {

                e.printStackTrace();

           } catch (IOException e)

           {

                e.printStackTrace();

           } catch (ParserConfigurationException e)

           {

                e.printStackTrace();

           }

           // 若有錯誤則回傳null

           return null;

     }

}

 


呼….結束了。

什麼?你又忘了!

再給我忘記這句

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

我就搥你

 

參數設定檔 AndroidManifest.xml

<?xml version=“1.0” encoding=“utf-8”?>

<manifest xmlns:android=“http://schemas.android.com/apk/res/android”

    package=“com.J_Test.XMLParserTest”

    android:versionCode=“1”

    android:versionName=“1.0” >

 

    <uses-sdk

        android:minSdkVersion=“8”

        android:targetSdkVersion=“17” />

    <uses-permission android:name=“android.permission.INTERNET” />

    <application

        android:allowBackup=“true”

        android:icon=“@drawable/ic_launcher”

        android:label=“@string/app_name”

        android:theme=“@style/AppTheme” >

        <activity

            android:name=“com.J_Test.XMLParserTest.Main”

            android:label=“@string/app_name” >

            <intent-filter>

                <action android:name=“android.intent.action.MAIN” />

                <category android:name=“android.intent.category.LAUNCHER” />

            </intent-filter>

        </activity>

    </application>

</manifest>

 

 

[Android] 要學會的關鍵技術—XML的讀取(2)

device-2012-03-23-142254  

系列文章:

[Android] 要學會的關鍵技術—XML的讀取(1) 

[Android] 要學會的關鍵技術—XML的讀取(2) 

[Android] 要學會的關鍵技術—XML的讀取(3)

 


上篇講清楚XML的讀取方式之後,接下來上程式碼

 

之前說了,我比較喜歡設計一個類別(Class)把解析完的內容塞進去

 

新聞類別 RssNews.java

package com.J_Test.XMLParserTest;

import java.io.Serializable;

/*
 * <rss version=”2.0″>
 * <channel version=”2.0″>
 * <title> 中央氣象局警報、特報 </title>
 * <link>http://www.cwb.gov.tw/</link>
 * <description> 中央氣象局 RSS服務 </description>
 * <language>zhtw</language>
 * <lastBuildDate>Sat, 03 Sep 2011 19:00:02 GMT</lastBuildDate>
 * <ttl>1</ttl>
 * <item>
 * <pubDate>Sat, 03 Sep 2011 13:34:26 GMT</pubDate>
 * <title>09/03 21:30 解除大雨特報發布</title>
 * <link>http://www.cwb.gov.tw/V7/prevent/warning/w26.htm?&id=20110904030002</link>
 * <description>(…)</description>
 * </item>
 * </channel>
 * </rss>
 */

@SuppressWarnings(“serial”)
public class RssNews implements Serializable

{

     private String title = null;

     private String link = null;

     private String pubDate = null;

 

     public void setTitle(String title)

     {

           this.title = title;

     }

 

     public String getTitle()

     {

           return title;

     }

 

     public void setLink(String link)

     {

           this.link = link;

     }

 

     public String getLink()

     {

           return link;

     }

 

     public void setPubDate(String pubDate)

     {

           this.pubDate = pubDate;

     }

 

     public String getPubDate()

     {

           return pubDate;

     }

 

     @Override

     public String toString()

     {

           return “RssNews [title=” + title + “, link=” + link + “, pubDate=” + pubDate + “]”;

     }

}

 

嗯,這複習了一下資料類別的寫法
我只記錄RSS文章的標題、連結、摘要…等我要的訊息
大多程式碼給eclipse自己產生,沒甚麼特別

繼承Serializable 只是為了到時候方便寫入到檔案而已,可以不寫

 

 

XML剖析器 SimpleXMLParser.java

package com.J_Test.XMLParserTest.Module;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

 

public class SimpleXMLParser

{

     protected SimpleXMLParsingHandler xmlParsingHandler;

     /**
      * 建構子,必須將xmlParsingHandlernew出來
      *
      */

     public SimpleXMLParser(SimpleXMLParsingHandler parser)

     {

           xmlParsingHandler = parser;

     }

 

     /**
      * XML的剖析出物件
      *
      * @param inputStream
      *            來源的FileInputStream
      * @return回傳包含物件陣列的資料 (回傳可以不只一個物件陣列)
      * @throws SAXException
      * @throws ParserConfigurationException
      * @throws IOException
      */

     public Object[] getData(InputStream inputStream) throws SAXException,

                IOException, ParserConfigurationException

     {

           Object[] data;

           /* 產生SAXParser物件 */

           SAXParserFactory spf = SAXParserFactory.newInstance();

           SAXParser sp = spf.newSAXParser();

           /* 產生XMLReader物件 */

           XMLReader xr = sp.getXMLReader();

           /* 設定自定義的MyHandlerXMLReader */

           if (xmlParsingHandler == null)

           {

                throw new NullPointerException(“xmlParsingHandler is null”);

           } else

           {

                xr.setContentHandler(xmlParsingHandler);

                /* 解析XML */

                xr.parse(new InputSource(inputStream));

                /* 取得RSS標題與內容列表 */

                data = (Object[]) xmlParsingHandler.getParsedData();

           }

           inputStream.close();

           return data;

     }

 

     /**
      * XML的剖析出物件 (多載方法)
      *
      * @param urlPath
      *            url網址
      * @throws IOException
      * @throws SAXException
      * @throws ParserConfigurationException
      */

     public Object[] getData(String urlPath) throws SAXException, IOException,

                ParserConfigurationException

     {

           URL url = new URL(urlPath);

           HttpURLConnection uc = (HttpURLConnection) url.openConnection();

           uc.setConnectTimeout(15000);

           uc.setReadTimeout(15000); // 設定timeout時間

           uc.connect(); // 開始連線

           int status = uc.getResponseCode();

           if (status == HttpURLConnection.HTTP_OK)

           {

                Object[] data = getData(url.openStream());

                return data;

           }

           return null;

     }

}

 

好了,這裡就是XML讀取的核心程式

只有二個方法,一個輸入InputStream,另一個是網址

這樣預留,到時候可以 讀取XML檔案

這裡提的是網路抓取嘛,所以當然是使用網址的方法摟

 

這裡有出現一個

uc.connect();

所以說,這是要放在另一個執行緒去執行的  (Android 4.0以後的環境,直接跑絕不會給過)

至於SimpleXMLParsingHandler是甚麼?等等就會看見了

 

 

XML剖析處理細節 SimpleXMLParsingHandler.java

package com.J_Test.XMLParserTest.Module;

import java.util.Stack;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import android.util.Log;

 

/**
 * 處理XML的方法細節的Class 有四個方法必須覆寫而且要呼叫super方法
 */

public abstract class SimpleXMLParsingHandler extends DefaultHandler

{

     protected static final String TAG = “ParsingHandler”;

     /** XML解析Node的堆疊 */

     private Stack<String> in_node;

     public Stack<String> getInNode()

     {

           return in_node;

     }

 

     /** 除錯模式(手動設定成true會顯示Log) */

     private boolean debugMode = false;

     /**
      * 將轉換的資料回傳
      */
     public abstract Object getParsedData();

     /**
      * XML文件開始解析時呼叫此method,這裡要加上new出自訂的物件的程式
      */

     @Override
     public void startDocument() throws SAXException

     {

           in_node = new Stack<String>();

     }

 

     /**
      * XML文件結束解析時呼叫此method
      */

     @Override
     public void endDocument() throws SAXException

     {

     }

 

     /**
      * 解析到Element的開頭時呼叫此method
      */

     @Override
     public void startElement(String namespaceURI, String localName,

                String qName, Attributes atts) throws SAXException

     {

           if (isDebugMode())

           {

                Log.v(TAG, “startElement:    qName=” + qName);

                for (int i = 0; i < atts.getLength(); i++)

                {

                     Log.v(TAG, “tt atts[“ + i + “]getQName=” + atts.getQName(i));

                     Log.v(TAG, “tt atts[“ + i + “]getValue=” + atts.getValue(i));

                }

           }

           in_node.push(qName);

     }

 

     /**

      * 解析到Element的結尾時呼叫此method

      */

     @Override

     public void endElement(String namespaceURI, String localName, String qName)

                throws SAXException

     {

           if (isDebugMode())

                Log.v(TAG, “endElement:    qName=” + qName);

           in_node.pop();

     }

 

     /** 取得Element的開頭結尾中間夾的字串 */

     @Override

     public void characters(char ch[], int start, int length)

     {

           String fetchStr = new String(ch).substring(start, start + length);

           // printNodePos();

           if (isDebugMode())

                Log.v(TAG, “t characters:    ch=” + fetchStr);

           characters(fetchStr);

     }

 

     /**
      * 取得Element的開頭結尾中間夾的字串這裡需要做「新增Node的上所有資料」
      *
      * @param fetchStr
      *            取得到的字串
      */

     public void characters(String fetchStr)

     {

     }

 

     // ————————————————————————————–

     /**

      * 印出現在Node的位置,除錯用。例如:rss -> channel -> title 

      */

     public String printNodePos()

     {

           StringBuffer sb = new StringBuffer();

           // 印出現在Node的位置

           for (int i = 0; i < in_node.size(); i++)

           {

                if (i > 0)

                     sb.append(” -> “);

                sb.append(in_node.get(i));

           }

           sb.append(“n”);

           return sb.toString();

     }

 

     /**

      * 在參數堆中找到我要的參數

      */

     public static String findAttr(Attributes atts, String findStr)

     {

           int i;

           for (i = 0; i < atts.getLength(); i++)

           {

                if (atts.getQName(i).compareToIgnoreCase(findStr) == 0)

                {

                     break;

                }

           }

           return atts.getValue(i);

     }

 

     public void setDebugMode(boolean debugMode)

     {

           this.debugMode = debugMode;

     }

 

     public boolean isDebugMode()

     {

           return debugMode;

     }

}

 

這個class呢…因為有abstract,所以必須要繼承實作 (當做函式庫了嘛)

其中加了很多不錯的函式,例如:

public static String findAttr(Attributes atts, String findStr)

找到我要的屬性

public String printNodePos()

印出路徑等等

 

路徑的概念就是上篇所說的,用個堆疊(Stack) 就可以搞定!

眼尖的你會發現,怎麼會有個

public void characters(String fetchStr)

然後甚麼都沒寫的放在那裡

其實在標準的

public void characters(char ch[], int start, int length)

就可看出端倪,我只是把擷取到的文字直接去呼叫只有String的參數的方法
到時候便於判斷

到目前為止,跟我們的RSS沒甚麼關連….

 

 

RSS新聞資料處理 RssNewsXMLParsingHandler.java

package com.J_Test.XMLParserTest.ParsingHandler;

import java.util.Stack;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import android.util.Log;
import com.J_Test.XMLParserTest.RssNews;
import com.J_Test.XMLParserTest.Module.SimpleXMLParsingHandler;

 

/**

 * 記錄著Rss新聞資料的解析XML處理方式的class (繼承了SimpleXMLParsingHandler

 */

public class RssNewsXMLParsingHandler extends SimpleXMLParsingHandler

{

     private static final String TAG = “RssNews ParsingHandler”;

     /** 用來儲存Rss新聞的物件 */

     private RssNews newsItem;

     /** 用來儲存Rss新聞的物件Stack(堆疊) */

     private Stack<RssNews> mNewsItem_list;

 

     /** 建構子 */

     public RssNewsXMLParsingHandler()

     {

     }

 

     /**

      * @return回傳RssNews[]。程式會將讀到的物件{ RssNews[] }包成Object[]然後回傳

      */

     @Override

     public Object[] getParsedData()

     {

           RssNews[] Arr_RssNews = (RssNews[]) mNewsItem_list

                     .toArray(new RssNews[mNewsItem_list.size()]);

           // 解析結果回報

           Log.v(TAG, String.format(“RssNews Count=%d”, Arr_RssNews.length));

           return new Object[] { Arr_RssNews };

     }

 

     /**

      * XML文件開始解析時呼叫此method

      */

     @Override

     public void startDocument() throws SAXException

     {

           super.startDocument();

           // 在文件開始的時候,宣告出該RssNews形態的Stack(堆疊)

           mNewsItem_list = new Stack<RssNews>();

     }

 

     /**

      * XML文件結束時呼叫此method

      */

     @Override

     public void endDocument() throws SAXException

     {

           super.endDocument();

     }

 

     /**

      * 解析到Element的開頭時呼叫此method

      */

     @Override

     publicvoid startElement(String namespaceURI, String localName,

                String qName, Attributes atts) throws SAXException

     {

           super.startElement(namespaceURI, localName, qName, atts);

           // 若搞不清楚現在在哪裡的話可以用printNodePos();

           // printNodePos();

           if (getInNode().size() >= 3
                     && getInNode().get(getInNode().size() – 3).equals(“rss”)
                     && getInNode().get(getInNode().size() – 2).equals(“channel”)
                     && getInNode().get(getInNode().size() – 1).equals(“item”))

           {

                // rss -> channel -> item這個位置中

                // 新增一個RssNews

                newsItem = new RssNews();

           }

     }

 

     /**

      * 解析到Element的結尾時呼叫此method

      */

     @Override

     public void endElement(String namespaceURI, String localName, String qName)

                throws SAXException

     {

 

           if (getInNode().size() >= 3
                     && getInNode().get(getInNode().size() – 3).equals(“rss”)
                     && getInNode().get(getInNode().size() – 2).equals(“channel”)
                     && getInNode().get(getInNode().size() – 1).equals(“item”))

           {

                // rss -> channel -> item這個位置中

                // 新增一筆新聞資料到 Stack(堆疊)

                mNewsItem_list.add(newsItem);

                newsItem = null;

           }

           super.endElement(namespaceURI, localName, qName);

     }

 

     /**

      * 取得Element的開頭結尾中間夾的字串

      */

     @Override

     public void characters(String fetchStr)

     {

           if (getInNode().size() >= 4
                     && getInNode().get(getInNode().size() – 4).equals(“rss”)
                     && getInNode().get(getInNode().size() – 3).equals(“channel”)
                     && getInNode().get(getInNode().size() – 2).equals(“item”))

           {

                // rss -> channel -> item -> XXX這個位置中

 

                // 新增Node的上所有資料

 

                // rss -> channel -> item -> title這個位置中

                if (getInNode().lastElement().equals(“title”))

                     // 設定標題

                     newsItem.setTitle(fetchStr);

                // rss -> channel -> item -> link這個位置中

                else if (getInNode().lastElement().equals(“link”))

                     // 設定連結

                     newsItem.setLink(fetchStr);

                // rss -> channel -> item -> pubDate這個位置中

                else if (getInNode().lastElement().equals(“pubDate”))

                     // 設定發佈日期

                     newsItem.setPubDate(fetchStr);

           }

     }

}

 

這就是我們的重點了,因為有我們自訂的SimpleXMLParsingHandler幫忙

程式碼簡潔多了

我們只要判斷 getInNode() 裡面的內容就行了

配合characters(String fetchStr)endElement()

就可以把資料塞進物件裡然後依序排好

 

順帶一提,最後

public Object[] getParsedData()

會把整理好的物件陣列,用包陣列的方式回傳

到時候只要強制轉型(cast)到指定物件格式就可以了

[Android] 要學會的關鍵技術—XML的讀取(1)

device-2012-03-23-142254  

系列文章:

[Android] 要學會的關鍵技術—XML的讀取(1) 

[Android] 要學會的關鍵技術—XML的讀取(2) 

[Android] 要學會的關鍵技術—XML的讀取(3) 

 

在Android開發之中,很重要的就是跟網路連線

今天談論XML的資料格式的傳遞

在Android的XML解析上有二種方式:

  1. DOM (Document Object Model),他會把整份XML檔案載入,然後成為一個樹狀物件

  2. SAX (Simple API for XML),照順序從頭向下讀,其中讀到一個節點、屬性…等,就採用事件的方式處理

很多效能測試上,SAX比DOM更有效率
這次的範例也是用SAX做的

 

這要配合XML格式來看

XML無所不在,最常用的RSS、你的Android的版面檔…

舉個RSS的例子好了,RSS也是用XML去做的

例如 Yahoo!奇摩 旅遊新聞

http://tw.news.yahoo.com/rss/travel

你用Chrome打開可能會長這樣

2013-01-06 22 32 32   

那是Chrome已經幫你整理過了,所有的元素(element)旁都有個箭頭可以縮放

這樣還是看不懂,沒關係,我來幫你整理一下
一個旅遊新聞RSS大概長這樣
(以下內容有部份元素省略)

 

<?xml version=“1.0” encoding=“utf-8”?>

<rss version=“2.0” >

    <channel>

        <title>旅遊新聞頭條新聞 – Yahoo!奇摩新聞</title>

        <link>http://tw.news.yahoo.com/travel/</link>

        <description>(…)</description>

        <language>zh-Hant-TW</language>

        <pubDate>Sun, 06 Jan 2013 05:30:00 +0800</pubDate>

        <ttl>5</ttl>

        <item>

            <title>冬日泡湯香港客愛北投</title>

                <link>http://tw.news.yahoo.com/(…)-024412185.html</link>

            <pubDate>Sun, 06 Jan 2013 10:44:12 +0800</pubDate>

                <description>(…)</description>

        </item>

    </channel>

</rss>

 

就如從剛剛所提到,RSS是照著XML的格式規範走的
開頭第一行就有XML的宣告,之後的內容就跟HTML感覺很像啦

這文件包含了RSS的channel(頻道)、頻道的名字、連結、頻道介紹、語言別、更新日期…等

item裡面的東西比較重要,代表一個RSS項目(Maybe是一篇文章、新聞),它的標題、連結、時間、簡介…等

 


再看一個例子好了,例如小弟部落格的RSS

http://feed.pixnet.net/blog/posts/rss/j796160836

內容節錄在這:

<?xml version=“1.0” encoding=“UTF-8”?>

<rss xmlns:content=“http://purl.org/rss/1.0/modules/content/” xmlns:atom=“http://www.w3.org/2005/Atom” version=“2.0”>

  <channel>

    <title><![CDATA[清新下午茶 :: 痞客邦 PIXNET ::]]></title>

    <link>http://j796160836.pixnet.net/blog</link>

    <description><![CDATA[夏天的午後,來杯下午茶,分享你我的心。]]></description>

    <pubDate>Thu, 15 Nov 2012 08:11:58 +0000</pubDate>

    <copyright>Copyright 2003-2013 Johnny ,Pixnet Digital Media Coporation. All rights reserved.</copyright>

    <generator>PIXNET Media Digital Coporation</generator>

    <language>zh</language>

    <docs>http://blogs.law.harvard.edu/tech/rss</docs>

    <atom:link rel=“hub” href=“http://pubsubhubbub.appspot.com:80”/>   

    <item>

      <title><![CDATA[[Android] 使用HTTPPOST方式和網頁表單溝通 (加上資料庫、執行緒)]]></title>

      <link>http://j796160836.pixnet.net/blog/post/30577968</link>

      <guid>http://j796160836.pixnet.net/blog/post/30577968</guid>

      <description><![CDATA[  (…) ]]></description>

      <content:encoded><![CDATA[  (…]]></content:encoded>

      <pubDate>Thu, 05 Apr 2012 16:19:35 +0000</pubDate>

      <category>Android</category>

      <comments>http://j796160836.pixnet.net/blog/post/30577968#comments</comments>

    </item>

  </channel>

</rss>

 

雖然內容比較多,但很多只要看重點就可以了

從剛剛的例子知道

要找這個頻道的名稱要找

rss -> channel -> title

要找一個項目(文章)的標題,就

rss -> channel -> item -> title

(其實這只是方便理解,表示路徑有正規的寫法,叫XPath …有興趣可以google一下)

這裡多的xmlns:contentxmlns:atomatom:link

都只是宣告不重要

但這裡多了個<content:encoded></content:encoded>標籤
它跟<description></description>的差別

只在於content:encoded是用HTML標籤格式化過的

description是純文字

 

眼尖的你應該有發現<![CDATA[   ]]>符號

它是告訴剖析器(parser),用這符號括起來的內容,沒有做任何的跳脫處理

例如:< 符號要寫成 &lt; 等等

請視為整段文字做處理

 

因為它是RSS,所以有格式規定

如果是你自己寫XML文件的話,格式都可以自己訂

 


 

剛剛提到了SAX採用事件的方式處理,所以我簡單列出以下的一些事件callback函數

 

文件類:

     * XML文件開始解析時呼叫此method

    public void startDocument() throws SAXException

     * XML文件結束時呼叫此method

    public void endDocument() throws SAXException

節點類:

     * 解析到Element的開頭時呼叫此method

    public void startElement(String namespaceURI, String localName,

            String qName, Attributes atts) throws SAXException 

     * 解析到Element的結尾時呼叫此method

    public void endElement(String namespaceURI, String localName, String qName)

            throws SAXException

節點內容:

     * 取得Element的開頭結尾中間夾的字串

    public void characters(String fetchStr)

 

到時候配上程式碼就大致就會知道了

它到時候要接一個DefaultHandler實做以上方法

 

我的話通常會把RSS資料擺到自己設計的物件(Object)裡面

到時候直接存檔,用ArrayList配合ListView做資料展示都很方便

 

像在startDocument()的時候通常會做一些變數的宣告(ArrayList)

SAX的問題就是:我只知道讀到節點了,但我不知道讀到哪一個節點(多深)

很多書上都是寫用元素的名稱做判斷

但我提出一個更方便更精準的做法

 

我把讀取的歷程用Stack(堆疊)記錄下來

意指在 startElement() 時把qName給push進去

in_node.push(qName); 

到了 endElement() 時把它pop掉

in_node.pop();

這個很簡單的動作我就可以知道我的歷程了

 

 

這篇先講概念,後面的程式碼才會看得比較有重點

[Android] 要學會的關鍵技術—XML的讀取(2) 

[Android] 要學會的關鍵技術—XML的讀取(3)  

 


 

參考資料

 

RSS的介紹

http://zh.wikipedia.org/zh-tw/RSS

http://tw.info.yahoo.com/rss/rookie.html

SAX的剖析方式

http://zh.wikipedia.org/zh-tw/SAX

CDDATA的說明

http://ianjung1974.blogspot.tw/2008/04/cdata-xml.html

Android 解析 XML

http://www.dotblogs.com.tw/nethawk/archive/2011/05/02/24047.aspx

 

 

[Android] 使用HTTP的POST方式和網頁表單溝通 (加上資料庫、執行緒)

Screen Shot 2012-04-06 at 下午4.47.14     

 

這是由之前寫的這一篇

http://j796160836.pixnet.net/blog/post/28994669

所延伸出來的

 

眼尖的網友已經發現,之前放上的程式碼範例已經不能Run了
把專案使用的Android版本放在2.3.3以下就可以正確執行
而把它升到4.0就會出錯

有人發現為什麼了嗎?


打開LogCat就可以清楚的看到

Screen Shot 2012-04-06 at 上午12.13.33  

Android 4.0 (ICS)在網路的部份多了一個新的Exception

叫做android.os.NetworkOnMainThreadException
意思很白話的就是:網路的活動跑在主要執行緒上了啦!

ICS很貼心的告訴你,這樣子你的APP可能會因為等待回應太久(超過5秒)
而會被系統強制關閉(收到ANR)

 

(備註:之前是為了示例方便,所以沒有照多執行緒的寫法撰寫,在這裡當然也就不能執行啦
Android光這點就還蠻嚴謹的)


OK,瞭解為何出錯的原因
也剛好在這裡,帶大家看看多執行緒要怎麼改吧

專案的Layout版面等定義上和之前這篇一模一樣
有特別修改的地方會特別標出

 

package com.J_Test.httpPostTest;

import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast; 

public class Main extends Activity implements OnClickListener

{

   private EditText txtMessage;

   private Button sendBtn;

   private String uriAPI = "http://192.168.1.3/httptest/httpPostTest.php";

   /* 「要更新版面」的訊息代碼 /

   protected static final int REFRESH_DATA = 0x00000001;

 

   /* 建立UI Thread使用的Handler,來接收其他Thread來的訊息 /

   Handler mHandler = new Handler()

   {

      @Override

      public void handleMessage(Message msg)

      {

          switch (msg.what)

          {

          // 顯示網路上抓取的資料

          case REFRESH_DATA:

             String result = null;

             if (msg.obj instanceof String)

                result = (String) msg.obj;

             if (result != null)

                // 印出網路回傳的文字

                Toast.makeText(Main.this, result, Toast.LENGTH_LONG).show();

             break;

          }

      }

   };

 

   @Override

   public void onCreate(Bundle savedInstanceState)

   {

      super.onCreate(savedInstanceState);

      setContentView(R.layout.main);

 

      txtMessage = (EditText) findViewById(R.id.txt_message);
      sendBtn = (Button) findViewById(R.id.send_btn);

 

      if (sendBtn != null)
          sendBtn.setOnClickListener(this);

   }

 

   @Override
   public void onClick(View v)

   {

      if (v == sendBtn)

      {

          if (txtMessage != null)

          {

             // 擷取文字框上的文字

             String msg = txtMessage.getEditableText().toString();

             // 動一個Thread(執行緒),將要傳送的資料放進Runnable中,讓Thread執行

             Thread t = new Thread(new sendPostRunnable(msg));

             t.start();

          }

      }

   }

 

   private String sendPostDataToInternet(String strTxt)

   {

      / 建立HTTP Post連線 /

      HttpPost httpRequest = new HttpPost(uriAPI);

      /*

       * Post運作傳送變數必須用NameValuePair[]陣列儲存

       */

      List<NameValuePair> params = new ArrayList<NameValuePair>();

      params.add(new BasicNameValuePair("data", strTxt));

 

      try

      {

          / 發出HTTP request /

          httpRequest.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));

          / 取得HTTP response /

          HttpResponse httpResponse = new DefaultHttpClient()

                .execute(httpRequest);

          / 若狀態碼為200 ok /

          if (httpResponse.getStatusLine().getStatusCode() == 200)

          {

             / 取出回應字串 /

             String strResult = EntityUtils.toString(httpResponse

                   .getEntity());

             // 回傳回應字串

             return strResult;

          }

      } catch (Exception e)

      {

          e.printStackTrace();

      }

      return null;

   }

 

   class sendPostRunnable implements Runnable

   {

      String strTxt = null;

 

      // 建構子,設定要傳的字串

      public sendPostRunnable(String strTxt)

      {

         this.strTxt = strTxt;

      }

 

      @Override

      public void run()

      {

          String result = sendPostDataToInternet(strTxt);

          mHandler.obtainMessage(REFRESH_DATA, result).sendToTarget();

      }

   }

}


特別注意這裡

  Handler mHandler = new Handler()

   {

      @Override

      public void handleMessage(Message msg)

      {

          switch (msg.what)

          {

          case REFRESH_DATA:

             // …………(相關程式碼)

             break;

          }

      }

   }; 

這是Android對於多執行緒的寫法

這裡做了一個Handler來接收訊息

因為當你建立執行緒,資料處理與主執行緒無關

當然,新建出來的執行緒,是碰不到UI介面的 (會跳Exception)

所以才會如此設計

 

訊息,說穿了就是一個int,可以自行定義訊息的內容

 

/** 「要更新版面」的訊息代碼 */

   protected static final intREFRESH_DATA = 0x00000001;

 

類別一開始就定義這個常數

然後在

public void handleMessage(Message msg){

}

的地方就可以使用 msg.what 存取到訊息的內容

這裡用Switch – case的方式撰寫

 


 

發訊息的函式如下

mHandler.obtainMessage(REFRESH_DATA, result).sendToTarget(); 

發訊息的相關方法呼叫可參考官方說明

http://developer.android.com/reference/android/os/Handler.html

 

文件上可看到,obtainMessage有這幾個多載方法

obtainMessage(int what);

obtainMessage(int what, int arg1, int arg2);

obtainMessage(int what, int arg1, int arg2, Object obj);

obtainMessage(int what, Object obj);

常用的有第一、第二、第四種

 

尤其是第四種,這訊息可以傳入Object

所以自由度很高

 


最後,是建立執行緒

 

// 動一個Thread(執行緒),將要傳送的資料放進Runnable中,讓Thread執行

Thread t = new Thread(new sendPostRunnable(msg));

t.start();

 

這裡是用一個臨時的執行緒的方式

在建構子之中傳入一個Runnable來達成

執行緒的部分也可以用匿名class的方式撰寫

 

// 擷取文字框上的文字

final String msg = txtMessage.getEditableText().toString();

new Thread()

{

       public void run()

       {

            String result = sendPostDataToInternet(msg);

            mHandler.obtainMessage(REFRESH_DATA, result).sendToTarget();

       }

}.start();

 

但這樣的方式要注意,內部類別(Inner class)當中

只能存取外部的常數(這是Java的規定)

final String msg;

所以為何String前面要加上final了

 

詳細多執行緒可參考

[Android] 多執行緒-Handler和Thread的關係

http://j796160836.pixnet.net/blog/post/28766165

[Android] 關於Thread執行緒

http://j796160836.pixnet.net/blog/post/29895257

 


很多網友都問到,

為何我在Android傳送出來的資料
在瀏覽器中怎麼都看不見

因為資料到伺服器上沒有將它存下來
在這裡可以做一些修改

我們把傳到伺服器上的資料
用MySQL存下來,然後再顯示最後輸入的資料

 

以PHP為例,我們在phpmyadmin上
建立一個資料庫,名稱叫 httpPostTest

實際設定如圖:

Screen Shot 2012-04-06 at 下午2.45.22  

這是其對應的SQL

CREATE TABLE IF NOT EXISTS weblog (
log_id int(11) NOT NULL AUTO_INCREMENT COMMENT ‘編號’,
data varchar(255) NOT NULL COMMENT ‘傳入的資料’,
post_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT ‘發佈時間’,
PRIMARY KEY (log_id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT=’訊息記錄’ AUTO_INCREMENT=1 ;

 


 

之前使用的 httpPostTest.php 我們做一些修改

<?php

// 資料庫相關資料
$database_dblink = "httpPostTest";
$username_dblink = "root";
$password_dblink = "YOUR_ROOT_PASSWORD";

// 建立資料庫連線
$dblink = mysql_pconnect("localhost", $username_dblink, $password_dblink) or trigger_error(mysql_error(),E_USER_ERROR);
mysql_query("SET NAMES utf8",$dblink);
mysql_query("SET CHARACTER_SET_CLIENT=utf8",$dblink);
mysql_query("SET CHARACTER_SET_RESULTS=utf8",$dblink);
mysql_select_db($database_dblink, $dblink);

// 宣告utf-8的編碼
header("Content-Type:text/html; charset=utf-8");
// 接收POST/GET的資料
$data=@$_REQUEST[‘data’];

// 如果有資料
if (strcmp(trim($data), "")!=0)
{
      // 將資料輸入進資料庫
      $insertSQL = sprintf("INSERT INTO weblog (data) VALUES (‘%s’);", $data);
      mysql_query($insertSQL, $dblink) or die(mysql_error());
}

// 從資料庫撈出來最後一筆資料
$query_rs = "SELECT * FROM weblog order by log_id desc limit 0,1";
$rs = mysql_query($query_rs, $dblink) or die(mysql_error());

$row = mysql_fetch_assoc($rs);
echo "data=".$row[‘data’]."n"."time=".$row[‘post_time’];

?>

這樣在瀏覽器就會暫存最後一筆的資料了

如果有學過PHP相關的語法

以上的程式碼應該不難才是

 

 


最後還是提醒一下,Android還沒有跑出來的朋友
若是在Logcat看到類似Permission denied的字眼

代表你忘記了AndroidManifest.xml的這句摟,詳細看之前寫的這篇

    <!– 這裡加入可以存取網路的權限 –>

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

 


關於source code的部份,我已經放上gitHub了

https://github.com/j796160836/httpPostTest

[Android] ViewFlipper “Receiver not registered” 的 bug

又抓到一個bug了,真開心

這次是Android自己本身控件的問題

有使用到ViewFlipper的朋友注意了

 

如果最近有遇到這個bug

java.lang.IllegalArgumentException: Receiver not registered: android.widget.ViewFlipper$1@464946a0

at android.app.ActivityThread$PackageInfo.forgetReceiverDispatcher(ActivityThread.java:793)
at android.app.ContextImpl.unregisterReceiver(ContextImpl.java:814)
at android.content.ContextWrapper.unregisterReceiver(ContextWrapper.java:331)
at android.widget.ViewFlipper.onDetachedFromWindow(ViewFlipper.java:104)
at android.view.View.dispatchDetachedFromWindow(View.java:6033)
…….(略刪)

網友看過source code 發現是google本身控件的Bug

可以用以下,最簡單的方式可以這樣解決

—————————————————————————-

解決方式

建立一個package名字叫做com.J_Test.temp

建立一個class名稱叫做myViewFlipper 

貼上其程式碼

 

package
com.J_Test.temp;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ViewFlipper;

public class myViewFlipper extends ViewFlipper

{
     // 建立預設的建構子
     public myViewFlipper(Context context)
     {
           super(context);
     }

     public myViewFlipper(Context context,
AttributeSet attrs)
     {
           super(context, attrs);
     }

 
     @Override
     protected void onDetachedFromWindow()
     {
           // Android 2.1~2.3.4bug ()
           // 解決ViewFlipper “Receiver not
registered” Error
           try
           {
                super.onDetachedFromWindow();
           } catch (IllegalArgumentException e)
           {
                stopFlipping();
           }
     }
}

 

 

使用方式

將舊有的程式碼

   <ViewFlipper
        android:id=“@+id/flipper”
        android:layout_width=“match_parent”
        android:layout_height=“wrap_content”
        android:flipInterval=“5000”
        android:inAnimation=“@anim/push_up_in”
        android:outAnimation=“@anim/push_up_out” >

 ………….

    </ViewFlipper>

替換掉成我們的控件

   <com.J_Test.temp.myViewFlipper
        android:id=“@+id/flipper”
        android:layout_width=“match_parent”
        android:layout_height=“wrap_content”
        android:flipInterval=“5000”
        android:inAnimation=“@anim/push_up_in”
        android:outAnimation=“@anim/push_up_out” >

 ………….

    </com.J_Test.v1.temp.myViewFlipper>

 

這樣就OK了

 

原理就是上述藍色字處有bug

我們繼承它,把super()用try-catch把他接起來

然後呼叫stopFlipping()會讓所有程序停止

 

 

—————————————————————————-

測試方式如下:

在XML做一個ViewFlipper

JAVA程式對應尋找其View

ViewFlipper mFlipper = (ViewFlipper) findViewById(R.id.flipper);

還沒有呼叫

mFlipper.startFlipping();

之前就把螢幕轉向

網友測試好像從Android 2.1就出現此bug

測試從Android 2.3.4都還仍然依舊

—————————————————————————-

 

 

參考資料

http://daniel-codes.blogspot.com/2010/05/viewflipper-receiver-not-registered.html
http://code.google.com/p/android/issues/detail?id=6191


[Android] 多執行緒-Handler和Thread的關係 (2)

之前寫過一個多執行緒,發現還是寫太亂了,不夠簡單

http://j796160836.pixnet.net/blog/post/28766165

 

我重新敘述一下

 

多執行緒的部分,有幾個名詞

  • Runnable 工作包 (要做的事情)
  • Thread 執行緒
  • Handler
  • Message

 

————————————————————————–

Runnable

就是像是專案管理裡的工作包,說穿了就是要做的事情啦,像是這樣

   private Runnable r1=new Runnable () {

        public void run() {

            //………………………..

            //做了很多事

        }

    };

這裡的 r1 就是一個runnable

————————————————————————–

Thread

在Android的世界裡,Thread分成二種

1.  單次型 (Java原有的)

2.  常駐型 (Android特有的)

 

 

————————————————————————–

1.  單次型

意指就是給他一件艱巨的任務

他做完就會關閉了

 

寫法實在有夠簡單

   
   Thread t1=
new Thread(r1);

   t1.start();


這裡的 r1 是一個runnable

————————————————————————–

 

2.  常駐型

就是做完事情他不會自動關閉,而是變成一個idle (閒置) 的狀態

閒置意思就是他沒事幹啦~  要給他事情做

 

        mThread = new HandlerThread(“name”);

        //Worker待命,等待其工作 (開啟Thread)

        mThread.start();

這樣子就可以建立且執行Thread了

連Runnable也不想打的話可以合併起來

 

     Thread XXX = new Thread(new Runnable()

     {

           publicvoid run()

           {

                // ……工作

           }

     }).start();

 

要給他事情做要這樣寫

 mThreadHandler.post(r1);

 

 

有沒有發現,單次型的Thread就是把事情定義下來然後呼叫start()

開始跑,跑完關閉

 

而常駐型的話,反而是讓你先start()

然後post(r1)   給他事情做,做完就閒置

 

請務必記得不用這個Thread的時候要把他關閉


        if (mThread != null)
            mThread.quit();

大部分的情況,常駐型的Thread

在onCreate()裡面建立Thread

在onDestory()裡面關閉Thread



————————————————————————–

Handler

那甚麼是Handler呢?

你可以想成是一個服務的窗口

給他事情做的地方


寫法有二種


1.  mUI_Handler.post(r2);
2.  mUI_Handler.sendEmptyMessage(MSG_UPLOAD_OK);

第一種就是直接給他一個Runnable,讓他去執行

第二種就是傳一個Message給他



Handler的建立

 private Handler mUI_Handler = new Handler();

這樣會建立一個基於Main Thread (UI Thread)的Handler


————————————————————————–

Message


這東西不複雜,剛剛不是還在講Runnable嘛?

Message就是要一言以蔽之,用一個值  (一句話)
代表一堆事情(Runnable)


先看看Handler的變形吧


Handler的建立

 private Handler mUI_Handler = new Handler();

這樣會建立一個基於Main Thread (UI Thread)的Handler

以下是他的變形

     private Handler mUI_Handler new Handler()

     {

           @Override

           public void handleMessage(Message msg)

           {

                switch (msg.what)

                {

                     case MSG_UPLOAD_OK:

                           // …………..

                           break;

                }

           }

     };

這樣會建立一個基於Main Thread (UI Thread)的Handler

有一個窗口,有一個地方可以處理Message的地方  ( 就是handleMessage() )

這裡用一個switch case的格式表示

msg.what  就是你訊息的內容

MSG_UPLOAD_OK  這就是你的訊息了(自己自訂)

像這樣

privatestaticfinalint MSG_UPLOAD_OK= 0x00000001;

說穿了就是個int而已


要使用的時候就這樣

 mUI_Handler.sendEmptyMessage(MSG_UPLOAD_OK);

可以使用帶參數的寫法

 mUI_Handler.obtainMessage(MSG_UPLOAD_OK, arg1, arg2).sendToTarget();

或是

 mUI_Handler.obtainMessage(MSG_UPLOAD_OK, obj1).sendToTarget();

這樣可以帶二個int去,或是直接帶object給他(收到了之後再去轉型…)









[Android] 如何Debug 含有Bundle (has Extra) 的Intent

2012-12-24 02 15 06  

不知道在Android做Intent除錯時
和我有類似的經驗

自己寫的APP,明明看到LogCat上面寫著

 

Starting activity: Intent { cmp=<你的Package名稱>/.<你的Activity名稱> (has extras) }

 

對呀,我的Activity被Intent喚起來了

但那個has extras又是啥鬼?
阿我就是想知道那些Extras是啥呀(怒吼~

 


 

如果用Log直接把Intent直接toString

Log.v("Test Intent", intent.toString());

對不起他只會跟你說是個物件,ID為何
這訊息根本不是我們想要的

 


 

沒關係,有解的。

 

你需要

intent.getExtras()

會回傳Bundle

 

利用Bundle的

bundle.keySet()  來達成

它會回傳一個只有Key名字的hash

那你可以使用.toArray()轉換成普通的陣列

 

組合起來成為一長串

Log.v("Test Intent", intent.getExtras().keySet().toArray().toString())

 

 就是它了。

 

 

參考文章
http://groups.google.com/group/android-developers/browse_thread/thread/00828fe418964285