2017年7月16日 星期日

Android:使用Handler與Thread更新UI

在android中系统不允許在非Main Thread更新UI。當我們在非主線程做了耗時操作後,需要去更新UI的時候,我們就需要使用Handler來執行更新操作。

範例用一個線程,在線程裡不斷循環,線程每休眠1s,sendMessage給Handle,在handleMessage方法裡更新UI線程即TextView的内容,讓TextView不斷更新時間。

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.user.service.MainActivity"
    android:orientation="vertical">
  
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:id="@+id/txt"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</LinearLayout>

MainActivity.java:

public class MainActivity extends AppCompatActivity {

    private TextView txt;
    private static final int msgKey1 = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        txt = (TextView)findViewById(R.id.txt);

        Thread t = new Thread(runnable);
        t.start();
    }
    
    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            do{
                try {
                    Thread.sleep(1000);
                    Message msg = new Message();
                    msg.what = msgKey1;
                    mHandler.sendMessage(msg);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }while (true);
        }
    };
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case msgKey1:
                    long time = System.currentTimeMillis();
                    Date date = new Date(time);
                    SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒 EEE");
                    txt.setText(format.format(date));
                    break;
                default:
                    break;
            }
        }
    };
}

要不斷更新UI除了使用Handler外,還可以使用runOnUiThread()這個方法來更新UI

public class MainActivity extends AppCompatActivity {

    private TextView txt;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        txt = (TextView)findViewById(R.id.txt);

        Thread t = new Thread(runnable);
        t.start();


    }

    private Runnable runnable = new Runnable() {
    @Override
    public void run() {
        try {
            while (true) {
                Thread.sleep(1000);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        // update TextView here!
                        long time = System.currentTimeMillis();
                        Date date = new Date(time);
                        SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒 EEE");
                        txt.setText(format.format(date));
                    }
                });
            }
        } catch (InterruptedException e) {
        }
    }
    };

2017年7月15日 星期六

Android:MVC模式

MVC模式已經被廣泛使用在各種的程式(桌面程式、Web程式、行動程式等),其中M代表模型(Model也就是資料),V代表視圖(View,也就是介面),C代表控制器(Controller,控制M和V),MVC主要的目的就是使程式中的資料和介面分離,這樣當更換視圖或模型時可以不影響對方,更有利於程式的維護。

MVC模式基本的原理,就是透過Controller連接View和Model,也就是當View中的資料變化時(如Listview要刪除某個清單項),會通知Controller,而非直接通知Model。這時當Controller接收到View的通知後,會在Model中才取對應的動作(如刪除資料庫中的某筆記錄)。

在Android中使用MVC的元件非常多,如所有的清單元件(Listview、Spinner、GridView),其中V就代表這些元件,M則代表各種資料來源,C則代表Adapter類別。

Android:Activity的四種啟動模式

在多Activity開發中,有可能是自己應用之間的Activity跳轉,或者夾帶其他應用的可復用Activity。

可能會希望一個Activity跳轉到原來某個Activity實例,而不是產生大量重複的Activity。

這需要為Activity配置特定的啟動模式,而不是使用默認的啟動模式。


Activity有四種啟動模式:
  • standard(預設)
  • singleTop
  • singleTask
  • singleInstance
設置的位置在AndroidManifest.xml文件中的<activity>標籤裡設定android:launchMode屬性:

<activity android:name=".MainActivity"
          android:launchMode="standard">

或是透過Intent來設定
Intent intent = new Intent(this, B.class);   
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);   
startActivity(intent);  

通常當一個Activity1啟動時,會把該Activity1放入一個Task裡,如果當有Activity2被啟動時,也會被放入Activity1所在的Task,以堆疊的方式來存放Activity,然後當使用者按下返回健或程式執行finish()時,該Activity就會從Task裡移除

然而有的時候我們的需求可能是,當產生新的Activity時,把它上入新的Task裡;或者希望將已存在的Activtiy拿到Task頂部來使用,而不是產生一個新的。

下面介紹4種Activity的啟動模式:


1. standard模式:

    首先說standard模式,也就是默認模式,不需要配置launchMode。
