Game2.tw提供最新手遊遊戲攻略,找攻略,就上Game2.tw

首頁 > Android > Android教程 >

安卓開發:Android創建和使用數據庫詳細指南

編輯:game2.tw
分享到:

  數據庫支持每個應用程序無論大小的生命線,除非你的應用程序隻處理簡單的數據,那麼就需要一個數據庫系統存儲你的結構化數據,Android使用SQLite數據庫,它是一個開源的、支持多操作系統的SQL數據庫,在許多領域廣泛使用,如Mozilla FireFox就是使用SQLite來存儲配置數據的,iPhone也是使用SQLite來存儲數據的。

  在Android中,你為某個應用程序創建的數據庫,隻有它可以訪問,其它應用程序是不能訪問的,數據庫位於Android設備/data/data/ /databases文件夾中,在這篇文章中,你將會學習到如何在Android中創建和使用數據庫。

  SQLite數據庫

  使用Eclipse創建一個Android項目,取名為Database,如圖1所示:

安卓開發:Android創建和使用數據庫詳細指南

圖1 數據庫-使用Eclipse創建你的Android新項目

    創建DBAdapter輔助類

  操作數據庫的最佳實踐是創建一個輔助類,由它封裝所有對數據庫的復雜訪問,對於調用代碼而言它是透明的,因此我創建瞭一個DBAdapter的輔助類,由它創建、打開、關閉和使用SQLite數據庫。

  首先,在src/ 文件夾(在這個例子中是src/net.learn2develop.Database)下添加一個DBAdapter.java文件。

  在DBAdapter.java文件中,導入所有你要使用到的命名空間:

package net.learn2develop.Databases;

  

  import android.content.ContentValues;

  import android.content.Context;

  import android.database.Cursor;

  import android.database.SQLException;

  import android.database.sqlite.SQLiteDatabase;

  import android.database.sqlite.SQLiteOpenHelper;

  import android.util.Log;

  public class DBAdapter

  {

  }

  接下來創建一個數據庫,取名為bookstitles,字段如圖2所示。

安卓開發:Android創建和使用數據庫詳細指南

