مشخصات مقاله
-
3352
-
0.0
-
20401
-
0
-
0
کار با پایگاه داده Sqlite در اندروید
این مبحث به شرح مفهوم دیتابیس در اندروید می پردازد. سپس به ترتیب نحوه ی استفاده از دیتابیس SQLite در اپلیکیشن، استفاده از ContentProvider جاری و ایجاد نمونه های جدید از آن را برای شما تشریح می کند. در نهایت نمونه ای از کاربرد چارچوب نرم افزاری (framework) Loader جهت بارگذاری داده به صورت ناهمزمان (asynch) در برنامه ی اندرویدی را برای شما به نمایش می گذارد.
پروژه ی این مبحث در محیط کاری Eclipse 4.2 نوشته شده، مبتنی بر ویرایش 1.6 زبان Java و ورژن 4.2 سیستم عامل اندروید می باشد.
آموزش SQLite و Android
SQLite چیست و چه کاربردی دارد؟
SQLite یک دیتابیس کد باز (Open Source) بوده، از امکانات و ویژگی های متعارف دیتابیس های رابطی نظیر ساختار نگارشی (syntax)SQL ، تراکنش ها (transactions) و دستورات آماده پشتیبانی می کند. دیتابیس مزبور در زمان اجرای اپلیکیشن حافظه ی کمی را مصرف می کند (حدودا 250 کیلوبایت). این امر سبب شده که SQLite گزینه ی خوبی برای جاسازی در سایر runtime ها تلقی شود.
SQLite از انواع داده ای همچون TEXT (معادل String در جاوا)، INTEGER (معادل long در جاوا) و REAL (مشابه double در جاوا) پشتیبانی می کند. سایر نوع های داده ای می بایست قبل از قابلیت ذخیره در دیتابیس، به یکی از فیلدهای نام برده تبدیل شوند.
SQLite به صورت خودکار قابلیت اعتبارسنجی انواع داده ای درج شده در ستون ها را ندارد. به عنوان مثال، ممکن است مقداری از نوع integer را در ستون رشته ای ذخیره کرده یا بالعکس. خواهید دید که دیتابیس مذکور از شما ایراد نخواهد گرفت.
آموزش SQLite در اندروید
SQLite تقریبا در تمامی دستگاه های اندروید جاسازی شده است. برای استفاده از این دیتابیس، توسعه دهنده نیازی به نصب و تنظیم آن ندارد. مدیریت (admin) دیتابیس نیز به صورت خودکار توسط خود اندروید انجام می شود.
کافی است دستورات SQL ایجاد و آپدیت دیتابیس را تعریف نموده و فراخوانی کنید. پس از آن باقی عملیات لازم را خود محیط و بستر اجرای اندروید (platform) به دست می گیرد.
دسترسی به دیتابیس SQLite به صورت خودکار دسترسی به سیستم فایل را نیز به دنبال دارد و به همین دلیل عملیات مرتبط با دیتابیس می تواند کند باشد.
دیتابیس اپلیکیشن به صورت خودکار در آدرس DATA/data/APP_NAME/databases/FILENAME ذخیره می شود.
بخش های آدرس فوق بر اساس قوانین و قواعد خاصی ساخته می شوند. DATA آدرس یا مسیری است که متد Environment.getDataDirectory() برمی گرداند. APP_NAME اسم اپلیکیشن شما است و در پایان، FILENAME اسمی است که در کد اپلیکیشن خود برای دیتابیس تعریف می کنید.
آموزش معماری SQLite
آموزش Package ها
پکیج android.database تمامی اطلاعات ضروری برای کار با دیتابیس را شامل می شود. پکیج android.database.sqlite تمامی کلاس های ویژه ی SQLite را دربرمی گیرد.
آموزش ایجاد و آپدیت دیتابیس با SQLiteOpenHelper
به منظور ایجاد و ارتقا دیتابیس در اپلیکیشن اندرویدی خود، لازم است یک کلاس فرزند (subclass) از SQLiteOpenHelper ایجاد نمایید. سپس در تابع سازنده (constructor) کلاس فرزند، متد super() از SQLiteOpenHelper را فراخوانی نموده و اسم و ورژن دیتابیس را مشخص نمایید.
در این کلاس می بایست پیاده سازی متدهای زیر را جهت ایجاد و آپدیت دیتابیس خود، بازنویسی (override) نمایید.
- onCreate() – چارچوب نرم افزاری اندروید (framework) این متد را زمانی صدا می زند که می خواهید به دیتابس دسترسی داشته باشید، اما دیتابیس هنوز ایجاد نشده است.
- onUpgrade() – هنگامی فراخوانی می شود که ورژن دیتابیس در کد اپلیکیشن ارتقا یافته باشد. این متد به شما اجازه می دهد تا schema دیتابیس جاری را بروز رسانی نموده یا دیتابیس جاری را حذف کنید و سپس آن را مجددا با متد onCreate() ایجاد نمایید.
هر دو متد یک آبجکت SQLiteDatabase به عنوان پارامتر می گیرند. این آبجکت جاوا در واقع همان دیتابیس است.
کلاس SQLiteOpenHelper با ارائه ی دو متد getReadableDatabase() و getWriteableDatabase() دسترسی به آبجکت SQLiteDatabase را به ترتیب به صورت فقط خواندنی و نوشتنی برای توسعه دهنده فراهم می آورد.
جداول دیتابیس بایستی _id را به عنوان کلید اصلی (primary key) بکار ببرند. بسیاری از رفتارها و توابع اندروید از این استاندارد پیروی می کنند.
توصیه می شود برای هر کلاس یک جدول جداگانه ایجاد نمایید. این کلاس ها متدهای استاتیک onCreate() و onUpgrade() را تعریف می کند. توابع مذکور در متدهای متناظر داخل SQLiteOpenHelper فراخوانی می شوند. با پیروی از این روش، حتی اگر تعداد جداول دیتابیس بسیار بالا باشد، پیاده سازی SQLiteOpenHelper به راحتی قابل دسترسی و خواندن خواهد بود.
آموزش کلاس پدر SQLiteDatabase
SQLiteDatabase کلاس پایه ای است که برای کار با دیتابیس می بایست از آن ارث بری کنید. این کلاس متدهایی را جهت بازکردن، کوئری گرفتن، آپدیت کردن و بستن دیتابیس در اختیار توسعه دهنده قرار می دهد. به عبارت دقیق تر، SQLiteDatabase سه متد بسیار مهم insert()، update() و delete() را ارائه می دهد.
علاوه بر آن، کلاس نام برده با ارائه ی متد execSQL()این امکان را برای برنامه نویس فراهم کرده تا دستور SQL را به صورت مستقیم اجرا کند.
آبجکت ContentValues جفت های کلید/مقدار (key/value) را ایجاد می کنند. Key همان id یا شناسه ی ستون جدول است و value نیز محتوا یا مقدار رکورد جدول در این ستون می باشد. از این آبجکت برای درج مقدار جدید و آپدیت مقادیر جاری دیتابیس استفاده می شود.
جهت ایجاد query ها و درخواست داده از دیتابیس می توانید به دو روش اقدام کنید: 1. می توانید یکی از دو متد rawQuery() و query() را فراخوانی نمایید 2. از کلاس SQLiteQueryBuilder استفاده کنید.
rawQuery() یک دستور SQL را مستقیما به عنوان ورودی می پذیرد.
query() یک interface ساخت یافته و الگوی پیاده سازی آماده برای تعریف دستور SQL ارائه می دهد.
SQLiteQueryBuilder یک کلاس کمکی (Convenience) است که این امکان را به شما می دهد تا query های sql را به راحتی بسازید.
مثالی از پیاده سازی متد rawQuery()
نمونه کد زیر متد rawQuery() را فراخوانی می کند.
Cursor cursor = getReadableDatabase().
rawQuery("select * from todo where _id = ?", new String[] { id });
مثالی از پیاده سازی متد query()
نمونه کد زیر متد query() را فراخوانی می کند.
return database.query(DATABASE_TABLE,
new String[] { KEY_ROWID, KEY_CATEGORY, KEY_SUMMARY, KEY_DESCRIPTION },
null, null, null, null, null);
متد query() پارامترهای زیر را به عنوان ورودی می گیرد.
گر شرطی برای اعمال و محدودسازی نتایج نیاز نباشد، می توانید null را به عنوان آرگومان ارسال نمایید. به عنوان مثال چنانچه لزومی برای اعمال شرط بر روی گروه ها نیست، می توانید null را پاس دهید.
در متد query() همان طور که مشاهده می کنید، بجای عبارت شرطی "whereClause" از این ساختار استفاده شده است: "_id=19 and summary=?". در واقع عملگر "=" جایگزین عبارت where می شود.
اگر با استفاده از ?مقادیر موقتی و مکان نگهدار تعریف نمایید (placeholder)، در آن صورت می بایست آن ها را به عنوان selectionArgs به کوئری ارسال کنید.
آموزش آبجکت Cursor
کوئری در خروجی یک آبجکت Cursor برمی گرداند. Cursor همان نتیجه ی کوئری و سطرهای واکشی شده از دیتابیس است و اساسا به یک سطر از نتیجه ی کوئری اشاره دارد. این روش بازیابی اطلاعات به اندروید اجازه می دهد تا نتایج کوئری را به راحتی buffer کند چرا که در این صورت سیستم دیگری نیازی به بارگذاری تمامی داده ها در حافظه ندارد.
برای بدست آوردن تعداد المان های کوئری خروجی می توانید متد getCount() را بکار ببرید.
به منظور حرکت بین سطرهای فردی، می توانید moveToFirst() و moveToNext() را فراخوانی نمایید. با استفاده از متد isAfterLast() می توانید بررسی کنید آیا به انتهای نتیجه ی کوئری رسیده اید یا خیر.
Cursor مجموعه متدهای (typed) وابسته به نوع get*() را در اختیار توسعه دهنده قرار می دهد. از جمله می توان به getLong(columnIndex)، getString(columnIndex) جهت دسترسی به مقادیر و داده های ستون با توجه به اندیس کنونی نتیجه اشاره کرد. getString(columnIndex) برای بازیابی داده از نوع رشته و getLong(columnIndex) برای واکشی داده هایی از نوع Long استفاده می شود. پارامتر "columnIndex" همان طور که از نامش پیدا است، شماره ی مکان قرار گیری و اندیس ستون مورد درخواست می باشد که داده را بر اساس آن استخراج می کند.
Cursor همچنین متدی به نام getColumnIndexOrThrow(String) را ارائه می دهد. این متد اسم ستون را به عنوان ورودی گرفته و در خروجی شماره ی مکان قرار گیری ستون را برمی گرداند.
لازم به ذکر است که آبجکت Cursor را می بایست با فراخوانی متد close() ببندید.
آموزش ListView ها، ListActivityها و SimpleCursorAdapter
ListView ها آبجکت های view ای هستند که لیستی از المان ها را برای کاربر به نمایش می گذارند.
ListActivitiy ها نیز activity های ویژه ای هستند که استفاده از ListView ها را آسان تر می سازند.
برای کار با دیتابیس ها و ListView ها می توانید از SimpleCursorAdapter استفاده نمایید. SimpleCursorAdapter به شما این امکان را می دهد تا برای هر سطر از ListView ها یک layout تنظیم نمایید.
شما بایستی آرایه ای تعریف کنید که دربردارنده ی اسم ستون ها می باشد و سپس آرایه ی دیگری جهت نگهداری ID و شناسه ی View هایی که باید با داده پر شوند ایجاد نمایید.
کلاس SimpleCursorAdapter ستون ها را بر اساس Cursor ارسالی، به view ها نگاشت می کند.
جهت دسترسی به Cursor می بایست کلاس Loader را بکار ببرید.
آموزش: استفاده از SQLite
مقدمه ای بر پروژه
در این بخش نحوه ی کار با دیتابیس SQLite را به صورت کاربردی خواهید آموخت. برای مدیریت داده ها از یک DAO (آبجکت دسترسی به داده ها) استفاده خواهید نمود. DAO وظیفه ی مدیریت اتصال به دیتابیس، دسترسی و ویرایش داده ها را بر عهده دارد. طی این مکانیزم همچنین آبجکت های دیتابیس به آبجکت های متعارف جاوا (Java Objects) تبدیل می شوند و از این طریق کد UI دیگر با لایه ی ذخیره ی دائمی داده ها یا persistence layer تعامل نخواهد داشت.
در پایان اپلیکیشن به صورت زیر خواهد بود:
گفتنی است که استفاده از الگو DAO همیشه و در همه ی شرایط بهترین روش نیست. در واقع این الگو، model object های جاوا (آبجکت هایی از روی جداول دیتابیس) ایجاد می کند و منابع بیشتری مصرف می کند. استفاده ی مستقیم از یک دیتابیس یا دسترسی به داده ها از طریق یک ContentProvider معمولا بهینه تر بوده و از آنجایی که در این دو روش دیگر نیازی به ساخت آبجکت های جاوا نیست، میزان حافظه مورد استفاده کاهش می یابد.
همان طور که گفته شد در این روش از DAO استفاده می شود. برای این مثال ورژن کتابخانه های اندروید را بر روی API 15 تنظیم نمایید که معادل اندروید 4.0 می باشد. در غیر این صورت می بایست از کلاس Loader برای مدیریت Cursor استفاده می شد که از اندروید 3.0 به بعد پشتیبانی می شود. بعلاوه استفاده از این کلاس پیچیدگی خاص خود را دارد.
آموزش ایجاد پروژه
یک پروژه ی جدید اندروید و یک activity به ترتیب به نام های de.vogella.android.sqlite.first و TestDatabaseActivity ایجاد نمایید.
آموزش ساخت Database و Data Model
ابتدا کلاس MySQLiteHelper را ایجاد نمایید. این کلاس دیتابیس اپلیکیشن را ایجاد می کند.
اگر به خاطر داشته باشید، متد onUpgrade() به کلی داده های فعلی بانک اطلاعاتی را حذف کرده و جدول را مجددا ایجاد می کند. سپس تعدادی ثابت (constant) جهت نگهداری مقادیر اسم جدول و ستون های جدول ایجاد می نماید.
package de.vogella.android.sqlite.first;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class MySQLiteHelper extends SQLiteOpenHelper {
public static final String TABLE_COMMENTS = "comments";
public static final String COLUMN_ID = "_id";
public static final String COLUMN_COMMENT = "comment";
private static final String DATABASE_NAME = "commments.db";
private static final int DATABASE_VERSION = 1;
// Database creation sql statement
private static final String DATABASE_CREATE = "create table "
+ TABLE_COMMENTS + "( " + COLUMN_ID
+ " integer primary key autoincrement, " + COLUMN_COMMENT
+ " text not null);";
public MySQLiteHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase database) {
database.execSQL(DATABASE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(MySQLiteHelper.class.getName(),
"Upgrading database from version " + oldVersion + " to "
+ newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS " + TABLE_COMMENTS);
onCreate(db);
}
}
کلاس Comment را ایجاد نمایید. این کلاس به منزله ی model (آبجکتی ساخته شده از جدول دیتابیس) خواهد بود و تمامی داده هایی که در دیتابیس ذخیره شده و برای کاربر به نمایش در می آید را دربرخواهد گرفت.
package de.vogella.android.sqlite.first;
public class Comment {
private long id;
private String comment;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
// Will be used by the ArrayAdapter in the ListView
@Override
public String toString() {
return comment;
}
}
کلاس CommentDataSource را ایجاد نمایید. این کلاس همان آبجکتی است که برای دسترسی به دیتابیس (DAO) از آن استفاده خواهید نمود. آبجکت ذکر شده اتصال به دیتابیس را نگه داشته و امکان درج comment های جدید و واکشی تمامی comment های جاری را برای اپلیکیشن فراهم می کند.
package de.vogella.android.sqlite.first;
import java.util.ArrayList;
import java.util.List;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
public class CommentsDataSource {
// Database fields
private SQLiteDatabase database;
private MySQLiteHelper dbHelper;
private String[] allColumns = { MySQLiteHelper.COLUMN_ID,
MySQLiteHelper.COLUMN_COMMENT };
public CommentsDataSource(Context context) {
dbHelper = new MySQLiteHelper(context);
}
public void open() throws SQLException {
database = dbHelper.getWritableDatabase();
}
public void close() {
dbHelper.close();
}
public Comment createComment(String comment) {
ContentValues values = new ContentValues();
values.put(MySQLiteHelper.COLUMN_COMMENT, comment);
long insertId = database.insert(MySQLiteHelper.TABLE_COMMENTS, null,
values);
Cursor cursor = database.query(MySQLiteHelper.TABLE_COMMENTS,
allColumns, MySQLiteHelper.COLUMN_ID + " = " + insertId, null,
null, null, null);
cursor.moveToFirst();
Comment newComment = cursorToComment(cursor);
cursor.close();
return newComment;
}
public void deleteComment(Comment comment) {
long id = comment.getId();
System.out.println("Comment deleted with id: " + id);
database.delete(MySQLiteHelper.TABLE_COMMENTS, MySQLiteHelper.COLUMN_ID
+ " = " + id, null);
}
public List getAllComments() {
List comments = new ArrayList();
Cursor cursor = database.query(MySQLiteHelper.TABLE_COMMENTS,
allColumns, null, null, null, null, null);
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
Comment comment = cursorToComment(cursor);
comments.add(comment);
cursor.moveToNext();
}
// make sure to close the cursor
cursor.close();
return comments;
}
private Comment cursorToComment(Cursor cursor) {
Comment comment = new Comment();
comment.setId(cursor.getLong(0));
comment.setComment(cursor.getString(1));
return comment;
}
}
آموزش ساخت ظاهر و UI اپلیکیشن
فایل main.xml در
کلاس TestDatabaseActivity را به صورت زیر ویرایش نمایید. برای نمایش آسان لیست ساده ای از اطلاعات می توانید از یک ListActivity استفاده نمایید. اپلیکیشن خود را نصب نموده، دکمه های Add و Delete را کلیک نمایید. سپس اپلیکیشن را restart کرده و اطمینان حاصل نمایید که داده ها پس از راه اندازی مجدد همچنان در جای خود هستند.
جهت به اشتراک گذاری داده ها بین چندین اپلیکیشن، می توانید از Content Provider استفاده نمایید. می توانید به Provider به چشم یک منبع داده ای نگاه کنید که اطلاعات موجود در آن به اپلیکیشنی که از آن استفاده می کند، وابسته نمی باشد. این امکان وجود دارد که از Provider مانند یک دیتابیس کوئری بگیرید، داده های آن را بروز رسانی و حتی حذف نمایید.علاوه بر آن provider ها قابلیتی دارند که به آن ها اجازه ی ذخیره داده ها در فایل، دیتابیس و شبکه را می دهد.
برای دسترسی به content provider می بایست ابتدا پیشوند متعارف content:// و سپس namespace مربوط به provider را ارائه نمایید (namespace همان نام content provider حامل داده های مورد نظر می باشد). لازم است namespace را داخل فایل manifest، تگ provider و در attribute (خصیصه) android:authorities تعریف و مقداردهی نمایید. مثال: content://test/.
URI پایه امکان دسترسی به مجموعه ای از منابع (برای مثال کل یک جدول) را فراهم می کند. چنانچه در بخش نهایی URI شناسه ی نمونه ذکر شود، در آن صورت تنها content ای که id آن ذکر شده، قابل دسترسی می باشد. مثال: content://test/2.
از آنجایی که داشتن URI های یک provider برای دسترسی به آن ضروی است، توصیه می شود URI ها را داخل constant هایی با سطح دسترسی public قرار داده، آن ها را به صورت مستند در اختیار دیگر توسعه دهندگان قرار دهید. به منظور ایجاد content provider اختصاصی خود بایستی یک کلاس که از android.content.ContentProvider ارث می برد (extend می کند)، ایجاد نمایید. سپس این کلاس را به عنوان content provider در فایل تنظیمات (Android manifest) معرفی کنید. تگ مربوطه در فایل تنظیمات باید خصیصه (attribute) android:authorities را داشته باشد. مقدار این attribute را برابر اسم content provider قرار می دهید. Authority در واقع مبنای URI را تشکیل داده و برای دسترسی به content provider مورد استفاده قرار می گیرد، از اینرو باید منحصر بفرد باشد.
در بدنه ی Content provider شما باید تعداد زیادی متد را پیاده سازی کنید. برای مثال می توان به query()، insert()، update()، delete()، getType() و onCreate() اشاره کرد. در صورتی که از متد خاصی
تا ویرایش 4.2 اندروید، content provider به صورت پیش فرض برای دیگر اپلیکیشن های اندرویدی قابل دسترسی می باشد. اما از ورژن 4.2 به بعد اندروید، برای دسترسی به content provider لازم است آن را به صورت صریح export نمایید. توصیه می شود پارامتر android:exported را همیشه در فایل تنظیمات قید کنید تا رفتار اپلیکیشن در ورژن های مختلف اندروید یکسان باشد.
چنانچه مستقیما با دیتابیس تعامل داشته باشید و همزمان از چند thread در دیتابیس مقادیری نوشته شوند (از چند thread همزمان چندین writer وجود داشته باشد) در آن صورت طبیعتا با مشکل مدیریت همروندی و concurrency مواجه می شوید.
مثال زیر از content provider آماده ی اپلیکیشن People استفاده می کند. برای این مثال لازم است تعدادی contact ثابت داشته باشید. منوی home را انتخاب نموده و سپس المان People را برای ایجاد contacts انتخاب کنید. اپلیکیشن از شما می پرسد آیا میلی به ورود و login در برنامه دارید یا خیر. می توانید login کنید یا "Not now" را انتخاب نمایید. حال بر روی دکمه ی "Create a new contact" کلیک نمایید. می توانید local contatcs را ایجاد کنید. پس از اینکه اولین contact را ایجاد کردید، اپلیکیشن به شما اجازه می دهد تا با کلیک بر روی دکمه ی "+" contact های بیشتری ایجاد کنید. در پایان چندین contact به اپلیکیشن خود اضافه کرده اید.
یک پروژه و activity جدید اندروید به ترتیب به نام های de.vogella.android.contentprovider و ContactsActivity ایجاد نمایید.
از آنجایی که تمامی اپلیکیشن ها نباید به اطلاعات شخصی مخاطبین دسترسی داشته باشند، لازم است برای دستیابی به ContentProvider contact، در فایل تنظیمات permission خاصی تنظیم نمایید. برای این منظور ابتدا فایل AndroidManifest.xml را باز نموده و تب Permissions را انتخاب کنید. در این تب، بر روی دکمه ی Add کلیک کرده و Uses Permission را انتخاب نمایید. از لیست کشویی (drop-down) حاضر، آیتم android.permission.READ_CONTACTS را انتخاب کنید. زمانی که اپلیکیشن را اجرا کنید، داده های لازم از کامپوننت نرم افزاری ContentProvider که اطلاعات اپلیکیشن People را حمل می کند، بارگذاری شده و نهایتا در المان رابط کاربری TextView به نمایش گذاشته می شود. جهت ارائه ی چنین اطلاعاتی به کاربر، توسعه دهندگان معمولا از لیست ساده که توسط ListView پیاده سازی می شود، استفاده می کنند.
یکی از مشکلات و چالش های دسترسی به دیتابیس و خواندن اطلاعات، پایین بودن سرعت آن است. بعلاوه، اپلیکیشن می بایست life cycle کامپوننت ها را در نظر گرفته و آن را مدیریت کند. برای مثال می توان به باز کردن و بستن cursor در صورت تغییر در تنظیمات و نحوه ی پیکربندی اشاره کرد. نمونه اپلیکیشنی که در زیر مشاهده می کنید، در Android Market نیز به صورت آماده در دسترس است. برای اینکه کاربران بیشتری بتوانند از آن استفاده کنند، اپلیکیشن به ویرایش 2.3 سیستم عامل اندروید downport شده و بر روی آن ها قابل اجرا می باشد. در صورتی که اپلیکیشن بارکد خوان را بر روی دستگاه خود نصب کرده باشید، می توانید با اسکن کد QR زیر به سرعت اپلیکیشن مورد نظر را در Android Market پیدا کنید. لازم به توضیح است که اپلیکیشن نام برده در ورژن های مختلف اندروید ممکن است ظاهر و رفتار متفاوتی داشته باشد. برای مثال ممکن است بجای OptionMenu در اپلیکیشن ActionBar را داشته باشید و پوسته (theme) برنامه کمی متفاوت باشد.
در بخش آموزشی حاضر یک اپلیکیشن به نام "To-do" ایجاد خواهید نمود. این برنامه به کاربر اجازه می دهد تا task یا کارهایی که باید انجام شوند را در لیستی وارد نماید. آیتم های ورودی در دیتابیس SQLite ذخیره شده و از طریق ContentProvider قابل دسترسی می باشد. یک پروژه و activity جدید به ترتیب به نام های de.vogella.android.todos و TodosOverviewActivity ایجاد نمایید. حال activity دیگری به نام TodoDetailActivity در پروژه ی جاری ایجاد نمایید.
پکیج de.vogella.android.todos.database را ایجاد نمایید. این پکیج کلاس هایی که عملیات مربوط به دیتابیس را اداره می کنند، در خود نگه می دارد. کلاس TodoDatabaseHelper زیر را ایجاد نمایید. این کلاس از SQLiteOpenHelper ارث برده و متدهای static کلاس کمکی (helper) TodoTable فراخوانی می کند. ContentProvider جهت دسترسی به داده های مورد نیاز پیاده سازی خواهید کرد.برای دسترسی به دیتابیس این بار مانند مثال قبلی از یک DAO (آبجکت دسترسی به داده) استفاده نخواهید نمود، بلکه یک ابتدا پکیج de.vogella.android.todos.contentprovider را ایجاد نمایید. سپس کلاس MyTodoContentProvider را ایجاد کنید که از ContentProvider ارث بری (extend) می کند.
MyTodoContentProvider توابع update()، insert()، delete() و query() را پیاده سازی می کند. این متدها به طور مستقیم از interface یا الگوی پیاده سازی SQLiteDatabase مشتق می شوند.
اپلیکیشن جاری به منابع و محتوای متعددی احتیاج دارد. ابتدا یک منوی listmenu.xml در پوشه ی res/menu تعریف نمایید. اگر از ویزارد Android resource برای ایجاد فایل "listmenu.xml" استفاده نمایید، پوشه ی مورد نیاز به صورت خودکار برای شما ایجاد می شود. چنانچه فایل را خود به صورت دستی ایجاد کنید، در آن صورت پوشه را نیز باید خود بسازید.
کاربر می تواند برای آیتم ها اولویت قرار دهد. برای priorities یک آرایه ی رشته ای ایجاد خواهید نمود. لازم است برای اپلیکیشن خود ثابت های رشته ای متعددی تعریف کنید. فایل strings.xml تحت پوشه ی res/values را ویرایش نمایید.
سه فایل layout تعریف خواهید کرد. یکی از فایل های layout برای نمایش و تنظیم ظاهر سطر در لیست مورد استفاده قرار می گیرد و دو فایل دیگر توسط activity های اپلیکیشن فراخوانی می شوند.
حال فایل todo_list.xml را ایجاد نمایید. این فایل ظاهر و چیدمان لیست را تعیین می کند. فایل todo_edit.xml را ایجاد کنید. این فایل layout برای ویرایش و تنظیم ظاهر آیتم های فردی todo در TodoDetailActivity مورد استفاده قرار می گیرد. کد activity های خود را به صورت زیر ویرایش کنید. ویرایش را از فایل TodosOverviewActivity.java آغاز نمایید. سپس فایل TodoDetailActivity.java را به صورت زیر ویرایش کنید. محتوای فایل AndroidManifest.xml در نهایت به صورت زیر خواهد بود.
اپلیکیشن خود را راه اندازی کنید. با کلیک بر روی دکمه ی "Insert" در ActionBar طبیعتا باید بتوانید آیتم جدیدی را در لیست درج نمایید. به منظور ویرایش آیتم جاری در لیست، بر روی سطر میزبان آن کلیک نمایید. با این کار activity دوم راه اندازی می شود.
SQLite دیتابیس را همراه با تمامی اطلاعات آن داخل یک فایل ذخیره می کند. در صورتی که به این فایل دسترسی دارید، می توانید به طور مستقیم با دیتابیس کار کنید. دسترسی مسقیم به فایل مزبور تنها در محیط شبیه ساز و دستگاه های root شده امکان پذیر می باشد. در محیط شبیه ساز یا دستگاه root شده اندروید، می توان از طریق پنجره ی فرمان یا command line به دیتابیس SQLite دسترسی داشت. جهت اتصال به دستگاه کافی است دستور زیر را در پنجره ی فرمان اجرا کنید.
Command adb در پوشه ی نصب Android SDK و زیرپوشه ی "platform-tools" مسقر می باشد. پرکاربرد ترین دستورات SQLite به شرح زیر می باشند:
تغییراتی که در SQLite اعمال می شوند همگی ACID هستند (Atomicity: تراکنش ها باید کامل اجرا شوند. Consistency: تمامی تراکنش ها باید دیتابیس را از یک حالت کامل و قابل قبول به یک حالت بی عیب و کامل دیگر منتقل کند. isolation: تراکنش هایی که موازی اجرا می شوند باید کاملا از هم مجزا بوده و در جامعیت دیتابیس اختلالی ایجاد نکند. Durability: کلیه ی تغییراتی که تراکنش ها بر روی دیتابیس اعمال می کنند همگی باید ذخیره شده و در صورت رخداد خطای ناگهانی آن ها را به حالت اول بازگرداند). در نتیجه تمامی عملیات update، insert و delete ای که بر روی پایگاه داده اجرا می شوند، از این چهار ویژگی برخوردار بوده و از آن ها تبعیت می کنند. در پی این ویژگی، به ناگذیر مقداری overhead و سربار در پردازش های دیتابیس وارد شده و کارایی را پایین می آورد. به همین جهت توصیه می شود عملیات update در SQLite را در قالب یک تراکنش (transaction) گنجانده (wrap) و پس از انجام موفقیت آمیز تمامی عملیات لازم تراکنش را ثبت نهایی (commit) نمایید. با این اقدام سرعت و کارایی اپلیکیشن به طور چشم گیری افزایش می یابد. برای آپدیت های زیاد می بایست متد yieldIfContendedSafely() را فراخوانی نمایید. SQLite در طول اجرای تراکنش بر روی دیتابیس قفل می گذارد. حال اگر سرویس گیرنده ی دیگری از داده های مورد نظر کوئری بگیرد، با فراخوانی این متد، اندروید تراکنش را متوقف کرده و یک تراکنش جدید باز می کند. از این طریق فرایند دوم می تواند به داده ها دسترسی پیدا کند.
package de.vogella.android.sqlite.first;
import java.util.List;
import java.util.Random;
import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
public class TestDatabaseActivity extends ListActivity {
private CommentsDataSource datasource;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
datasource = new CommentsDataSource(this);
datasource.open();
List
آموزش نصب و اجرای اپلیکیشن
آموزش مفهوم Content Provider
Content Provider چیست و چه کاربردی دارد؟
Provider اطلاعات را بر اساس URI مربوطه کپسوله سازی می کند. هر URI ای که با content:// شروع می شود در واقع به منابع و محتوایی اشاره دارد که از طریق Provider قابل دسترسی است.
آدرس URI یک منبع به شما این امکان را می دهد تا از طریق Provider عملیات ساده ی CRUD (ایجاد، بازیابی، بروز رسانی و حذف) را بر روی داده های منبع مورد نظر اجرا نمایید.
همان طور که قبلا اشاره شد، provider داده های مورد نیاز اپلیکیشن را در اختیار آن قرار می دهد. این داده ها ممکن است در یک دیتابیس SQLite، در سیستم فایل، داخل فایل های flat یا بر روی سرور راه دور (remote server) مستقر باشند.
اگرچه از Provider می توان برای دسترسی به داده ها در یک اپلیکیشن نیز استفاده کرد، اما معمولا توسعه دهندگان از آن برای به اشتراک گذاری داده ها بین چندین اپلیکیشن بهره می گیرند. داده های اپلیکیشن به صورت پیش فرض دارای سطح دسترسی private می باشند. اینجا است که content provider به یاری شما می آید؛ این کامپوننت نرم افزاری دسترسی به داده های سایر اپلیکیشن ها را بر اساس یک interface ساخت یافته به راحتی مهیا می سازد.
Content provider را می بایست داخل فایل تنظیمات اپلیکیشن اندرویدی خود (manifest) تعریف نمایید.
آموزش قالب پایه ای و الگوی تعریف URI جهت دسترسی به Content Provider
در زیر بخش ها مختلف یک URI را شرح می دهیم.
قالب و ساختار کلی URI به این صورت می باشد:
آموزش دسترسی به content provider
بسیاری از منابع داده ای اندروید همچون contacts (اطلاعات مخاطبین) از طریق content provider قابل دسترسی هستند.
آموزش پیاده سازی content provider اختصاصی
پشتیبانی نمی کنید، توصیه می شود UnsupportedOperationException() را فراخوانی نمایید.
متد query() در خروجی یک آبجکت Cursor برمی گرداند.
آموزش Content provider و مبحث امنیت
به منظور تنظیم قابلیت رویت و دسترسی content provider خود، کافی است پارامتر android:exported=false|true را در تعریف content provider خود داخل فایل AndroidManifest.xml ذکر نمایید.
آموزش Thread safety (برطرف سازی مشکل همزمانی با استفاده از کلیدواژه ی synchronized)
یک content provider می تواند همزمان از چندین برنامه مورد دسترسی قرار گیرد. به این دلیل لازم است دسترسی thread-safe را پیاده سازی نمایید. آسان ترین راه، استفاده از کلیدواژه ی synchronized در مقابل تمامی متدهای provider است. بدین وسیله تنها یک thread در لحظه می تواند به این متدها دسترسی داشته باشد.
(thread-safe = دسترسی چند thread به یک data structure بدون اینکه در پروسه ی کاری thread ها اختلالی به وجود آید).
اگر لزومی ندارد که اندروید دسترسی به provider را sync کند، مقدار خصیصه (attribute) android:multiprocess= را در تگ آموزش: استفاده ی کاربردی از ContentProvider
آموزش ایجاد contact ها (مخاطبین) در محیط شبیه ساز
آموزش استفاده از Contact Content Provider
فایل layout مربوطه را نیز در آدرس
res/layout_ folder ویرایش نمایید. ID المان TextView را به contactview تغییر داده و متن پیش فرض را حذف نمایید.
فایل layout خروجی می بایست دارای ظاهری مشابه زیر باشد.
پیاده سازی activity را به صورت زیر ویرایش نمایید.
package de.vogella.android.contentprovider;
import android.app.Activity;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.widget.TextView;
public class ContactsActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contacts);
TextView contactView = (TextView) findViewById(R.id.contactview);
Cursor cursor = getContacts();
while (cursor.moveToNext()) {
String displayName = cursor.getString(cursor
.getColumnIndex(ContactsContract.Data.DISPLAY_NAME));
contactView.append("Name: ");
contactView.append(displayName);
contactView.append("\n");
}
}
private Cursor getContacts() {
// Run query
Uri uri = ContactsContract.Contacts.CONTENT_URI;
String[] projection = new String[] { ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME };
String selection = ContactsContract.Contacts.IN_VISIBLE_GROUP + " = '"
+ ("1") + "'";
String[] selectionArgs = null;
String sortOrder = ContactsContract.Contacts.DISPLAY_NAME
+ " COLLATE LOCALIZED ASC";
return managedQuery(uri, projection, selection, selectionArgs,
sortOrder);
}
}
Unresolved directive in 001_article.adoc - include::../AndroidBackgroundProcessing/content/120_loader.adoc[]
آموزش Cursor ها و Loader ها
تا ویرایش 3.0 سیستم عامل اندروید، توسعه دهندگان با فراخوانی متد managedQuery() در کلاس های activity، چرخه ی حیات یا lifecycle کامپوننت ها را اداره می کردند.
اما از ویرایش 3.0 به بعد اندروید، استفاده از این متد دیگر توصیه نمی شود و توسعه دهنده برای دسترسی به ContentProvider می بایست از چارچوب نرم افزاری (framework) Loader استفاده کند.
کلاس SimpleCursorAdapter که همراه با ListViews نیز قابل استفاده می باشد، متد swapCursor() را در اختیار شما قرار می دهد. کلاس Loader اپلیکیشن شما می تواند با فراخوانی این متد، Cursor را در متد onLoadFinished() بروز رسانی کند.
کلاس CursorLoader، آبجکت Cursor را پس از تغییر در تنظیمات و نحوه ی پیکربندی مجددا متصل می کند.
آموزش: SQLite، ContentProvider و Loader با پیاده سازی اختصاصی
مرور کلی
در نمونه اپلیکیشن جاری، کارها یا task هایی که داخل لیست درج می شوند، "todos" نام دارند.
اپلیکیشن مورد نظر دارای دو activity می باشد که یکی برای مشاهده ی آیتم های todo و دیگری ویژه ی ایجاد و ویرایش آیتم مورد نظر تعبیه شده است. دو activity این اپلیکیشن از طریق آبجکت های Intent با یکدیگر تعامل کرده و اطلاعات مورد نیاز را رد و بدل می نمایند.
به منظور بارگذاری و مدیریت Cursor به طور ناهمزمان (asynch)، activity اصلی اپلیکیشن از کلاس Loader بهره می گیرد.
در پایان اپلیکیشن دارای ظاهری مشابه زیر خواهد بود.
آموزش ایجاد پروژه
آموزش تعریف کلاس های مدیریت Database
همان طور که قبلا گفته شد، بهتر است ویژه ی هر جدول در دیتابیس، یک کلاس جداگانه تعریف نمایید. هر چند نمونه اپلیکیشن حاضر یک جدول بیشتر ندارد، اما توصیه می شود از این روش پیروی کنید تا در صورت گسترش schema دیتابیس (پوشه ای که جداول دیتابیس در آن قرار می گیرند) تمهیدات لازم را از قبل اندیشیده و برای آن آماده باشید.
کلاس زیر را ایجاد نمایید. این کلاس مقادیر مربوط به اسم جدول و ستون های آن را در قالب چند constant نگه می دارد.
package de.vogella.android.todos.database;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
public class TodoTable {
// Database table
public static final String TABLE_TODO = "todo";
public static final String COLUMN_ID = "_id";
public static final String COLUMN_CATEGORY = "category";
public static final String COLUMN_SUMMARY = "summary";
public static final String COLUMN_DESCRIPTION = "description";
// Database creation SQL statement
private static final String DATABASE_CREATE = "create table "
+ TABLE_TODO
+ "("
+ COLUMN_ID + " integer primary key autoincrement, "
+ COLUMN_CATEGORY + " text not null, "
+ COLUMN_SUMMARY + " text not null,"
+ COLUMN_DESCRIPTION
+ " text not null"
+ ");";
public static void onCreate(SQLiteDatabase database) {
database.execSQL(DATABASE_CREATE);
}
public static void onUpgrade(SQLiteDatabase database, int oldVersion,
int newVersion) {
Log.w(TodoTable.class.getName(), "Upgrading database from version "
+ oldVersion + " to " + newVersion
+ ", which will destroy all old data");
database.execSQL("DROP TABLE IF EXISTS " + TABLE_TODO);
onCreate(database);
}
}
package de.vogella.android.todos.database;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class TodoDatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "todotable.db";
private static final int DATABASE_VERSION = 1;
public TodoDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
// Method is called during creation of the database
@Override
public void onCreate(SQLiteDatabase database) {
TodoTable.onCreate(database);
}
// Method is called during an upgrade of the database,
// e.g. if you increase the database version
@Override
public void onUpgrade(SQLiteDatabase database, int oldVersion,
int newVersion) {
TodoTable.onUpgrade(database, oldVersion, newVersion);
}
}
آموزش ایجاد ContentProvider
package de.vogella.android.todos.contentprovider;
import java.util.Arrays;
import java.util.HashSet;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import de.vogella.android.todos.database.TodoDatabaseHelper;
import de.vogella.android.todos.database.TodoTable;
public class MyTodoContentProvider extends ContentProvider {
// database
private TodoDatabaseHelper database;
// used for the UriMacher
private static final int TODOS = 10;
private static final int TODO_ID = 20;
private static final String AUTHORITY = "de.vogella.android.todos.contentprovider";
private static final String BASE_PATH = "todos";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY
+ "/" + BASE_PATH);
public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE
+ "/todos";
public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE
+ "/todo";
private static final UriMatcher sURIMatcher = new UriMatcher(
UriMatcher.NO_MATCH);
static {
sURIMatcher.addURI(AUTHORITY, BASE_PATH, TODOS);
sURIMatcher.addURI(AUTHORITY, BASE_PATH + "/#", TODO_ID);
}
@Override
public boolean onCreate() {
database = new TodoDatabaseHelper(getContext());
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// Uisng SQLiteQueryBuilder instead of query() method
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
// check if the caller has requested a column which does not exists
checkColumns(projection);
// Set the table
queryBuilder.setTables(TodoTable.TABLE_TODO);
int uriType = sURIMatcher.match(uri);
switch (uriType) {
case TODOS:
break;
case TODO_ID:
// adding the ID to the original query
queryBuilder.appendWhere(TodoTable.COLUMN_ID + "="
+ uri.getLastPathSegment());
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
SQLiteDatabase db = database.getWritableDatabase();
Cursor cursor = queryBuilder.query(db, projection, selection,
selectionArgs, null, null, sortOrder);
// make sure that potential listeners are getting notified
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
int uriType = sURIMatcher.match(uri);
SQLiteDatabase sqlDB = database.getWritableDatabase();
long id = 0;
switch (uriType) {
case TODOS:
id = sqlDB.insert(TodoTable.TABLE_TODO, null, values);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse(BASE_PATH + "/" + id);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int uriType = sURIMatcher.match(uri);
SQLiteDatabase sqlDB = database.getWritableDatabase();
int rowsDeleted = 0;
switch (uriType) {
case TODOS:
rowsDeleted = sqlDB.delete(TodoTable.TABLE_TODO, selection,
selectionArgs);
break;
case TODO_ID:
String id = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsDeleted = sqlDB.delete(
TodoTable.TABLE_TODO,
TodoTable.COLUMN_ID + "=" + id,
null);
} else {
rowsDeleted = sqlDB.delete(
TodoTable.TABLE_TODO,
TodoTable.COLUMN_ID + "=" + id
+ " and " + selection,
selectionArgs);
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsDeleted;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
int uriType = sURIMatcher.match(uri);
SQLiteDatabase sqlDB = database.getWritableDatabase();
int rowsUpdated = 0;
switch (uriType) {
case TODOS:
rowsUpdated = sqlDB.update(TodoTable.TABLE_TODO,
values,
selection,
selectionArgs);
break;
case TODO_ID:
String id = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsUpdated = sqlDB.update(TodoTable.TABLE_TODO,
values,
TodoTable.COLUMN_ID + "=" + id,
null);
} else {
rowsUpdated = sqlDB.update(TodoTable.TABLE_TODO,
values,
TodoTable.COLUMN_ID + "=" + id
+ " and "
+ selection,
selectionArgs);
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
}
private void checkColumns(String[] projection) {
String[] available = { TodoTable.COLUMN_CATEGORY,
TodoTable.COLUMN_SUMMARY, TodoTable.COLUMN_DESCRIPTION,
TodoTable.COLUMN_ID };
if (projection != null) {
HashSet
این کلاس همچنین با فراخوانی متدی به نام checkColumns()، اطمینان حاصل می کند که کوئری فقط و فقط ستون های معتبر و مجاز را درخواست می کند.
ContentProvider اپلیکیشن خود را در فایل AndroidManifest.xml ثبت نمایید.
<>
آموزش منابع (Resource ها)
این فایل XML برای تعریف ظاهر option menu در اپلیکیشن بکار می رود. با تنظیم مقدار android:showAsAction= بر روی"always"، آیتم مورد نظر در ActionBar اپلیکیشن به نمایش گذاشته می شود.
ابتدا فایل priority.xml زیر را در پوشه ی res/values ایجاد نمایید.
آموزش تعریف فایل های layout
Row layout به آیکونی به نام reminder اشاره دارد. آیکونی از نوع "png" به نام "reminder.png" در پوشه های res/drawable (drawable-hdpi, drawable-mdpi, drawable-ldpi) ایجاد نمایید.
اگر خود آیکون مناسبی در دست ندارید، می توانید آیکونی که ویزارد اندروید (ic_launcher.png in the res/drawable* folders) در اختیار شما قرار می دهد را بکار ببرید یا اسم (reference) اشاره گر به آن را در فایل layout ویرایش نمایید. لازم به ذکر است که ADT ممکن است اسم آیکون تولید شده را تغییر دهد. از اینرو اگر اسم فایل شما "ic_launcher.png" نبود، تعجب نکنید.
در صورت تمایل می توانید تعریف آیکون را از فایل "todo_row.xml" به کلی حذف کنید. این فایل را در مرحله ی بعدی ایجاد خواهید نمود.
فایل "todo_row.xml" را در پوشه ی
آموزش تنظیم Activity ها
package de.vogella.android.todos;
import android.app.ListActivity;
import android.app.LoaderManager;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import de.vogella.android.todos.contentprovider.MyTodoContentProvider;
import de.vogella.android.todos.database.TodoTable;
/*
* TodosOverviewActivity displays the existing todo items
* in a list
*
* You can create new ones via the ActionBar entry "Insert"
* You can delete existing ones via a long press on the item
*/
public class TodosOverviewActivity extends ListActivity implements
LoaderManager.LoaderCallbacks
package de.vogella.android.todos;
import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
import de.vogella.android.todos.contentprovider.MyTodoContentProvider;
import de.vogella.android.todos.database.TodoTable;
/*
* TodoDetailActivity allows to enter a new todo item
* or to change an existing
*/
public class TodoDetailActivity extends Activity {
private Spinner mCategory;
private EditText mTitleText;
private EditText mBodyText;
private Uri todoUri;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.todo_edit);
mCategory = (Spinner) findViewById(R.id.category);
mTitleText = (EditText) findViewById(R.id.todo_edit_summary);
mBodyText = (EditText) findViewById(R.id.todo_edit_description);
Button confirmButton = (Button) findViewById(R.id.todo_edit_button);
Bundle extras = getIntent().getExtras();
// check from the saved Instance
todoUri = (bundle == null) ? null : (Uri) bundle
.getParcelable(MyTodoContentProvider.CONTENT_ITEM_TYPE);
// Or passed from the other activity
if (extras != null) {
todoUri = extras
.getParcelable(MyTodoContentProvider.CONTENT_ITEM_TYPE);
fillData(todoUri);
}
confirmButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
if (TextUtils.isEmpty(mTitleText.getText().toString())) {
makeToast();
} else {
setResult(RESULT_OK);
finish();
}
}
});
}
private void fillData(Uri uri) {
String[] projection = { TodoTable.COLUMN_SUMMARY,
TodoTable.COLUMN_DESCRIPTION, TodoTable.COLUMN_CATEGORY };
Cursor cursor = getContentResolver().query(uri, projection, null, null,
null);
if (cursor != null) {
cursor.moveToFirst();
String category = cursor.getString(cursor
.getColumnIndexOrThrow(TodoTable.COLUMN_CATEGORY));
for (int i = 0; i < mCategory.getCount(); i++) {
String s = (String) mCategory.getItemAtPosition(i);
if (s.equalsIgnoreCase(category)) {
mCategory.setSelection(i);
}
}
mTitleText.setText(cursor.getString(cursor
.getColumnIndexOrThrow(TodoTable.COLUMN_SUMMARY)));
mBodyText.setText(cursor.getString(cursor
.getColumnIndexOrThrow(TodoTable.COLUMN_DESCRIPTION)));
// always close the cursor
cursor.close();
}
}
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
saveState();
outState.putParcelable(MyTodoContentProvider.CONTENT_ITEM_TYPE, todoUri);
}
@Override
protected void onPause() {
super.onPause();
saveState();
}
private void saveState() {
String category = (String) mCategory.getSelectedItem();
String summary = mTitleText.getText().toString();
String description = mBodyText.getText().toString();
// only save if either summary or description
// is available
if (description.length() == 0 && summary.length() == 0) {
return;
}
ContentValues values = new ContentValues();
values.put(TodoTable.COLUMN_CATEGORY, category);
values.put(TodoTable.COLUMN_SUMMARY, summary);
values.put(TodoTable.COLUMN_DESCRIPTION, description);
if (todoUri == null) {
// New todo
todoUri = getContentResolver().insert(
MyTodoContentProvider.CONTENT_URI, values);
} else {
// Update todo
getContentResolver().update(todoUri, values, null, null);
}
}
private void makeToast() {
Toast.makeText(TodoDetailActivity.this, "Please maintain a summary",
Toast.LENGTH_LONG).show();
}
}
تست اپلیکیشن
جهت حذف آیتم دلخواه از لیست کافی است بر روی آن طولانی مدت کلیک نمایید.
آموزش محل ذخیره سازی دیتابیس SQLite
یک دستگاه استاندارد اندروید اجازه ی دسترسی و خواندن (read-access) با فایل دیتابیس را نمی دهد.
آموزش دسترسی به دیتابیس از راه دور به وسیله ی command line (Shell access)
adb shell
سپس دستور "cd" را برای تغییر پوشه ی دیتابیس فراخوانی نموده و با اجرای دستور "sqlite3" به دیتابیس متصل شوید. برای مثال در اپلیکیشن جاری دستورات لازم به شرح زیر است:
# Switch to the data directory
cd /data/data
# Our application
cd de.vogella.android.todos
# Switch to the database dir
cd databases
# Check the content
ls
# Assuming that there is a todotable file
# connect to this table
sqlite3 todotable.db
بهینه سازی اپلیکیشن
کد زیر این افزایش کارایی را به نمایش می گذارد.
db.beginTransaction();
try {
for (int i= 0; i< values.lenght; i++){
// TODO prepare ContentValues object values
db.insert(your_table, null, values);
// In case you do larger updates
yieldIfContededSafely()
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}