這種模式下,如果重複創建同一個Activity實例,intent將發送給新的實例。即同一個Activity有多個


2.singleTop模式:

    singleTop和standard模式,都會將intent發送新的實例
如果Task的頂部已經存在了該Activity,那麼,它便不會重新創建,而是調用 onNewIntent。如果,該Activity存在,但不是在頂部,那麼該Activity依然要重新創建。


3.singleTask模式: 

    如果在Task已經有該Activity的實例,就重用該實例(會調用實例的onNewIntent())。重用時,會讓該實例回到Task頂部,因此在它上面的實例將會被移除棧。如果Task中不存在該實例,將會創建新的實例放入棧中。


4.singleInstance模式:

     加載該Activity时如果没有實例化,他會創建新的Task后,實例化Activity放入Task,如果已經存在,直接調用 onNewIntent,該Activity的Task中不允許啟動其它的Activity,任何從該Activity動的其他Activity都將被放到其他task中

  • 是否允許多個實例

「standard」和「singleTop」可以被實例化多次,並且是可以存在於不同的task中;這種實例化時一個task可以包括一個activity的多個實例;「singleTask」和」singleInstance」則限制只生成一個實例

  • 是否允許其它activity存在於本task內

「singleInstance」獨占一個task,其它activity不能存在那個task里,如果它啟動了一個新的activity,不管新的activity的launch mode 如何,新的activity都將會到別的task里運行,而另外三種模式,則可以和其它activity共存。

  • 是否每次都生成新實例

「standard」對於每一個啟動Intent都會生成一個activity的新實例;「singleTop」的activity如果在task的棧頂的話,則不生成新的該activity的實例,直接使用棧頂的實例,否則,生成該activity的實例;「singleTask」 如果在棧頂,則接受intent,否則,該intent會被丟棄,但是該task仍會回到前台。 當已經存在的activity實例處理新的intent時候,會調用onNewIntent方法;「singleInstance」是其所在棧的唯一activity,它會每次都被重用。

2017年7月13日 星期四

Android:SQLite資料庫

SQLite是一套開放原始碼的資料函式庫,使用標準的SQL語法,提供無須連線的資料庫的管理系統。

二大優點:

(1) 小而美,該資料庫系統非常小(大約 275k 大小空間),屬於輕量級的資料庫系統,主要是由 C 寫

成,支援大部份標準 SQL 語法,。

(2) 省成本:創建者不僅將其原始碼公開,還無須支付任何費用

SQLite 與其他 Server 等級的資料庫最大的差異點在於,SQLite 直接將資料庫的資料儲存在本機

端,而非 Server 端,而且一個資料庫即儲存成一個檔案。




建立 SQLite 資料庫,必須建立子類別繼承 SQLiteOperHelper 並覆寫 onCreate() 與 onUpgrade(),尤以改寫onCreate() 為重要。

程式碼:

activity_main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context="tw.com.hjchen.sqlitedemo.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Name"
            android:textSize="20sp"/>
        <EditText
            android:id="@+id/editName"
            android:layout_width="wrap_content"
            android:layout_height="40dp"
            android:width="230dp"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Tel"
            android:textSize="20sp"/>
        <EditText
            android:id="@+id/editTel"
            android:layout_width="wrap_content"
            android:layout_height="40dp"
            android:width="230dp"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Email"
            android:textSize="20sp"/>
        <EditText
            android:id="@+id/editEmail"
            android:layout_width="wrap_content"
            android:layout_height="40dp"
            android:width="230dp"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ID"
            android:textSize="20sp"/>
        <EditText
            android:id="@+id/editID"
            android:layout_width="wrap_content"
            android:layout_height="40dp"
            android:width="230dp"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal">
        <Button
            android:id="@+id/btnAdd"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Add"
            android:width="100dp"/>
        <Button
            android:id="@+id/btnUpdate"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Update"
            android:width="100dp"/>
        <Button
            android:id="@+id/btnDel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Del"
            android:width="100dp"/>
    </LinearLayout>

    <ScrollView
        android:id="@+id/scrollView1"
        android:layout_width="match_parent"
        android:layout_height="120dp">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                android:id="@+id/txtResult"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Result"
                android:textSize="20sp"/>
        </LinearLayout>
    </ScrollView>

    <ListView
        android:id="@+id/listData"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></ListView>