圖2 數據庫字段

  在DBAdapter.java文件中,定義清單1中的常量。

  清單1 定義DBAdapter.java文件中的常量

  package net.learn2develop.Database;

  import android.content.ContentValues;

  import android.content.Context;

  import android.database.Cursor;

  import android.database.SQLException;

  import android.database.sqlite.SQLiteDatabase;

  import android.database.sqlite.SQLiteOpenHelper;

  import android.util.Log;

  public class DBAdapter

  {

  public static final String KEY_ROWID = _id;

  public static final String KEY_ISBN = isbn;

  public static final String KEY_TITLE = title;

  public static final String KEY_PUBLISHER = publisher;

  private static final String TAG = DBAdapter;

  private static final String DATABASE_NAME = books;

  private static final String DATABASE_TABLE = titles;

  private static final int DATABASE_VERSION = 1;

  private static final String DATABASE_CREATE =

  create table titles (_id integer primary key autoincrement,

  + isbn text not null, title text not null,

  + publisher text not null);;

  private final Context context;

  }

  DATABASE_CREATE常量包括創建titles表的SQL語句。

  在DBAdapter類中,你可以擴展SQLiteOpenHelper類,它是一個Android輔助類,主要用於數據庫創建和版本管理。實際上,你可以覆蓋onCreate()和onUpgrade()方法,如清單2所示。

  清單2 在DBAdapter類中,擴展SQLiteOpenHelper類覆蓋onCreate() 和 onUpgrade()方法

  package net.learn2develop.Database;

  import android.content.ContentValues;

  import android.content.Context;

  import android.database.Cursor;

  import android.database.SQLException;

  import android.database.sqlite.SQLiteDatabase;

  import android.database.sqlite.SQLiteOpenHelper;

  import android.util.Log;

  public class DBAdapter

  {

  public static final String KEY_ROWID = _id;

  public static final String KEY_ISBN = isbn;

  public static final String KEY_TITLE = title;

  public static final String KEY_PUBLISHER = publisher;

  private static final String TAG = DBAdapter;

  private static final String DATABASE_NAME = books;

  private static final String DATABASE_TABLE = titles;

  private static final int DATABASE_VERSION = 1;

  private static final String DATABASE_CREATE =

  create table titles (_id integer primary key autoincrement,

  + isbn text not null, title text not null,

  + publisher text not null);;

  private final Context context;

  private DatabaseHelper DBHelper;

  private SQLiteDatabase db;

  public DBAdapter(Context ctx)

  {

  this.context = ctx;

  DBHelper = new DatabaseHelper(context);

  }

  private static class DatabaseHelper extends SQLiteOpenHelper

  {

  DatabaseHelper(Context context)

  {

  super(context, DATABASE_NAME, null, DATABASE_VERSION);

  }

  @Override

  public void onCreate(SQLiteDatabase db)

  {

  db.execSQL(DATABASE_CREATE);

  }

  @Override

  public void onUpgrade(SQLiteDatabase db, int oldVersion,

  int newVersion)

  {

  Log.w(TAG, Upgrading database from version  + oldVersion

  +  to

  + newVersion + , which will destroy all old data);

  db.execSQL("DROP TABLE IF EXISTS titles");

  onCreate(db);

  }

  }

  }

  onCreate()方法創建一個新的數據庫,onUpgrade()方法用於升級數據庫,這可以通過檢查DATABASE_VERSION常量定義的值來實現,對於onUpgrade()方法而言,隻不過是簡單地刪除表,然後在創建表而已。

  現在你可以定義不同的方法來打開和關閉數據庫,如清單3中的添加/編輯/刪除/行的函數。

  清單3 定義打開和關閉數據庫以及增加/編輯/刪除表中行的方法

  public class DBAdapter

  {

  //...

  //...

  //---打開數據庫---

  public DBAdapter open() throws SQLException

  {

  db = DBHelper.getWritableDatabase();

  return this;

  }

  //---關閉數據庫---

  public void close()

  {

  DBHelper.close();

  }

  //---向數據庫插入一個標題---

  public long insertTitle(String isbn, String title, String publisher)

  {

  ContentValues initialValues = new ContentValues();

  initialValues.put(KEY_ISBN, isbn);

  initialValues.put(KEY_TITLE, title);

  initialValues.put(KEY_PUBLISHER, publisher);

  return db.insert(DATABASE_TABLE, null, initialValues);

  }

  //---刪除一個指定的標題---

  public boolean deleteTitle(long rowId)

  {

  return db.delete(DATABASE_TABLE, KEY_ROWID + = + rowId, null) > 0;

  }

  //---檢索所有標題---

  public Cursor getAllTitles()

  {

  return db.query(DATABASE_TABLE, new String[] {

  KEY_ROWID,

  KEY_ISBN,

  KEY_TITLE,

  KEY_PUBLISHER},

  null,

  null,

  null,

  null,

  null);

  }

  //---檢索一個指定的標題---

  public Cursor getTitle(long rowId) throws SQLException

  {

  Cursor mCursor =

  db.query(true, DATABASE_TABLE, new String[] {

  KEY_ROWID,

  KEY_ISBN,

  KEY_TITLE,

  KEY_PUBLISHER

  },

  KEY_ROWID + = + rowId,

  null,

  null,

  null,

  null,

  null);

  if (mCursor != null) {

  mCursor.moveToFirst();

  }

  return mCursor;

  }

  //---更新一個標題---

  public boolean updateTitle(long rowId, String isbn,

  String title, String publisher)

  {

  ContentValues args = new ContentValues();

  args.put(KEY_ISBN, isbn);

  args.put(KEY_TITLE, title);

  args.put(KEY_PUBLISHER, publisher);

  return db.update(DATABASE_TABLE, args,

  KEY_ROWID + = + rowId, null) > 0;

  }

  }Database;

  import android.content.ContentValues;

  import android.content.Context;

  import android.database.Cursor;

  import android.database.SQLException;

  import android.database.sqlite.SQLiteDatabase;

  import android.database.sqlite.SQLiteOpenHelper;

  import android.util.Log;

  public class DBAdapter

  {

  public static final String KEY_ROWID = _id;

  public static final String KEY_ISBN = isbn;

  public static final String KEY_TITLE = title;

  public static final String KEY_PUBLISHER = publisher;

  private static final String TAG = DBAdapter;

  private static final String DATABASE_NAME = books;

  private static final String DATABASE_TABLE = titles;

  private static final int DATABASE_VERSION = 1;

  private static final String DATABASE_CREATE =

  create table titles (_id integer primary key autoincrement,

  + isbn text not null, title text not null,

  + publisher text not null);;

  private final Context context;

  private DatabaseHelper DBHelper;

  private SQLiteDatabase db;

  public DBAdapter(Context ctx)

  {

  this.context = ctx;

  DBHelper = new DatabaseHelper(context);

  }

  private static class DatabaseHelper extends SQLiteOpenHelper

  {

  DatabaseHelper(Context context)

  {

  super(context, DATABASE_NAME, null, DATABASE_VERSION);

  }

  @Override

  public void onCreate(SQLiteDatabase db)

  {

  db.execSQL(DATABASE_CREATE);

  }

  @Override

  public void onUpgrade(SQLiteDatabase db, int oldVersion,

  int newVersion)

  {

  Log.w(TAG, Upgrading database from version  + oldVersion

  +  to

  + newVersion + , which will destroy all old data);

  db.execSQL("DROP TABLE IF EXISTS titles");

  onCreate(db);

  }

  }

  //---打開數據庫---

  public DBAdapter open() throws SQLException

  {

  db = DBHelper.getWritableDatabase();

  return this;

  }

  //---關閉數據庫---

  public void close()

  {

  DBHelper.close();

  }

  //---向數據庫中插入一個標題---

  public long insertTitle(String isbn, String title, String publisher)

  {

  ContentValues initialValues = new ContentValues();

  initialValues.put(KEY_ISBN, isbn);

  initialValues.put(KEY_TITLE, title);

  initialValues.put(KEY_PUBLISHER, publisher);

  return db.insert(DATABASE_TABLE, null, initialValues);

  }

  //---刪除一個指定標題---

  public boolean deleteTitle(long rowId)

  {

  return db.delete(DATABASE_TABLE, KEY_ROWID +

  = + rowId, null) > 0;

  }

  //---檢索所有標題---

  public Cursor getAllTitles()

  {

  return db.query(DATABASE_TABLE, new String[] {

  KEY_ROWID,

  KEY_ISBN,

  KEY_TITLE,

  KEY_PUBLISHER},

  null,

  null,

  null,

  null,

  null);

  }

  //---檢索一個指定標題---

  public Cursor getTitle(long rowId) throws SQLException

  {

  Cursor mCursor =

  db.query(true, DATABASE_TABLE, new String[] {

  KEY_ROWID,

  KEY_ISBN,

  KEY_TITLE,

  KEY_PUBLISHER

  },

  KEY_ROWID + = + rowId,

  null,

  null,

  null,

  null,

  null);

  if (mCursor != null) {

  mCursor.moveToFirst();

  }

  return mCursor;

  }

  //---更新一個標題---

  public boolean updateTitle(long rowId, String isbn,

  String title, String publisher)

  {

  ContentValues args = new ContentValues();

  args.put(KEY_ISBN, isbn);

  args.put(KEY_TITLE, title);

  args.put(KEY_PUBLISHER, publisher);

  return db.update(DATABASE_TABLE, args,

  KEY_ROWID + = + rowId, null) > 0;

  }

  }

  註意Android使用Cursor類返回一個需要的值,Cursor作為一個指針從數據庫查詢返回結果集,使用Cursor允許Android更有效地管理它們需要的行和列,你使用ContentValues對象存儲鍵/值對,它的put()方法允許你插入不同數據類型的鍵值。

  清單4顯示瞭完整的DBAdapter.java源代碼。

  清單4 DBAdapter.java完整源代碼

  package net.learn2develop.

  數據庫支持每個應用程序無論大小的生命線,除非你的應用程序隻處理簡單的數據,那麼就需要一個數據庫系統存儲你的結構化數據,Android使用SQLite數據庫,它是一個開源的、支持多操作系統的SQL數據庫,在許多領域廣泛使用,如Mozilla FireFox就是使用SQLite來存儲配置數據的,iPhone也是使用SQLite來存儲數據的。

  在Android中,你為某個應用程序創建的數據庫,隻有它可以訪問,其它應用程序是不能訪問的,數據庫位於Android設備/data/data/ /databases文件夾中,在這篇文章中,你將會學習到如何在Android中創建和使用數據庫。

  SQLite數據庫

  使用Eclipse創建一個Android項目,取名為Database,如圖1所示:

