[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

 

 

[Java] Eclipse的偏好設定(操作習慣和好用技巧)

2012-11-15 15 55 03

實在不得不說,Eclipse真的是個很強大的IDE

有些好用快捷鍵在還沒有開始設定偏好設定前先分享一下
這些快捷鍵

Ctrl + Shift + F    自動排版(超好用

Alt + /                 開啟字彙選擇框(自動完成框)
Ctrl + D               刪除一行
Ctrl + /                註解/解除註解多行
Ctrl + B               建置專案
Ctrl + L               到指定行號(一般編輯器都是Ctrl + G)
Ctrl + 1               跳出修正建議框

 

 


其實這篇的產生因為重灌電腦,常常好多Eclipse好用的偏好設定都忘了設定上去
然後選項又超多,分類錯綜複雜 

除了做個備份,也一併分享給大家

這些路徑不用強記,可以憑印象打關鍵字,很多選項就會跳出來了唷

 

 

專案的編碼格式(Encoding)

這我不知為何,Eclipse它的預設值是ANSI

在還沒有專案的時候,可以像我一樣調整成utf-8

但是如果已有專案的話,文字有可能會變亂碼唷

這時候用convertz救援吧

 

路徑:General > Workspace

關鍵字:encoding

2012-11-15 15 57 41

Text file encoding 的地方,文字框打入 utf-8

另外,Build automatically 的地方,我喜歡把勾勾去掉

需要的時候手動按 Ctrl + B 建置專案

 

 

字體大小設定

我雖然沒有老花,但就是不喜歡字體小小的糊在一起,對Eclipse第一個開刀的一定是字體大小

字體大一點,自己看得也舒服

路徑:General > Appearance > Colors and Fonts

關鍵字:font

2012-11-15 15 59 51

在Basic裡的Text Font修改字體大小

如果只是程式碼,其實只要調Text Font就好,其他會跟著Text Font去調整

至於中文字嘛…預設的字型調過了之後,中文字還是很小
這時可能要調字型,至於字型…每個人的適應能力不同

通常是找每個字體間距相等的為佳,這部份留給大家自行嘗試摟 

 

 

自動格式化設定

路徑:Java > Code Style > Formatter

關鍵字:Format

Eclipse最強大的功能就是「自動格式化(Auto Format)」  (灑花)

在coding的時候可以按個快捷鍵 Ctrl + Shift + F

在凌亂的格式,都整得服服貼貼

 

 

因為受大學老師程式碼風格影響,大括弧「 { 」一定要換一行

所以我們就這樣對應調整

2012-11-15 16 03 46

Eclipse預設風格是不給調的,複製一份設定檔即可

按下New,打入一個喜歡的名字

2012-11-15 16 05 23

在Braces裡,除了最後一個之外,其它都設定成Next line
右邊有預覽窗格,可以邊調邊看效果 

 

 

自動完成框

不只是Eclipse,很多整合開發環境(IDE)都會提供自動完成框

尤其是Visual studio,打幾個字就跳出候選字選取框

就像你在用google一樣簡單

因為電腦CPU等等的條件影響,預設不是甚麼鍵都觸發之
但我們可以把他打開

 

路徑:Java > Editor > Content Assist

關鍵字:auto

2012-11-15 16 07 10

這個自動完成的功能,因為受到網路文章的影響

發現大為好用,故分享給大家

我就直接轉載了

 

勾選 "Enable auto-activation" 的選項
Auto activation delay 為提示出現的延遲時間,建議可以設為 0ms (預設是200ms)
Auto activation triggers for Java 為 IDE 遇到何種字元會自動啟動提示,預設只有 dot,請改為 .abcdefghijklmnopqrstuvwxyz(,
Auto activation triggers for Javadoc 在註解區塊中遇到何種字元會啟動提示,預設是 @ 及 #

 

這個Auto activation triggers for Java,如果CPU不夠力的時候,可以調回預設值:點點 「 .

手動按 alt + / 也是會跳出來啦

 

參考:

http://icercat.pixnet.net/blog/post/23671027

 

ADB連接逾時

這個選項是從這裡來的

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

在開發Android,可以用wifi 來做打開除錯 (就像是連接USB一樣)

但這功能需要root

 

最近是遇到當網路環境不穩定時,連接常常逾時(timeout)

所以可以在這裡設定timeout的時間

路徑:Android > DDMS

2012-11-15 15 55 37

 

ADB connection time out (ms): 預設是5000

經由測試,大概改到80000足夠

 

 


將會不定時增加…

[RDP] 遠端桌面使用的常用快捷鍵

因為常常使用遠端桌面,太常忘記了要怎麼按

在遠端桌面按 Ctrl + Alt + Delete 了,就筆記一下

 

本機快速鍵 遠端桌面快速鍵 功能說明
CTRL + ALT + DEL CTRL + ALT + END

顯示 [Windows 安全性] 對話方塊

CTRL + ALT + BREAK

在視窗和全螢幕顯示模式間切換。

Win Key 或 CTRL + ESC

ALT + HOME

顯示 [開始] 功能表。

ALT + TAB

ALT + PAGE UP

從左到右切換視窗
SHIFT + ALT + TAB

ALT + PAGE DOWN

從右到左切換視窗
PrintScreen

CTRL + ALT + 數字鍵上的加號(+)

將遠端的整個螢幕畫面複製回本機。
ALT + PrintScreen

CTRL + ALT + 數字鍵上的減號(-)

將遠端的作用中的螢幕畫面複製回本機。

 

資料來源

http://blog.miniasp.com/post/2008/07/22/The-hotkey-usage-in-Remote-Desktop-environment.aspx

http://windows.microsoft.com/zh-HK/windows-vista/What-shortcut-keys-can-I-use-in-Remote-Desktop-Connection

[JSP] JSP連接MySQL初探

這篇算是因為網友詢問,才會有的一篇文章吧

 

最近也在看JSP的一些寫法

因為還沒有看很懂,所以這一篇一直都沒有寫

用Java跑網頁有二種型態

  • Servlet
  • JSP

Servlet可以算是JSP的前身,可以處理比較複雜的事情
JSP的寫法比較像PHP,那我們就從這裡開始吧

範例不難,但環境設定上頗麻煩的

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

環境設定

再開始之前,先講一下環境設定

標題是連MySQL資料庫嘛,所以要有MySQL取得方式我就不細講了

有在寫PHP+MySQL的話,目前在使用的MySQL就大方的借過來用吧

 

 

若是使用整合環境(如appserv、wamp、xampp…等等)

就只需要補上JDK、Tomcat、MySQL Connecter,官方下載在這裡:

JDK

http://www.oracle.com/technetwork/java/javase/downloads/index.html

Tomcat

http://tomcat.apache.org/download-70.cgi

MySQL Connecter for Java

http://www.mysql.com/downloads/connector/j/

選擇你的作業系統看是x86還是x64,選擇對應的檔案安裝

然後用Next大法(一直Next),一路給他裝完

 

如果,你像我一樣覺得這樣裝很凌亂,或是說是新建環境,甚麼都沒有的話

可以採用新版的XAMPP 1.81

http://www.apachefriends.org/zh_tw/xampp.html

這個版本裡面就有附tomcat摟

少一個要補檔的對象

接下來只要下載JDK + MySQL Connecter就行摟!

 

 

個人是建議用JDK+XAMPP的配置,或是整合環境+JDK+官方tomcat

(對了,新版含tomcat的XAMPP不能跟tomcat混裝,路徑會錯亂…)

XAMPP他主要功能有包入Apache HTTP Server、MySQL Server、FileZilla FTP Server、Mail Server、Tomcat Server

細項有php、pear、perl、cgi、phpmyadmin、mbstring、gd….等等(糟糕離題了…)

 

反正,整合環境有他的好處,除了設定檔要自己設定之外

其他大多都設定好了

 

 

MySQL Connector的地方先說明一下

只要下載完畢,解壓縮

取出mysql-connector-java-5.1.22-bin.jar這個檔案就好,壓縮檔裡的其他資料可以不用

(筆者抓到的是Connector/J 5.1.22版本,所以檔案叫做mysql-connector-java-5.1.22-bin.jar

請依照你下載的版本做變化)

然後將它放到tomcat的目錄的lib裡,所以變成

官方Tomcat路徑:

C:\Program Files\Apache Software Foundation\Tomcat 7.0\lib\mysql-connector-java-5.1.22-bin.jar

XAMPP包含tomcat路徑:

C:\xampp\tomcat\lib\mysql-connector-java-5.1.22-bin.jar

 

環境變數

主要有點麻煩的是Java的環境設定

Windows環境變數的方法在這裡:

WinXP在我的電腦按滑鼠右鍵 -> 內容 -> (上方頁籤)進階 -> 環境變數

Win7是在開始 -> 我的電腦按右鍵 -> 內容 -> (在左側欄)進階系統設定 -> (上方頁籤)進階 -> 環境變數

 

要添加這些環境變數

純JAVA環境
———————
JAVA_HOME
C:\Program Files\Java\jdk1.6.0_35
CLASSPATH
.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\jre;%JAVA_HOME%\src
Path
.;%JAVA_HOME%\bin

JAVA環境+Tomcat網頁伺服器環境
———————
JAVA_HOME
C:\Program Files\Java\jdk1.6.0_35
CATALINA_HOME
(官方Tomcat路徑)
C:\Program Files\Apache Software Foundation\Tomcat 7.0
 
(XAMPP包含tomcat路徑)
C:\xampp\tomcat
(二者依環境,擇其一使用) 

CLASSPATH
.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\jre;%JAVA_HOME%\src;%CATALINA_HOME%\lib\servlet-api.jar;%CATALINA_HOME%\lib\mysql-connector-java-5.1.22-bin.jar
Path
.;%JAVA_HOME%\bin

有些地方要注意

如果裡面有值的話(像是Path),打個分號 ; 再連接上去

藍字的地方是他的版本號,要依照你的版本做變化(查看一下該路徑是否有東西)

設定完畢要重新開機(使用者登出再登入)才會生效

這樣就設定完成了

 

弄到這裡可以先測試一下

phpmyadmin是否能正常連線MySQL?(代表php+MySQL正常)

tomcat是否正常啟動? http://localhost:8080/ 有沒有東西?

 

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

建立範例DB

終於回到程式面了,可以開個資料庫名字叫test_jspdb

執行以下SQL;

CREATE TABLE IF NOT EXISTS `employee` 
`emp_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`emp_name` varchar(100) NOT NULL,
`emp_age` int(11) NOT NULL,
PRIMARY KEY (`emp_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=3;

INSERT INTO `employee` (`emp_id`, `emp_name`, `emp_age`) VALUES
(1, ‘mary’, 20)
(2, ‘amy’, 22);

 

資料表內容很簡單,就只有員工姓名,員工年齡而已

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

 

JSP範例

這時候可以建立一個連線的範例來做了

網站的根目錄在:

C:\xampp\tomcat\webapps\ROOT

C:\Program Files\Apache Software Foundation\Tomcat 7.0\webapps\ROOT

在那裡建立一個空檔案db_test.jsp然後貼上以下程式碼。

然後瀏覽 http://localhost:8080/db_test.jsp 看看能不能使用

如果出現can’t load mysql driver代表說MySQL Connector的設定有問題

查看檔案位置是否正確及環境變數設定是否有更新

 

 

 

<%@ page contentType=”text/html;charset=utf-8″%>

<%@ page import=”java.sql.*”%>
<%
try
{
//讀取mysqlDriver驅動程式
Class.forName(“org.gjt.mm.mysql.Driver”).newInstance();

try
{
//連接mysql資料庫

// 資料庫名稱”zend_test”,帳號”root”,密碼”123456″,
// 使用Unicode編碼”true”,字元集”UTF-8″
String db_user=”root”;
String db_pwd=”12345″;
String db_database=”test_jspdb”;
Connection conn= DriverManager.getConnection(“jdbc:mysql://localhost:3306/”+db_database+”?user=”+db_user+”&password=”+db_pwd+”&useUnicode=true&characterEncoding=UTF-8″);

try
{
//建立statement
Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
try
{
//建立SQL查詢
String sql=”select * from album”;
ResultSet rs = stmt.executeQuery(sql);

%>

<h2>Employee</h2>
<table border=1>
<tr><th>id</th><th>artist</th><th>title</th></tr>
<%
//顯示資料

while(rs.next())
{
%>
<tr>
<td><%=rs.getString(“id”)%></td><td><%=rs.getString(“artist”)%></td><td><%=rs.getString(“title”)%></td>
</tr>
<%
}
%>
</table>
<%

// 關閉連線
rs.close();
rs = null;
stmt.close();
stmt = null;
conn.close();

}
catch (Exception ex)
{
out.println(“can’t read data”);
out.println(ex.toString());
}
}
catch (Exception e)
{
out.println(“can’t create statement”);
out.println(e.toString());
}
}
catch(Exception e)
{
out.println(“can’t content mysql database”);
out.println(e.toString());
}

}
catch(Exception e)
{
out.println(“can’t load mysql driver”);
out.println(e.toString());
}

%>

 

 寫法跟PHP差不多吧!有個重點要提一下

 

其餘的update insert delete指令可以這樣寫)

String sql = “INSERT INTO `employee` (`emp_name`, `emp_age`) VALUES
(‘mary’, 20),”;

// 執行SQL語法
Statement stmt = conn.createStatement();
stmt.executeUpdate(sql);

 

你可能會疑惑createStatement這個方法

ResultSet.TYPE_FORWARD_ONLY (預設值)

就只能使用next()方法取得下一筆(指標順序定位)

 

ResultSet.TYPE_SCROLL_INSENSITIVE與 ResultSet.TYPE_SCROLL_SENSITIVE

能使用afterLast()、previous()、absolute()、relative()其他方法

有 ResultSet.CONCUR_READ_ONLY與ResultSet.CONCUR_UPDATABLE兩個參數可以設定,前者表示只能讀取 ResultSet的資料,後者表示可以直接使用ResultSet來操作資料庫

(這個部份還沒有驗證,我想應該跟update insert delete指令無關)

 

如果有誤煩請指正

 

參考資料

http://caterpillar.onlyfun.net/Gossip/JavaGossip-V2/ResultSetCursor.htm

http://jjnnykimo.pixnet.net/blog/post/21585335-jsp%E9%80%A3%E7%B5%90mysql%E8%B3%87%E6%96%99%E5%BA%AB%E7%AF%84%E4%BE%8B

http://blog.yam.com/carl44/article/14825336

http://blog.alumni.nctu.edu.tw/plate/web/papermsg.jsp?UI=markcool&PI=96 

[MySQL] 數字10以下的中文數字排序(或是列表排序)

這是網路上參考別人寫的方式,靈機一動想到的

因為想到這個方法覺得很BG且好笑
就當個茶餘飯後的笑話分享給大家

在這之前還是先呼籲一下,

在建立DB的時候,數字就應該用數字的格式儲存

 

不要像我犯同樣的錯誤
拿小寫的中文數字(一二三四五六七八九) 
或是大寫的中文數字(壹貳參肆伍陸柒捌玖拾)

去儲存數字,然後自找苦吃

因為它的ASCII的code大小也沒照順序(所以資料庫的文字排序失效) 

————————————————————

網路上對於MSSQL有個解法
就是使用列表比對的方式

也有人使用自訂函數去parse中文數字然後轉換成對應的數字

也可以利用一個對應的轉換表,用inner join的方式去做

————————————————————

在MySQL裡,我找到一個還不錯的做法:

http://dev.mysql.com/doc/refman/5.0/en/string-functions.html#function_locate

Locate函數:

Locate(欄位名稱, 搜尋比對的字串)

找尋字串中,第一個出現的位置,並回傳其位置

而這位置就是我們要的順序結果(反正多大也不重要,至少相對順序是對的)

如果我有一張表table01

todo  week 
寫作業  一 
買東西  三 
聚餐  二 
看電影  四 

想照星期排序(這就中文數字了吧….Orz)

就這樣下

select *, LOCATE( week,  ‘一二三四五六日’ ) from table01 order by LOCATE( week,  ‘一二三四五六日’ );

聰明的你,就會在phpmyadmin看到這個結果,然後就會跟我一樣大笑XDD

todo  week  LOCATE( week,  ‘一二三四五六日’ )
寫作業  一  1
聚餐  2
買東西  三  3
看電影  四  4

 

理論上,只要是不重複的字串應該都可以適用

拜託鞭小力點…

[Fedora 14-17] 架設VPN Server (pptpd)

安裝PPTPD及相關服務

yum install -y rp-pppoe gcc pptp pptp-setup  ppp ppp-devel
 
安裝方式有二種:
 
RPM安裝(要依照Linux的版本安裝)
 
例如Fedora 14 (32bit)
wget http://poptop.sourceforge.net/yum/stable/fc14/i386/pptpd-1.3.4-2.fc14.i686.rpm
rpm -ivh pptpd-1.3.4-2.fc14.i686.rpm
 
Fedora 14 (64bit)
wget http://poptop.sourceforge.net/yum/stable/fc14/x86_64/pptpd-1.3.4-2.fc14.x86_64.rpm
rpm -ivh pptpd-1.3.4-2.fc14.x86_64.rpm
 
YUM安裝(要依照Linux的版本安裝)
 
rpm -ivh http://poptop.sourceforge.net/yum/stable/fc14/pptp-release-current.noarch.rpm
yum install pptpd
 
 
下載並安裝核心模組
 
 
wget http://sourceforge.net/projects/poptop/files/mppe%20module%20builder/kernel_ppp_mppe-1.0.2%20dkms-2.0.6/kernel_ppp_mppe-1.0.2-3dkms.noarch.rpm/download
 
wget http://downloads.sourceforge.net/project/poptop/mppe%20module%20builder/kernel_ppp_mppe-1.0.2%20dkms-2.0.6/kernel_ppp_mppe-1.0.2-3dkms.noarch.rpm?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fpoptop%2Ffiles%2Fmppe%2520module%2520builder%2Fkernel_ppp_mppe-1.0.2%2520dkms-2.0.6%2F&ts=1341655470&use_mirror=nchc
 
wget http://downloads.sourceforge.net/project/poptop/mppe%20module%20builder/kernel_ppp_mppe-1.0.2%20dkms-2.0.6/dkms-2.0.6-1.noarch.rpm?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fpoptop%2Ffiles%2Fmppe%2520module%2520builder%2Fkernel_ppp_mppe-1.0.2%2520dkms-2.0.6%2F&ts=1341655518&use_mirror=nchc
 
rpm -ivh dkms-2.0.6-1.noarch.rpm
rpm -ivh kernel_ppp_mppe-1.0.2-3dkms.noarch.rpm
 
 
若沒有按照版本安裝會有類似以下錯誤: 
Error! Bad return status for module build on kernel: 3.4.2-1.fc16.x86_64 (x86_64)
Consult the make.log in the build directory
/var/lib/dkms/kernel_ppp_mppe/1.0.2/build/ for more information.
 
Error! Could not locate ppp_generic.ko for module kernel_ppp_mppe in the DKMS tree.
You must run a dkms build for kernel 3.4.2-1.fc16.x86_64 (x86_64) first.
 
========================================================
 
設定PPTPD
 
 
設定Client看到的伺服器IP,以及DHCP分發的IP範圍
vi /etc/pptpd.conf
加入以下:
localip 10.0.0.1
remoteip 10.0.0.10-100
 
—————————————————-
 
設定pptpd的加密方式和發給Client的DNS
vi /etc/ppp/options.pptpd
refuse-pap
refuse-chap
refuse-mschap
 
require-mschap-v2
require-mppe-128
 
ms-dns 168.95.1.1
 
—————————————————-
 
設定帳號密碼(若使用ppp撥號(xDSL),撥號的帳密也會在這裡)
格式為:
帳號、使用的服務、密碼、接受的IP(通常為*號)
中間用空格隔開
 
vi /etc/ppp/chap-secrets
內容大致為:
# client        server           secret             IP addresses
[email protected]”      *         “your password”
test pptpd 12345 *
 
 
—————————————————- 
 
設定系統IP轉發
vi /etc/sysctl.conf
修改
net.ipv4.ip_forward = 1
 
讓系統重新載入設定值
sysctl -p
 
—————————————————-
 
設定防火牆
 
主要是設定讓PPTP的Client能夠NAT上網
請依照對外連線的網路卡代號取代掉ppp0
ppp+的代表用來連接VPN Client的網卡,不需更動
vi /etc/sysconfig/iptables
 
 
*nat
:PREROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -o ppp0 -j MASQUERADE
COMMIT
 
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state –state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m state –state NEW -m tcp -p tcp –dport 22 -j ACCEPT
# 其他要開啟服務的port依照格式寫在這裡
 
#—-vpn—-
-A INPUT -i ppp0 -p tcp –dport 1723 -j ACCEPT
-A INPUT -i ppp0 -p gre -j ACCEPT
-A FORWARD -i ppp+ -o ppp0 -j ACCEPT
-A FORWARD -i ppp0 -o ppp+ -j ACCEPT
#—-vpn—-
 
-A INPUT -j REJECT –reject-with icmp-host-prohibited
-A FORWARD -j REJECT –reject-with icmp-host-prohibited
COMMIT
 
—————————————————-
 
重啟防火牆(Fedora 14以上使用)
systemctl restart iptables.service
systemctl restart ip6tables.service
 
重啟防火牆(Fedora 14含以下使用)
/etc/init.d/iptables restart

 

 
========================================================
 
啟動PPTPD等服務
 
 
啟動PPTPD服務
/etc/init.d/pptpd start
重啟PPTPD服務
/etc/init.d/pptpd restart
斷除所有VPN連線
/etc/init.d/pptpd restart-kill
 
 
查看相關的Log
tail -n 30 /var/log/messages
 
查看連線情況
netstat -an | grep :1723 | sort
 
========================================================
 

[ASP]在純ASP中操作陣列產生JSON格式

因為專案需求,需要把原本ASP的資料轉成JSON的格式

我知道ASP是大家動態網頁設計的好夥伴(笑)

(迷之音:ASP都出這麼久了,很多都已經轉成ASP.net了幹嘛這麼執著呢?)

就跟你說是專案需求了嘛(巴落去

這只是我的小小筆記,以免有機會又用上找不到怎麼做
尤其是程式語言對於陣列(Array)的操作方式都不大一樣

我盡量強化大家的JSON概念,盡量不特別提程式碼的部份

——————————–

學JSON格式只有二句話:

物件(object)用大括號 { }

陣列(array)用中括號 [ ]

先記住這概念

——————————–

 

ASP要使用JSON要下載此元件:

http://code.google.com/p/aspjson/

我們只需要他的ASP JSON (for production) 就好:
寫文的時候是JSON_2.0.4.asp 

把他放在你的目錄然後用以下方式引用:

<!–#include file=”JSON_2.0.4.asp”–>

這裡有些範例蠻有用的:

http://code.google.com/p/aspjson/wiki/Samples2

 

引用部份範例來說明

物件寫法

<%
Dim o1
Set o1 = jsObject()

      o1(“name”) = “Tom”
      o1(“lastname”) = “Chen”

o1.Flush
%>

顯示

{“name”:”Tom”,”lastname”:”Chen”}

陣列寫法

<%
Dim o2
Set o2 = jsArray()

      o2(Null) = 2
      o2(Null) = 4
      o2(Null) = 6
      o2(Null) = 8

o2.Flush
%>

顯示

[2,4,6,8]

———————

所以我們學到,宣告空白變數使用Dim

決定是何種型態使用 Set 這個字

不要小看這個關鍵字,他可是很有用的

 

 

最後,當你把資料形態設定為jsObject之後

直接像範例一樣 o(“name”) 直接在括號之中放入新的key值,當成變數來指定

 

而資料形態設定為jsArray的時候

可以像範例一樣用o2(Null) 的方式增加值

 

範例集有提到jsArray和jsObject中的對轉,我覺得不是很重要,所以就不引用了

 

再來看比較tricky的

物件包物件

<%
Dim o3
Set o3 = jsObject()

      Set o3(“person”) = jsObject()
            o3(“person”)(“name”) = “Tom”
            o3(“person”)(“lastname”) = “Chen”

      Set o3(“equipment”) = jsObject()
            o3(“equipment”)(“name”) = “keyboard”
            o3(“equipment”)(“type”) = “electronic”

o3.Flush
%>

顯示

{“person”:{“name”:”Tom”,”lastname”:”Chen”},”equipment”:{“name”:”keyboard”,”type”:”electronic”}}

陣列包陣列

<%

Dim o4
Set o4 = jsArray()

      Set o4(Null) = jsArray()
            o4(Null)(Null) = 0
            o4(Null)(Null) = 2
            o4(Null)(Null) = 4
            o4(Null)(Null) = 6

      Set o4(Null) = jsArray()
            o4(Null)(Null) = 1
            o4(Null)(Null) = 3
            o4(Null)(Null) = 5
            o4(Null)(Null) = 7

o4.Flush
%>

顯示

[[0,2,4,6],[1,3,5,7]]

在陣列包陣列的範例中,就會明顯看到
其實程式它是從上到下,照順序執行

雖然 Set o4(Null) = jsArray() 這二行都一樣
但意義不同

難度慢慢增高摟

陣列包物件

<%
Dim o5
Set o5 = jsArray()

      Set o5(Null) = jsObject()
            o5(Null)(“name”) = “Tom”
            o5(Null)(“lastname”) = “Chen”
      Set o5(Null) = jsObject()
            o5(Null)(“name”) = “Amy”
            o5(Null)(“lastname”) = “Lin”

o5.Flush
%>

顯示

[{“name”:”Tom”,”lastname”:”Chen”},{“name”:”Amy”,”lastname”:”Lin”}]

這JSON格式已經接近資料庫撈出來的格式了

物件包陣列

<%
Dim o6
Set o6 = jsObject()
      o6(“name”) = “Tom”
      o6(“lastname”) = “Chen”

      Set o6(“numbers”) = jsArray()
            o6(“numbers”)(Null) = 2
            o6(“numbers”)(Null) = 4
            o6(“numbers”)(Null) = 5
            o6(“numbers”)(Null) = 6

o6.Flush
%>

顯示

{“name”:”Tom”,”lastname”:”Chen”,”numbers”:[2,4,5,6]}

———————

最後跳過中間的步驟,直接開最後的大絕!

陣列包物件包陣列包物件

心理OS:哪那麼多陣列物件呀~

<%
Dim o7
Set o7 = jsArray()

      Set o7(Null) = jsObject()
            o7(Null)(“name”) = “Tom”
            o7(Null)(“lastname”) = “Chen”

            Set o7(Null)(“report”) = jsArray()

                  Set o7(Null)(“report”)(Null) = jsObject()
                        o7(Null)(“report”)(Null)(“subject”)=”Math”
                        o7(Null)(“report”)(Null)(“score”)=80

                  Set o7(Null)(“report”)(Null) = jsObject()
                        o7(Null)(“report”)(Null)(“subject”)=”English”
                        o7(Null)(“report”)(Null)(“score”)=90

      Set o7(Null) = jsObject()
            o7(Null)(“name”) = “Amy”
            o7(Null)(“lastname”) = “Lin”

            Set o7(Null)(“report”) = jsArray()

                  Set o7(Null)(“report”)(Null) = jsObject()
                        o7(Null)(“report”)(Null)(“subject”)=”Math”
                        o7(Null)(“report”)(Null)(“score”)=86

            Set o7(Null)(“report”)(Null) = jsObject()
                        o7(Null)(“report”)(Null)(“subject”)=”English”
                        o7(Null)(“report”)(Null)(“score”)=88

o7.Flush
%>

顯示

[{“name”:”Tom”,”lastname”:”Chen”,”report”:[{“subject”:”Math”,”score”:80},{“subject”:”English”,”score”:90}]},{“name”:”Amy”,”lastname”:”Lin”,”report”:[{“subject”:”Math”,”score”:86},{“subject”:”English”,”score”:88}]}]

看起來很複雜其實並不然

換成XML的格式就長這樣

<data>

    <student>
        <name>Tom</name>
        <lastname>Chen</lastname>

        <report>

            <subject>
                <name>Math</name>
                <score>80</score>
            </subject>
            <subject>
                <name>English</name>
                <score>90</score>
            </subject>

        </report>
    </student>

    <student>
        <name>Amy</name>
        <lastname>Lin</lastname>

        <report>

            <subject>
                <name>Math</name>
                <score>86</score>
            </subject>
            <subject>
                <name>English</name>
                <score>88</score>
            </subject>

        </report>
    </student>

</data>


做成大家看得懂的表格

成績單1

姓名  Tom Chen 
數學 80 
英文  90 

成績單2

姓名  Amy Lin 
數學 86
英文 88 

在回顧一下上面那二個很噁心的寫法
有困難嗎?有任何讓你造成不舒服的感覺嗎? 

 

 

 

===============================================

以下內容看不懂就算了,沒關係

 

最後看看這資料庫的函式QueryToJSON

http://code.google.com/p/aspjson/wiki/SQLtoJSON

FunctionQueryToJSON(dbc, sql)
       
Dim rs, jsa, col
       
Set rs = dbc.Execute(sql)
       
Set jsa = jsArray()
       
WhileNot(rs.EOF Or rs.BOF)
               
Set jsa(Null)= jsObject()
               
ForEach col In rs.Fields
                        jsa
(Null)(col.Name)= col.Value
               
Next
        rs
.MoveNext
       
Wend
       
SetQueryToJSON= jsa
EndFunction

如果上面都看懂的話,只是把欄位名稱和值,用For each印出來,放進jsObject裡而已

rs.Fields就是取得recordset的欄位物件,用

col.Name  取得欄位名
col.Value  取得值

 

若我資料庫有張表

student

s_id  name  lastname 
Tom  Chen 
 Amy Lin 

report

r_id  s_id  subject  score 
Math  80 
English  90
Math  86
English  88

 

是不是我們的資料庫就可以這樣寫呢?

sql = “select * from student”

Set rs = dbc.Execute(sql)
Set jsa = jsArray()
While Not (rs.EOF Or rs.BOF)
   Set jsa(Null) = jsObject()
   For Each col In rs.Fields
      jsa(Null)(col.Name) = col.Value
   Next

   sql2 = “SELECT subject,score FROM report WHERE s_id=”&rs(“s_id”)
   Set rs2 = dbc.Execute(sql2)

   Set jsa(Null)(“report”) = jsArray()
   While Not (rs2.EOF Or rs2.BOF)
      Set jsa(Null)(“report”)(Null) = jsObject()

      For Each col2 In rs2.Fields
          jsa(Null)(“report”)(Null)(col2.Name) = col2.Value
      Next

      rs2.MoveNext
   Wend

   rs.MoveNext
Wend