</LinearLayout>

data_item.xml:(listview裡的item)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/txtId"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"/>
    <TextView
        android:id="@+id/txtName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"/>
    <TextView
        android:id="@+id/txtTel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"/>
    <TextView
        android:id="@+id/txtEmail"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"/>

</LinearLayout>

DbConstants.java:

import  android.provider.BaseColumns;

public interface DbConstants extends BaseColumns{
    public static final String TABLE_NAME="friends";
    public static final String NAME="name";
    public static final String TEL="tel";
    public static final String EMAIL="email";
}

DBHelper.java:

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import static android.provider.BaseColumns._ID;
import static tw.com.hjchen.sqlitedemo.DbConstants.TABLE_NAME;
import static tw.com.hjchen.sqlitedemo.DbConstants.NAME;
import static tw.com.hjchen.sqlitedemo.DbConstants.TEL;
import static tw.com.hjchen.sqlitedemo.DbConstants.EMAIL;

public class DBHelper extends SQLiteOpenHelper{
    private final static String DATABASE_NAME="demo.db";  //資料庫檔案名稱
    private final static int DATABASE_VERSION=1;   //資料庫版本
    public DBHelper(Context context){
        super(context,DATABASE_NAME,null,DATABASE_VERSION);
    }
    public void onCreate(SQLiteDatabase db){
        final String INIT_TABLE="create table "+TABLE_NAME+"("+_ID+" integer primary key autoincrement,"+NAME+" char,"+TEL+" char,"+EMAIL+" char)";
        db.execSQL(INIT_TABLE);
    }
    public void onUpgrade(SQLiteDatabase db,int oldVersion,int newVersion){
        final String DROP_TABLE="drop tabel if exists "+TABLE_NAME;
        db.execSQL(DROP_TABLE);
        onCreate(db);
    }
}

MainActivity.java:

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import static android.provider.BaseColumns._ID;
import static tw.com.hjchen.sqlitedemo.DbConstants.TABLE_NAME;
import static tw.com.hjchen.sqlitedemo.DbConstants.NAME;
import static tw.com.hjchen.sqlitedemo.DbConstants.TEL;
import static tw.com.hjchen.sqlitedemo.DbConstants.EMAIL;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.*;
public class MainActivity extends AppCompatActivity implements OnClickListener{