安卓開發:Android創建和使用數據庫詳細指南

圖1 數據庫-使用Eclipse創建你的Android新項目

    創建DBAdapter輔助類

  操作數據庫的最佳實踐是創建一個輔助類,由它封裝所有對數據庫的復雜訪問,對於調用代碼而言它是透明的,因此我創建瞭一個DBAdapter的輔助類,由它創建、打開、關閉和使用SQLite數據庫。

  首先,在src/ 文件夾(在這個例子中是src/net.learn2develop.Database)下添加一個DBAdapter.java文件。

  在DBAdapter.java文件中,導入所有你要使用到的命名空間:

package net.learn2develop.Databases;

  

  import android.content.ContentValues;

  import android.content.Context;

  import android.database.Cursor;

  import android.database.SQLException;

  import android.database.sqlite.SQLiteDatabase;

  import android.database.sqlite.SQLiteOpenHelper;

  import android.util.Log;

  public class DBAdapter

  {

  }

  接下來創建一個數據庫,取名為bookstitles,字段如圖2所示。

安卓開發:Android創建和使用數據庫詳細指南

圖2 數據庫字段

  在DBAdapter.java文件中,定義清單1中的常量。

  清單1 定義DBAdapter.java文件中的常量

  package net.learn2develop.Database;

  import android.content.ContentValues;

  import android.content.Context;

  import android.database.Cursor;

  import android.database.SQLException;

  import android.database.sqlite.SQLiteDatabase;

  import android.database.sqlite.SQLiteOpenHelper;

  import android.util.Log;

  public class DBAdapter

  {

  public static final String KEY_ROWID = _id;

  public static final String KEY_ISBN = isbn;

  public static final String KEY_TITLE = title;

  public static final String KEY_PUBLISHER = publisher;

  private static final String TAG = DBAdapter;

  private static final String DATABASE_NAME = books;

  private static final String DATABASE_TABLE = titles;

  private static final int DATABASE_VERSION = 1;

  private static final String DATABASE_CREATE =

  create table titles (_id integer primary key autoincrement,

  + isbn text not null, title text not null,

  + publisher text not null);;

  private final Context context;

  }

  DATABASE_CREATE常量包括創建titles表的SQL語句。

  在DBAdapter類中,你可以擴展SQLiteOpenHelper類,它是一個Android輔助類,主要用於數據庫創建和版本管理。實際上,你可以覆蓋onCreate()和onUpgrade()方法,如清單2所示。

  清單2 在DBAdapter類中,擴展SQLiteOpenHelper類覆蓋onCreate() 和 onUpgrade()方法

  package net.learn2develop.Database;

  import android.content.ContentValues;

  import android.content.Context;

  import android.database.Cursor;

  import android.database.SQLException;

  import android.database.sqlite.SQLiteDatabase;

  import android.database.sqlite.SQLiteOpenHelper;

  import android.util.Log;

  public class DBAdapter

  {

  public static final String KEY_ROWID = _id;

  public static final String KEY_ISBN = isbn;

  public static final String KEY_TITLE = title;

  public static final String KEY_PUBLISHER = publisher;

  private static final String TAG = DBAdapter;

  private static final String DATABASE_NAME = books;

  private static final String DATABASE_TABLE = titles;

  private static final int DATABASE_VERSION = 1;

  private static final String DATABASE_CREATE =

  create table titles (_id integer primary key autoincrement,

  + isbn text not null, title text not null,

  + publisher text not null);;

  private final Context context;

  private DatabaseHelper DBHelper;

  private SQLiteDatabase db;

  public DBAdapter(Context ctx)

  {

  this.context = ctx;

  DBHelper = new DatabaseHelper(context);

  }

  private static class DatabaseHelper extends SQLiteOpenHelper

  {

  DatabaseHelper(Context context)

  {

  super(context, DATABASE_NAME, null, DATABASE_VERSION);

  }

  @Override

  public void onCreate(SQLiteDatabase db)

  {

  db.execSQL(DATABASE_CREATE);

  }

  @Override

  public void onUpgrade(SQLiteDatabase db, int oldVersion,

  int newVersion)

  {

  Log.w(TAG, Upgrading database from version  + oldVersion

  +  to

  + newVersion + , which will destroy all old data);

  db.execSQL("DROP TABLE IF EXISTS titles");

  onCreate(db);

  }

  }

  }

  onCreate()方法創建一個新的數據庫,onUpgrade()方法用於升級數據庫,這可以通過檢查DATABASE_VERSION常量定義的值來實現,對於onUpgrade()方法而言,隻不過是簡單地刪除表,然後在創建表而已。

  現在你可以定義不同的方法來打開和關閉數據庫,如清單3中的添加/編輯/刪除/行的函數。

  清單3 定義打開和關閉數據庫以及增加/編輯/刪除表中行的方法

  public class DBAdapter

  {

  //...

  //...

  //---打開數據庫---

  public DBAdapter open() throws SQLException

  {

  db = DBHelper.getWritableDatabase();

  return this;

  }

  //---關閉數據庫---

  public void close()

  {

  DBHelper.close();

  }

  //---向數據庫插入一個標題---

  public long insertTitle(String isbn, String title, String publisher)

  {

  ContentValues initialValues = new ContentValues();

  initialValues.put(KEY_ISBN, isbn);

  initialValues.put(KEY_TITLE, title);

  initialValues.put(KEY_PUBLISHER, publisher);

  return db.insert(DATABASE_TABLE, null, initialValues);

  }

  //---刪除一個指定的標題---

  public boolean deleteTitle(long rowId)

  {

  return db.delete(DATABASE_TABLE, KEY_ROWID + = + rowId, null) > 0;

  }

  //---檢索所有標題---

  public Cursor getAllTitles()

  {

  return db.query(DATABASE_TABLE, new String[] {

  KEY_ROWID,

  KEY_ISBN,

  KEY_TITLE,

  KEY_PUBLISHER},

  null,

  null,

  null,

  null,

  null);

  }

  //---檢索一個指定的標題---

  public Cursor getTitle(long rowId) throws SQLException

  {

  Cursor mCursor =

  db.query(true, DATABASE_TABLE, new String[] {

  KEY_ROWID,

  KEY_ISBN,

  KEY_TITLE,

  KEY_PUBLISHER

  },

  KEY_ROWID + = + rowId,

  null,

  null,

  null,

  null,

  null);

  if (mCursor != null) {

  mCursor.moveToFirst();

  }

  return mCursor;

  }

  //---更新一個標題---

  public boolean updateTitle(long rowId, String isbn,

  String title, String publisher)

  {

  ContentValues args = new ContentValues();

  args.put(KEY_ISBN, isbn);

  args.put(KEY_TITLE, title);

  args.put(KEY_PUBLISHER, publisher);

  return db.update(DATABASE_TABLE, args,

  KEY_ROWID + = + rowId, null) > 0;

  }

  }Database;

  import android.content.ContentValues;

  import android.content.Context;

  import android.database.Cursor;

  import android.database.SQLException;

  import android.database.sqlite.SQLiteDatabase;

  import android.database.sqlite.SQLiteOpenHelper;

  import android.util.Log;

  public class DBAdapter

  {

  public static final String KEY_ROWID = _id;

  public static final String KEY_ISBN = isbn;

  public static final String KEY_TITLE = title;

  public static final String KEY_PUBLISHER = publisher;

  private static final String TAG = DBAdapter;

  private static final String DATABASE_NAME = books;

  private static final String DATABASE_TABLE = titles;

  private static final int DATABASE_VERSION = 1;

  private static final String DATABASE_CREATE =

  create table titles (_id integer primary key autoincrement,

  + isbn text not null, title text not null,

  + publisher text not null);;

  private final Context context;

  private DatabaseHelper DBHelper;

  private SQLiteDatabase db;

  public DBAdapter(Context ctx)

  {

  this.context = ctx;

  DBHelper = new DatabaseHelper(context);

  }

  private static class DatabaseHelper extends SQLiteOpenHelper

  {

  DatabaseHelper(Context context)

  {

  super(context, DATABASE_NAME, null, DATABASE_VERSION);

  }

  @Override

  public void onCreate(SQLiteDatabase db)

  {

  db.execSQL(DATABASE_CREATE);

  }

  @Override

  public void onUpgrade(SQLiteDatabase db, int oldVersion,

  int newVersion)

  {

  Log.w(TAG, Upgrading database from version  + oldVersion

  +  to

  + newVersion + , which will destroy all old data);

  db.execSQL("DROP TABLE IF EXISTS titles");

  onCreate(db);

  }

  }

  //---打開數據庫---

  public DBAdapter open() throws SQLException

  {

  db = DBHelper.getWritableDatabase();

  return this;

  }

  //---關閉數據庫---

  public void close()

  {

  DBHelper.close();

  }

  //---向數據庫中插入一個標題---

  public long insertTitle(String isbn, String title, String publisher)

  {

  ContentValues initialValues = new ContentValues();

  initialValues.put(KEY_ISBN, isbn);

  initialValues.put(KEY_TITLE, title);

  initialValues.put(KEY_PUBLISHER, publisher);

  return db.insert(DATABASE_TABLE, null, initialValues);

  }

  //---刪除一個指定標題---

  public boolean deleteTitle(long rowId)

  {

  return db.delete(DATABASE_TABLE, KEY_ROWID +

  = + rowId, null) > 0;

  }

  //---檢索所有標題---

  public Cursor getAllTitles()

  {

  return db.query(DATABASE_TABLE, new String[] {

  KEY_ROWID,

  KEY_ISBN,

  KEY_TITLE,

  KEY_PUBLISHER},

  null,

  null,

  null,

  null,

  null);

  }

  //---檢索一個指定標題---

  public Cursor getTitle(long rowId) throws SQLException

  {

  Cursor mCursor =

  db.query(true, DATABASE_TABLE, new String[] {

  KEY_ROWID,

  KEY_ISBN,

  KEY_TITLE,

  KEY_PUBLISHER

  },

  KEY_ROWID + = + rowId,

  null,

  null,

  null,

  null,

  null);

  if (mCursor != null) {

  mCursor.moveToFirst();

  }

  return mCursor;

  }

  //---更新一個標題---

  public boolean updateTitle(long rowId, String isbn,

  String title, String publisher)

  {

  ContentValues args = new ContentValues();

  args.put(KEY_ISBN, isbn);

  args.put(KEY_TITLE, title);

  args.put(KEY_PUBLISHER, publisher);

  return db.update(DATABASE_TABLE, args,

  KEY_ROWID + = + rowId, null) > 0;

  }

  }

  註意Android使用Cursor類返回一個需要的值,Cursor作為一個指針從數據庫查詢返回結果集,使用Cursor允許Android更有效地管理它們需要的行和列,你使用ContentValues對象存儲鍵/值對,它的put()方法允許你插入不同數據類型的鍵值。

  清單4顯示瞭完整的DBAdapter.java源代碼。

  清單4 DBAdapter.java完整源代碼

  package net.learn2develop.

熱門遊戲: 崩壞學園| 植物大戰殭屍2| 武俠Q傳| 神魔之塔| 遠的要命的王國| 部落戰爭| 曹操之野望| 戰神無雙| 釣魚發燒友| 一姬當千| 三國異聞錄.初章| 仙國志| 魔靈召喚: 天空之役| 攻城掠地手機版| 史上最坑爹的遊戲3| 忍者必須死2| 一個都不能死| 神魔之塔簡體騰訊版| 123猜猜猜台灣版| 成語大挑戰| 怪物x聯盟| 全民打棒球2013| 龍珠Q傳| 口袋戰姬| 瘋狂猜成語| 爐石戰記:魔獸英雄傳| 愛新覺羅| LINE釣魚大師| 魅子online| 勇者前線 BraveFrontier| 真三國大戰| 召喚圖板 サモンズボード| 放開那三國| 愛養成2| Line Rangers| Boom Beach| 巨砲連隊| 鬼武傳| 戰姬天下| 幻想の英雄| 暗黑戰神| 神之刃| COOKIE RUN 跑跑薑餅人| 猜猜巧克力| 神鬼幻想| 神鵰俠侶| 卡通農場 Hay day| LINE Pokopang 波兔村保衛戰| 秦時明月| 坑爹的遊戲2| 我是火影| 龍之力量| 城堡爭霸 - Castle Clash| 海賊大亂鬥| Ace Fishing 釣魚發燒友| Chain Chronicle 鎖鏈戰記|

Game2.tw遊戲攻略、資訊網站
申請友情鏈接,申請遊戲專區建立,發放遊戲活動碼,請聯繫bd#game2.tw(#替換成@)