    private DBHelper dbHelper;
    private TextView result;
    private ListView listData;
    private EditText editName,editTel,editEmail,editId;
    private Button btnAdd,btnDel,btnUpdatel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViews();
        openDatabase();   //開啟資料庫
        show();          //show scrollview
        showInList();    //show listview

    }
    private void findViews(){
        editName=(EditText)findViewById(R.id.editName);
        editTel=(EditText)findViewById(R.id.editTel);
        editEmail=(EditText)findViewById(R.id.editEmail);
        editId=(EditText)findViewById(R.id.editID);
        btnAdd=(Button)findViewById(R.id.btnAdd);
        btnDel=(Button)findViewById(R.id.btnDel);
        btnUpdatel=(Button)findViewById(R.id.btnUpdate);
        result=(TextView)findViewById(R.id.txtResult);
        listData=(ListView)findViewById(R.id.listData);
        btnAdd.setOnClickListener(this);
        btnDel.setOnClickListener(this);
        btnUpdatel.setOnClickListener(this);
    }
    protected void onDestroy(){
        super.onDestroy();
        closeDatabase();     //關閉資料庫
    }
    private void openDatabase(){
        dbHelper=new DBHelper(this);   //取得DBHelper物件

    }
    private void closeDatabase(){
        dbHelper.close();
    }
    public void onClick(View view){
        switch (view.getId()){
            case R.id.btnAdd:
                add();
                break;
            case R.id.btnDel:
                del();
                break;
            case R.id.btnUpdate:
                update();
                break;
            default:
                break;
        }
        show();
        showInList();
    }
    private void add(){
        SQLiteDatabase db=dbHelper.getWritableDatabase();  //透過dbHelper取得讀取資料庫的SQLiteDatabase物件,可用在新增、修改與刪除
        ContentValues values=new ContentValues();  //建立 ContentValues 物件並呼叫 put(key,value) 儲存欲新增的資料,key 為欄位名稱  value 為對應值。
        values.put(NAME,editName.getText().toString());
        values.put(TEL,editTel.getText().toString());
        values.put(EMAIL,editEmail.getText().toString());
        db.insert(TABLE_NAME,null,values);
        cleanEditText();
    }
    private  void del(){
        SQLiteDatabase db=dbHelper.getWritableDatabase();
        String id=editId.getText().toString();
        db.delete(TABLE_NAME,_ID+"="+id,null);
        cleanEditText();
    }
    private void update(){
        SQLiteDatabase db=dbHelper.getWritableDatabase();
        String id=editId.getText().toString();
        ContentValues values=new ContentValues();
        values.put(NAME,editName.getText().toString());
        values.put(TEL,editTel.getText().toString());
        values.put(EMAIL,editEmail.getText().toString());
        db.update(TABLE_NAME,values,_ID+"="+id,null);
        cleanEditText();
    }
    private void cleanEditText(){
        editName.setText("");
        editTel.setText("");
        editEmail.setText("");
        editId.setText("");
    }

    private Cursor getCursor(){
        SQLiteDatabase db=dbHelper.getReadableDatabase();  //透過dbHelper取得讀取資料庫的SQLiteDatabase物件,可用在查詢
        String[] columns={_ID,NAME,TEL,EMAIL};
        Cursor cursor = db.query(TABLE_NAME,columns,null,null,null,null,null);  //查詢所有欄位的資料
        return cursor;
    }

    private void show(){
        Cursor cursor = getCursor();  //取得查詢物件Cursor
        StringBuilder resultData = new StringBuilder("Result:\n");
        while (cursor.moveToNext()){
            int id = cursor.getInt(0);
            String name = cursor.getString(1);
            String tel = cursor.getString(2);
            String email = cursor.getString(3);
            resultData.append(id).append(": ");
            resultData.append(name).append(": ");
            resultData.append(tel).append(": ");
            resultData.append(email).append("\n");
        }
        result.setText(resultData);
    }
    private void showInList(){
        Cursor cursor = getCursor();
        String[] from = {_ID,NAME,TEL,EMAIL};
        int[] to = {R.id.txtId,R.id.txtName,R.id.txtTel,R.id.txtEmail};
        SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,R.layout.data_item,cursor,from,to); //SimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to)
        listData.setAdapter(adapter);
    }

}

PS:這裡特別說明一下SQLite的查詢功能,SQLiteDatabase類別提供二種查詢功能方法:rawQuery() 與 query()

rawQuery():接受 SQL 查詢語法,可直接將 SQL 查詢語法傳入即可。

query():必須先將 SQL 查詢語法依照 query()的參數來切割。


例:SQL 查詢語法為:Select name,phone from member where name like ?


  •  rawQuery(String sql, String[] selectionArgs)


查詢語法為:

String sql = " Select name,phone from member where name like ? "

String[] selectionArgs = {"%明%"};

Cursor cursor = db.rawQuery(sql,selectionArgs);

  •  query(String table, String[] columns, String selection, String[] selectionArgs,String groupBy,String having, String orderBy);


查詢語法為:

String[] columns = {"name","phone"};

String selection = "name Like ? ";

String[] selectionArgs = {"%明%"};

Cursor cursor = db.query(TABLE_NAME , columns, selection, selectionArgs, null, null, null);

2017年7月9日 星期日

Android:使用BaseAdapter客製化ListView中的Item

在Android裡,ListView可以說是非常常用的一個元件,要使用ListView、Spinner、GridView這類清單元件,就必須使用Adapter,如果清單資料不需要很複雜時,可以使用ArrayAdapter和SimpleAdapter,但是如果清單元件如果有客製化需求時,就必須使用BaseAdapter。


例如:此篇範例想要做出listView中的每一列的最前面都有圖片,並且偶數列給它顏色


BaseAdapter的特性就是可以重用View來節省資源,滑出去的View會被從新拿來使用,這樣就不必使用太多的資源了。
要使用BaseAdapter就必須讓一個類別來繼承它,並且實做四個方法,分別為:getCount()、getItem()、getItemId()和getView()。

  • getCount():會回傳View中的個數。

  • getItem(int postition):會回傳position所對應的資料。

  • getItemId(int position):會回傳position所對影的id值,可供辨識。

  • getView(int position , View convertView , ViewGroup parent):當欲展示一個項目給使用者時,會自動呼叫此方法,會把實體化過的view傳回去給listview。


程式碼:

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.user.baseadapter.MainActivity">

    <ListView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/listview"></ListView>
    
</RelativeLayout>

list_row.xml:listview中每一列要顯示的元件的布局檔(一張圖片和文字)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
    <ImageView
        android:layout_width="10dp"
        android:layout_height="20dp"
        android:id="@+id/image"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/textview"
        android:paddingLeft="30dp"/>

</LinearLayout>

Encapsulation.java:

public class ListName {
    private String name;

    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return this.name;
    }
}

BaseAdapter.java:

public class BaseAdapter extends android.widget.BaseAdapter{

        private ArrayList<ListName> listData;
        private LayoutInflater layoutInflater;
        public BaseAdapter(Context context, ArrayList<ListName> listData) {
            this.listData = listData;
            layoutInflater = LayoutInflater.from(context);  //獲得LayoutInflater
        }
        @Override
        public int getCount() {
            return listData.size();
        }
        @Override
        public Object getItem(int position) {
            return listData.get(position);
        }
        @Override
        public long getItemId(int position) {
            return position;
        }
        public View getView(int position, View convertView, ViewGroup parent) {

            ViewHolder holder;

            if (convertView == null) {  //判斷是否被使用過,如果沒有就初始化它
                convertView = layoutInflater.inflate(R.layout.list_row, null); //LayoutInflater.inflate來載入xml文件布局檔
                holder = new ViewHolder();  //實體化holder
                holder.image = (ImageView)convertView.findViewById(R.id.image);
                holder.textview = (TextView) convertView.findViewById(R.id.textview);

                convertView.setTag(holder);  //把convertView塞入holder裡,下次convertView不是空的條件下 就可以直接把holder拿出來用
            } else {
                holder = (ViewHolder) convertView.getTag();
            }

            ListName encapsulation = listData.get(position);

            holder.image.setBackgroundColor(Color.parseColor("#f6565a"));  //給image設定顏色
//            convertView.setBackgroundColor(Color.WHITE);

            if (position%2==0){   //判斷是不是偶數列,是的話就把那列設定顏色
                convertView.setBackgroundColor(Color.parseColor("#0ce8e2"));
            }

            holder.textview.setText(encapsulation.getName());
            return convertView;
        }
        static class ViewHolder {  //用來裝在listview裡每一列要顯示的元件
            TextView textview;
            ImageView image;

        }

}

MainActivity.java:

public class MainActivity extends AppCompatActivity {

    private ListView listview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        String[] s={"Jack","Mary","William","Micheal","Tom","Watson","Alice","Anna","Eve","Jane","Amanda","Laura","Diana","Lydia","Ivy","Vanessa","Ashley","Jennifer","Stephanie","Bella","Catherine","Eileen"};

        listview = (ListView)findViewById(R.id.listview);
        ArrayList<ListName> list = new ArrayList<>();

        for(int i=0;i<s.length;i++){
            ListName encapsulation = new ListName();
            encapsulation.setName(s[i]);
            list.add(encapsulation);
        }
        listview.setAdapter(new BaseAdapter(this,list));


    }
}

2017年7月6日 星期四

Android:使用Socket的簡單聊天室範例

之前有在Java裡寫了Server-Client介紹,這篇就來使用Android來寫一個簡單的對話App,使用Socket和ServerSocket來連結Server和Client端,並且透過I/O傳遞資料

Client端:

 Server端:





Client端:
activity_main.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.and_sv.MainActivity" >

    <EditText
        android:id="@+id/edtname"
        android:layout_width="100dp"
        android:layout_height="wrap_content" >
        
    </EditText>

    <EditText
        android:id="@+id/edttext"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/edtname"
        android:layout_marginLeft="20dp"
        android:layout_toRightOf="@+id/edtname"
        android:ems="10" >
    </EditText>

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignRight="@+id/edttext"
        android:layout_below="@+id/edttext"
        android:text="送出" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/edtname"
        android:layout_below="@+id/button1" />

</RelativeLayout>

MainActivity.java:

public class MainActivity extends Activity {
 private EditText edtname,edttext;
 private TextView textview1;
 private Button button1;
 String tmp;                // 暫存文字訊息
    Socket clientSocket;
    
    public static Handler mHandler = new Handler();
    
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  edtname = (EditText)findViewById(R.id.edtname);
  edttext = (EditText)findViewById(R.id.edttext);
  button1 = (Button)findViewById(R.id.button1);
  textview1 = (TextView)findViewById(R.id.textView1);
  button1.setOnClickListener(btnlistener);
  
        Thread t = new Thread(readData);
         // 啟動執行緒
         t.start();

 }
 private OnClickListener btnlistener = new Button.OnClickListener(){

  @Override
  public void onClick(View v) {
   // TODO Auto-generated method stub
   if(clientSocket.isConnected()){
     
                BufferedWriter bw;

                try {
                    // 取得網路輸出串流
                    bw = new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream()));

                    // 寫入訊息
                    bw.write(edtname.getText()+":"+edttext.getText()+"\n");

                    // 立即發送
                    bw.flush();
                } catch (IOException e) {

                }
                // 將文字方塊清空
                edttext.setText("");
            }    
  }
  
 };
  
     // 顯示更新訊息
     private Runnable updateText = new Runnable() {
         public void run() {
             // 加入新訊息並換行
             textview1.append(tmp + "\n");
         }
     };
  
     // 取得網路資料
     private Runnable readData = new Runnable() {
         public void run() {
             // server端的IP
             InetAddress serverIp;
             try {
                 // 以內定(本機電腦端)IP為Server端
                 serverIp = InetAddress.getByName("10.0.2.2");
                 int serverPort = 5050;
                 clientSocket = new Socket(serverIp, serverPort);
  
                 // 取得網路輸入串流
                 BufferedReader br = new BufferedReader(new InputStreamReader(
                         clientSocket.getInputStream()));
  
                 // 當連線後
                 while (clientSocket.isConnected()) {
                     // 取得網路訊息
                     tmp = br.readLine();
  
                     // 如果不是空訊息則
                     if(tmp!=null)
                         // 顯示新的訊息
                         mHandler.post(updateText);
                 }
  
             } catch (IOException e) {
  
             }
         }
     };
  
 }

Server端:

public class Server {
 
 private static int serverport = 5050;
    private static ServerSocket serverSocket;
    private static int count=0;
 
    // 用串列來儲存每個client
    private static ArrayList players=new ArrayList();
 
    // 程式進入點
    public static void main(String[] args) {
     
        try {
            serverSocket = new ServerSocket(serverport);
            System.out.println("Server is start.");
            // 顯示等待客戶端連接
            System.out.println("Waiting for clinet connect");
            // 當Server運作中時
            while (!serverSocket.isClosed()) {
                
                // 呼叫等待接受客戶端連接
                waitNewPlayer();
            }
 
        } catch (IOException e) {
            System.out.println("Server Socket ERROR");
        }
 
    }
 
    // 等待接受客戶端連接
    public static void waitNewPlayer() {
        try {
            Socket socket = serverSocket.accept();
            ++count;
            System.out.println("現在使用者個數:"+count);
 
            // 呼叫創造新的使用者
            createNewPlayer(socket);
            
        } catch (IOException e) {
 
        }
 
    }
 
    // 創造新的使用者
    public static void createNewPlayer(final Socket socket) throws IOException {
     
     
        // 以新的執行緒來執行
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 增加新的使用者
                    players.add(socket);
                    // 取得網路串流 
                    BufferedReader br = new BufferedReader(
                            new InputStreamReader(socket.getInputStream()));
                    // 當Socket已連接時連續執行
                    while (socket.isConnected()) {
                        // 取得網路串流的訊息
                        String msg= br.readLine();
                        
                        if(msg==null){
                         System.out.println("Client Disconnected!");
                         break;
                        }
 
                        //輸出訊息 
                        System.out.println(msg);
                        
                        // 廣播訊息給其它的客戶端
                        castMsg(msg);

                    }
                    
 
                } catch (IOException e) {
                 e.getStackTrace();
                }
                finally{
                 // 移除客戶端
              players.remove(socket);
              --count;
              System.out.println("現在使用者個數:"+count);
                }
             
            }
        });
 
        // 啟動執行緒
        t.start();
    }
 
    // 廣播訊息給其它的客戶端
    public static void castMsg(String Msg){
        // 創造socket陣列
        Socket[] ps=new Socket[players.size()]; 
 
        // 將players轉換成陣列存入ps
        players.toArray(ps);
 
        // 走訪ps中的每一個元素
        for (Socket socket : ps ) {
            try {
                // 創造網路輸出串流
                BufferedWriter bw;
                bw = new BufferedWriter( new OutputStreamWriter(socket.getOutputStream()));
 
                // 寫入訊息到串流
                bw.write(Msg+"\n");
 
                // 立即發送
                bw.flush();
            } catch (IOException e) {
 
            }
        }
    }

}

PS:因為要持續的更新Textview所以必須使用new Handler().post(runnable)
在Android裡只有(Main Thread或UI Thread)主線程負責UI的操作或事件,子線程不可以插手有關UI的事,但是可以透過子線程去處理複雜的事情,再透過Handler去通知主線程更新UI

2017年7月5日 星期三

Java:Socket連線Server-Client

在Client-Server架構裡,不同的伺服器程式會使用不同的埠號,同時在伺服器裡執行,當Client端發出請求後,Server端的程式會建立一個Socket物件,並透過這個物件與Client端連線,以便進行資料的傳遞,Socket可以說是程式與網路之間的一種介面,要把資訊傳送到網路,就必須透過Socket的幫忙。

Java裡提供了ServerSocket與Socket類別,可將Client端的電腦連上伺服器,並可從伺服器傳遞資料給Client端。

Server端:

public class Server{

  
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  try {
   ServerSocket svs = new ServerSocket(5050);  //建立ServerSocket物件,並設定埠號5050
   System.out.println("waiting for client");   
   Socket sk = svs.accept();                   //Client提出請求,accept()會傳回一個Socket物件,並讓sk指向它
   System.out.println("Clinet connected");
   while(sk.isConnected()){                    //當Socket持續在連接時,就做下面的事
    BufferedReader br = new BufferedReader(new InputStreamReader(sk.getInputStream()));
    String msg=br.readLine();
    if(msg==null){                      //當Client disconnect時,readline會傳回null
     break;
    }
    System.out.println(msg);
    
   }
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
 }

}

Client端:

public class Client {


 public static void main(String[] args) {
  // TODO Auto-generated method stub

  BufferedWriter bw;
  try {
   Socket sk = new Socket("127.0.0.1",5050);    //建立Socket物件,並設定ip(127.0.0.1是自己的主機)和埠號
   System.out.println("已連線Server");
   bw = new BufferedWriter(new OutputStreamWriter(sk.getOutputStream()));   //利用sk來取得輸出串流
   while(sk.isConnected()){
    BufferedReader  br=new BufferedReader(new InputStreamReader(System.in));  //透過輸入串流來取得Client的輸入
    System.out.print("client:");            
    bw.write("Client:"+br.readLine()+"\n");   //寫入輸出串流
    bw.flush();                               //立即傳送
   }
   
  }catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }

}

PS:因為Server是在自己電腦執行,所以IP就是127.0.0.1
埠號使用者可以設定的只有1024~65535,0~1023為系統所保留

Server端











Client端