کانال بله, جهت پشتیبانی و اطلاع رسانی کانال بله, جهت پشتیبانی و اطلاع رسانی
عضویت

کار با پایگاه داده 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() پارامترهای زیر را به عنوان ورودی می گیرد.

جدول پارامترهای ورودی متد query()
شرح
پارامتر
اسم جدولی که از آن کوئری گرفته می شود.
String dbName
لیستی از ستون ها که باید در خروجی بازگردانده شوند. با ارسال مقدار "null" به عنوان پارامتر ورودی، تمامی ستون ها در خروجی بازگردانی می شوند.
String[] columnNames
عبارت شرطی Where که به مثابه ی فیلتری جهت واکشی داده های مورد نیاز استفاده می شود. ارسال مقدار null، تمامی داده ها را در خروجی برمی گرداند.
String whereClause
می توانید ?s را در عبارت "whereClause"" نیز بکار ببرید. این مکان نگهدار (placeholder) سپس با مقادیر واقعی از آرایه ی selectionArgs جایگزین می شوند.
String[] selectionArgs
می توانید ?s را در عبارت "whereClause"" نیز بکار ببرید. این مکان نگهدار (placeholder) سپس با مقادیر واقعی از آرایه ی selectionArgs جایگزین می شوند.
String[] selectionArgs
یک فیلتر است که نحوه ی گروه بندی سطرها را مشخص می کند. ارسال
String[] groupBy
مقدار null به عنوان پارامتر سبب می شود که سطرها مرتب نشوند.
این پارامتر نتیجه را به آن گروه از سطرها که با شرط اعمال شده منطبق هستند، محدود می کند. به عبارت دیگر برای اعمال شرط و محدود کردن خروجی در گروه ها مورد استفاده قرار می گیرد.
String[] having
ستون هایی از جدول که برای مرتب سازی داده ها مورد استفاده قرار می گیرد را شامل می شود. با ارسال null به عنوان پارامتر هیچ مرتب سازی صورت نمی گیرد.
String[] orderBy

گر شرطی برای اعمال و محدودسازی نتایج نیاز نباشد، می توانید 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 تعامل نخواهد داشت.
در پایان اپلیکیشن به صورت زیر خواهد بود:

استفاده از SQLite

گفتنی است که استفاده از الگو 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 در res/layout_ folder را به صورت زیر ویرایش نمایید. این layout دو دکمه برای افزودن/حذف comment ها و یک ListView (لیست ساده) جهت نمایش comment های جاری تعریف می کند. متن comment بعدها در کلاس activity و به وسیله ی یکrandom generator تولید خواهد شد.



                
                
                
    
                

کلاس TestDatabaseActivity را به صورت زیر ویرایش نمایید. برای نمایش آسان لیست ساده ای از اطلاعات می توانید از یک ListActivity استفاده نمایید.

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 values = datasource.getAllComments();
                // use the SimpleCursorAdapter to show the
                // elements in a ListView
                ArrayAdapter adapter = new ArrayAdapter(this,                             android.R.layout.simple_list_item_1, values);
                setListAdapter(adapter);
        }
        // Will be called via the onClick attribute
        // of the buttons in main.xml
        public void onClick(View view) {
                @SuppressWarnings("unchecked")
                ArrayAdapter adapter = (ArrayAdapter) getListAdapter();
                Comment comment = null;
                switch (view.getId()) {
                case R.id.add:
                        String[] comments = new String[] { "Cool", "Very nice", "Hate it" };
                        int nextInt = new Random().nextInt(3);
                        // save the new comment to the database
                        comment = datasource.createComment(comments[nextInt]);
                        adapter.add(comment);
                        break;
                case R.id.delete:
                        if (getListAdapter().getCount() > 0) {
                                comment = (Comment) getListAdapter().getItem(0);
                                datasource.deleteComment(comment);
                                adapter.remove(comment);
                        }
                        break;
                }
                adapter.notifyDataSetChanged();
        }
        @Override
        protected void onResume() {
                datasource.open();
                super.onResume();
        }
        @Override
        protected void onPause() {
                datasource.close();
                super.onPause();
        }
}

آموزش نصب و اجرای اپلیکیشن

اپلیکیشن خود را نصب نموده، دکمه های Add و Delete را کلیک نمایید. سپس اپلیکیشن را restart کرده و اطمینان حاصل نمایید که داده ها پس از راه اندازی مجدد همچنان در جای خود هستند.

آموزش مفهوم Content Provider

Content Provider چیست و چه کاربردی دارد؟

جهت به اشتراک گذاری داده ها بین چندین اپلیکیشن، می توانید از Content Provider استفاده نمایید. می توانید به Provider به چشم یک منبع داده ای نگاه کنید که اطلاعات موجود در آن به اپلیکیشنی که از آن استفاده می کند، وابسته نمی باشد. این امکان وجود دارد که از Provider مانند یک دیتابیس کوئری بگیرید، داده های آن را بروز رسانی و حتی حذف نمایید.علاوه بر آن 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

برای دسترسی به content provider می بایست ابتدا پیشوند متعارف content:// و سپس namespace مربوط به provider را ارائه نمایید (namespace همان نام content provider حامل داده های مورد نظر می باشد). لازم است namespace را داخل فایل manifest، تگ provider و در attribute (خصیصه) android:authorities تعریف و مقداردهی نمایید. مثال: content://test/.
در زیر بخش ها مختلف یک URI را شرح می دهیم.
قالب و ساختار کلی URI به این صورت می باشد: :////

  1. standard prefix یا پیشوندی که همیشه ثابت است: content://.
  2. authority یا نام content provider.
  3. data path نوع درخواست را مشخص می کند.
  4. id رکورد مورد نظر را مشخص می کند.

URI پایه امکان دسترسی به مجموعه ای از منابع (برای مثال کل یک جدول) را فراهم می کند. چنانچه در بخش نهایی URI شناسه ی نمونه ذکر شود، در آن صورت تنها content ای که id آن ذکر شده، قابل دسترسی می باشد. مثال: content://test/2.

آموزش دسترسی به content provider

از آنجایی که داشتن URI های یک provider برای دسترسی به آن ضروی است، توصیه می شود URI ها را داخل constant هایی با سطح دسترسی public قرار داده، آن ها را به صورت مستند در اختیار دیگر توسعه دهندگان قرار دهید.
بسیاری از منابع داده ای اندروید همچون contacts (اطلاعات مخاطبین) از طریق content provider قابل دسترسی هستند.

آموزش پیاده سازی content provider اختصاصی

به منظور ایجاد 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() اشاره کرد. در صورتی که از متد خاصی
پشتیبانی نمی کنید، توصیه می شود UnsupportedOperationException() را فراخوانی نمایید.
متد query() در خروجی یک آبجکت Cursor برمی گرداند.

آموزش Content provider و مبحث امنیت

تا ویرایش 4.2 اندروید، content provider به صورت پیش فرض برای دیگر اپلیکیشن های اندرویدی قابل دسترسی می باشد. اما از ورژن 4.2 به بعد اندروید، برای دسترسی به content provider لازم است آن را به صورت صریح export نمایید.
به منظور تنظیم قابلیت رویت و دسترسی content provider خود، کافی است پارامتر android:exported=false|true را در تعریف content provider خود داخل فایل AndroidManifest.xml ذکر نمایید.

نکته:

توصیه می شود پارامتر android:exported را همیشه در فایل تنظیمات قید کنید تا رفتار اپلیکیشن در ورژن های مختلف اندروید یکسان باشد.

آموزش Thread safety (برطرف سازی مشکل همزمانی با استفاده از کلیدواژه ی synchronized)

چنانچه مستقیما با دیتابیس تعامل داشته باشید و همزمان از چند thread در دیتابیس مقادیری نوشته شوند (از چند thread همزمان چندین writer وجود داشته باشد) در آن صورت طبیعتا با مشکل مدیریت همروندی و concurrency مواجه می شوید.
یک content provider می تواند همزمان از چندین برنامه مورد دسترسی قرار گیرد. به این دلیل لازم است دسترسی thread-safe را پیاده سازی نمایید. آسان ترین راه، استفاده از کلیدواژه ی synchronized در مقابل تمامی متدهای provider است. بدین وسیله تنها یک thread در لحظه می تواند به این متدها دسترسی داشته باشد.
(thread-safe = دسترسی چند thread به یک data structure بدون اینکه در پروسه ی کاری thread ها اختلالی به وجود آید).
اگر لزومی ندارد که اندروید دسترسی به provider را sync کند، مقدار خصیصه (attribute) android:multiprocess= را در تگ ، داخل فایل تنظیمات اپلیکیشن خود برابر true قرار دهید. این کار سبب می شود در هر یک از process های مربوط به کلاینت (پاسخ دهی به درخواست های کاربر)، یک نمونه از provider ایجاد شده و نیاز به اجرای IPC به کلی از میان برداشته شود (IPC = عبارت است از مجموعه ای از روش ها برای تبادل داده بین چندین پردازش خرد و کوچک/thread. بنابراین هر یک از این روشها می تواند برای ارتباط بین یک یا چند پردازش بکار رود).

آموزش: استفاده ی کاربردی از ContentProvider

مثال زیر از content provider آماده ی اپلیکیشن People استفاده می کند.

آموزش ایجاد contact ها (مخاطبین) در محیط شبیه ساز

برای این مثال لازم است تعدادی contact ثابت داشته باشید. منوی home را انتخاب نموده و سپس المان People را برای ایجاد contacts انتخاب کنید.

ایجاد contact ها (مخاطبین) در محیط شبیه ساز

اپلیکیشن از شما می پرسد آیا میلی به ورود و login در برنامه دارید یا خیر. می توانید login کنید یا "Not now" را انتخاب نمایید. حال بر روی دکمه ی "Create a new contact" کلیک نمایید. می توانید local contatcs را ایجاد کنید.

ایجاد contact ها (مخاطبین) در محیط شبیه ساز ایجاد contact ها (مخاطبین) در محیط شبیه ساز

پس از اینکه اولین contact را ایجاد کردید، اپلیکیشن به شما اجازه می دهد تا با کلیک بر روی دکمه ی "+" contact های بیشتری ایجاد کنید. در پایان چندین contact به اپلیکیشن خود اضافه کرده اید.

آموزش استفاده از Contact Content Provider

یک پروژه و activity جدید اندروید به ترتیب به نام های de.vogella.android.contentprovider و ContactsActivity ایجاد نمایید.
فایل layout مربوطه را نیز در آدرس
res/layout_ folder ویرایش نمایید. ID المان TextView را به contactview تغییر داده و متن پیش فرض را حذف نمایید.
فایل layout خروجی می بایست دارای ظاهری مشابه زیر باشد.



                

از آنجایی که تمامی اپلیکیشن ها نباید به اطلاعات شخصی مخاطبین دسترسی داشته باشند، لازم است برای دستیابی به ContentProvider contact، در فایل تنظیمات permission خاصی تنظیم نمایید. برای این منظور ابتدا فایل AndroidManifest.xml را باز نموده و تب Permissions را انتخاب کنید. در این تب، بر روی دکمه ی Add کلیک کرده و Uses Permission را انتخاب نمایید. از لیست کشویی (drop-down) حاضر، آیتم android.permission.READ_CONTACTS را انتخاب کنید.
پیاده سازی 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);
        }
}

زمانی که اپلیکیشن را اجرا کنید، داده های لازم از کامپوننت نرم افزاری ContentProvider که اطلاعات اپلیکیشن People را حمل می کند، بارگذاری شده و نهایتا در المان رابط کاربری TextView به نمایش گذاشته می شود. جهت ارائه ی چنین اطلاعاتی به کاربر، توسعه دهندگان معمولا از لیست ساده که توسط ListView پیاده سازی می شود، استفاده می کنند.

                    Unresolved directive in 001_article.adoc - include::../AndroidBackgroundProcessing/content/120_loader.adoc[]

آموزش Cursor ها و Loader ها

یکی از مشکلات و چالش های دسترسی به دیتابیس و خواندن اطلاعات، پایین بودن سرعت آن است. بعلاوه، اپلیکیشن می بایست life cycle کامپوننت ها را در نظر گرفته و آن را مدیریت کند. برای مثال می توان به باز کردن و بستن cursor در صورت تغییر در تنظیمات و نحوه ی پیکربندی اشاره کرد.
تا ویرایش 3.0 سیستم عامل اندروید، توسعه دهندگان با فراخوانی متد managedQuery() در کلاس های activity، چرخه ی حیات یا lifecycle کامپوننت ها را اداره می کردند.
اما از ویرایش 3.0 به بعد اندروید، استفاده از این متد دیگر توصیه نمی شود و توسعه دهنده برای دسترسی به ContentProvider می بایست از چارچوب نرم افزاری (framework) Loader استفاده کند.
کلاس SimpleCursorAdapter که همراه با ListViews نیز قابل استفاده می باشد، متد swapCursor() را در اختیار شما قرار می دهد. کلاس Loader اپلیکیشن شما می تواند با فراخوانی این متد، Cursor را در متد onLoadFinished() بروز رسانی کند.
کلاس CursorLoader، آبجکت Cursor را پس از تغییر در تنظیمات و نحوه ی پیکربندی مجددا متصل می کند.

آموزش: SQLite، ContentProvider و Loader با پیاده سازی اختصاصی

مرور کلی

نمونه اپلیکیشنی که در زیر مشاهده می کنید، در Android Market نیز به صورت آماده در دسترس است. برای اینکه کاربران بیشتری بتوانند از آن استفاده کنند، اپلیکیشن به ویرایش 2.3 سیستم عامل اندروید downport شده و بر روی آن ها قابل اجرا می باشد. در صورتی که اپلیکیشن بارکد خوان را بر روی دستگاه خود نصب کرده باشید، می توانید با اسکن کد QR زیر به سرعت اپلیکیشن مورد نظر را در Android Market پیدا کنید. لازم به توضیح است که اپلیکیشن نام برده در ورژن های مختلف اندروید ممکن است ظاهر و رفتار متفاوتی داشته باشد. برای مثال ممکن است بجای OptionMenu در اپلیکیشن ActionBar را داشته باشید و پوسته (theme) برنامه کمی متفاوت باشد.

SQLite، ContentProvider و Loader با پیاده سازی اختصاصی

در بخش آموزشی حاضر یک اپلیکیشن به نام "To-do" ایجاد خواهید نمود. این برنامه به کاربر اجازه می دهد تا task یا کارهایی که باید انجام شوند را در لیستی وارد نماید. آیتم های ورودی در دیتابیس SQLite ذخیره شده و از طریق ContentProvider قابل دسترسی می باشد.
در نمونه اپلیکیشن جاری، کارها یا task هایی که داخل لیست درج می شوند، "todos" نام دارند.
اپلیکیشن مورد نظر دارای دو activity می باشد که یکی برای مشاهده ی آیتم های todo و دیگری ویژه ی ایجاد و ویرایش آیتم مورد نظر تعبیه شده است. دو activity این اپلیکیشن از طریق آبجکت های Intent با یکدیگر تعامل کرده و اطلاعات مورد نیاز را رد و بدل می نمایند.
به منظور بارگذاری و مدیریت Cursor به طور ناهمزمان (asynch)، activity اصلی اپلیکیشن از کلاس Loader بهره می گیرد.
در پایان اپلیکیشن دارای ظاهری مشابه زیر خواهد بود.

SQLite، ContentProvider و Loader با پیاده سازی اختصاصی SQLite، ContentProvider و Loader با پیاده سازی اختصاصی

آموزش ایجاد پروژه

یک پروژه و activity جدید به ترتیب به نام های de.vogella.android.todos و TodosOverviewActivity ایجاد نمایید. حال activity دیگری به نام TodoDetailActivity در پروژه ی جاری ایجاد نمایید.

آموزش تعریف کلاس های مدیریت Database

پکیج de.vogella.android.todos.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);
        }
}

کلاس TodoDatabaseHelper زیر را ایجاد نمایید. این کلاس از SQLiteOpenHelper ارث برده و متدهای static کلاس کمکی (helper) TodoTable فراخوانی می کند.

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 جهت دسترسی به داده های مورد نیاز پیاده سازی خواهید کرد.برای دسترسی به دیتابیس این بار مانند مثال قبلی از یک DAO (آبجکت دسترسی به داده) استفاده نخواهید نمود، بلکه یک

آموزش ایجاد ContentProvider

ابتدا پکیج de.vogella.android.todos.contentprovider را ایجاد نمایید. سپس کلاس MyTodoContentProvider را ایجاد کنید که از ContentProvider ارث بری (extend) می کند.

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 requestedColumns = new HashSet(
                                        Arrays.asList(projection));
                        HashSet availableColumns = new HashSet(
                                        Arrays.asList(available));
                        // check if all columns which are requested are available
                        if (!availableColumns.containsAll(requestedColumns)) {
                                throw new IllegalArgumentException(
                                                "Unknown columns in projection");
                        }
                }
        }
}

MyTodoContentProvider توابع update()، insert()، delete() و query() را پیاده سازی می کند. این متدها به طور مستقیم از interface یا الگوی پیاده سازی SQLiteDatabase مشتق می شوند.
این کلاس همچنین با فراخوانی متدی به نام checkColumns()، اطمینان حاصل می کند که کوئری فقط و فقط ستون های معتبر و مجاز را درخواست می کند.
ContentProvider اپلیکیشن خود را در فایل AndroidManifest.xml ثبت نمایید.

<>
                
   

آموزش منابع (Resource ها)

اپلیکیشن جاری به منابع و محتوای متعددی احتیاج دارد. ابتدا یک منوی listmenu.xml در پوشه ی res/menu تعریف نمایید. اگر از ویزارد Android resource برای ایجاد فایل "listmenu.xml" استفاده نمایید، پوشه ی مورد نیاز به صورت خودکار برای شما ایجاد می شود. چنانچه فایل را خود به صورت دستی ایجاد کنید، در آن صورت پوشه را نیز باید خود بسازید.
این فایل XML برای تعریف ظاهر option menu در اپلیکیشن بکار می رود. با تنظیم مقدار android:showAsAction= بر روی"always"، آیتم مورد نظر در ActionBar اپلیکیشن به نمایش گذاشته می شود.



                
    

کاربر می تواند برای آیتم ها اولویت قرار دهد. برای priorities یک آرایه ی رشته ای ایجاد خواهید نمود.
ابتدا فایل priority.xml زیر را در پوشه ی res/values ایجاد نمایید.



                
                Urgent
                Reminder
    

لازم است برای اپلیکیشن خود ثابت های رشته ای متعددی تعریف کنید. فایل strings.xml تحت پوشه ی res/values را ویرایش نمایید.



                Hello World, Todo!
                Todo
                Currently there are no Todo items maintained
                Add Item
                Delete Todo
                Summary
                Delete Todo
                Summary
                Description
                Confirm

آموزش تعریف فایل های layout

سه فایل layout تعریف خواهید کرد. یکی از فایل های layout برای نمایش و تنظیم ظاهر سطر در لیست مورد استفاده قرار می گیرد و دو فایل دیگر توسط activity های اپلیکیشن فراخوانی می شوند.
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" را در پوشه ی res/layout_ ایجاد نمایید.



                
    
                
    

حال فایل todo_list.xml را ایجاد نمایید. این فایل ظاهر و چیدمان لیست را تعیین می کند.



                
    		
                

فایل todo_edit.xml را ایجاد کنید. این فایل layout برای ویرایش و تنظیم ظاهر آیتم های فردی todo در TodoDetailActivity مورد استفاده قرار می گیرد.



                
    
                
                
        
    
                
    
                

آموزش تنظیم Activity ها

کد activity های خود را به صورت زیر ویرایش کنید. ویرایش را از فایل TodosOverviewActivity.java آغاز نمایید.

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 {
        private static final int ACTIVITY_CREATE = 0;
        private static final int ACTIVITY_EDIT = 1;
        private static final int DELETE_ID = Menu.FIRST + 1;
        // private Cursor cursor;
        private SimpleCursorAdapter adapter;
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.todo_list);
                this.getListView().setDividerHeight(2);
                fillData();
                registerForContextMenu(getListView());
        }
        // create the menu based on the XML defintion
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
                MenuInflater inflater = getMenuInflater();
                inflater.inflate(R.menu.listmenu, menu);
                return true;
        }
        // Reaction to the menu selection
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
                switch (item.getItemId()) {
                case R.id.insert:
                        createTodo();
                        return true;
                }
                return super.onOptionsItemSelected(item);
        }
        @Override
        public boolean onContextItemSelected(MenuItem item) {
                switch (item.getItemId()) {
                case DELETE_ID:
                        AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
                                        .getMenuInfo();
                        Uri uri = Uri.parse(MyTodoContentProvider.CONTENT_URI + "/"
                                        + info.id);
                        getContentResolver().delete(uri, null, null);
                        fillData();
                        return true;
                }
                return super.onContextItemSelected(item);
        }
        private void createTodo() {
                Intent i = new Intent(this, TodoDetailActivity.class);
                startActivity(i);
        }
        // Opens the second activity if an entry is clicked
        @Override
        protected void onListItemClick(ListView l, View v, int position, long id) {
                super.onListItemClick(l, v, position, id);
                Intent i = new Intent(this, TodoDetailActivity.class);
                Uri todoUri = Uri.parse(MyTodoContentProvider.CONTENT_URI + "/" + id);
                i.putExtra(MyTodoContentProvider.CONTENT_ITEM_TYPE, todoUri);
                startActivity(i);
        }
        private void fillData() {
                // Fields from the database (projection)
                // Must include the _id column for the adapter to work
                String[] from = new String[] { TodoTable.COLUMN_SUMMARY };
                // Fields on the UI to which we map
                int[] to = new int[] { R.id.label };
                getLoaderManager().initLoader(0, null, this);
                adapter = new SimpleCursorAdapter(this, R.layout.todo_row, null, from,
                                to, 0);
                setListAdapter(adapter);
        }
        @Override
        public void onCreateContextMenu(ContextMenu menu, View v,
                        ContextMenuInfo menuInfo) {
                super.onCreateContextMenu(menu, v, menuInfo);
                menu.add(0, DELETE_ID, 0, R.string.menu_delete);
        }
        // creates a new loader after the initLoader () call
        @Override
        public Loader onCreateLoader(int id, Bundle args) {
                String[] projection = { TodoTable.COLUMN_ID, TodoTable.COLUMN_SUMMARY };
                CursorLoader cursorLoader = new CursorLoader(this,
                                MyTodoContentProvider.CONTENT_URI, projection, null, null, null);
                return cursorLoader;
        }
        @Override
        public void onLoadFinished(Loader loader, Cursor data) {
                adapter.swapCursor(data);
        }
        @Override
        public void onLoaderReset(Loader loader) {
                // data is not available anymore, delete reference
                adapter.swapCursor(null);
        }
}

سپس فایل TodoDetailActivity.java را به صورت زیر ویرایش کنید.

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();
        }
}

محتوای فایل AndroidManifest.xml در نهایت به صورت زیر خواهد بود.



                
                
                
                
                
                
            
        
                
        
                
        
    

تست اپلیکیشن

اپلیکیشن خود را راه اندازی کنید. با کلیک بر روی دکمه ی "Insert" در ActionBar طبیعتا باید بتوانید آیتم جدیدی را در لیست درج نمایید.
جهت حذف آیتم دلخواه از لیست کافی است بر روی آن طولانی مدت کلیک نمایید.

تست اپلیکیشن

به منظور ویرایش آیتم جاری در لیست، بر روی سطر میزبان آن کلیک نمایید. با این کار activity دوم راه اندازی می شود.

آموزش محل ذخیره سازی دیتابیس SQLite

SQLite دیتابیس را همراه با تمامی اطلاعات آن داخل یک فایل ذخیره می کند. در صورتی که به این فایل دسترسی دارید، می توانید به طور مستقیم با دیتابیس کار کنید. دسترسی مسقیم به فایل مزبور تنها در محیط شبیه ساز و دستگاه های root شده امکان پذیر می باشد.
یک دستگاه استاندارد اندروید اجازه ی دسترسی و خواندن (read-access) با فایل دیتابیس را نمی دهد.

آموزش دسترسی به دیتابیس از راه دور به وسیله ی command line (Shell access)

در محیط شبیه ساز یا دستگاه root شده اندروید، می توان از طریق پنجره ی فرمان یا command line به دیتابیس SQLite دسترسی داشت. جهت اتصال به دستگاه کافی است دستور زیر را در پنجره ی فرمان اجرا کنید.

adb shell

Command adb در پوشه ی نصب Android SDK و زیرپوشه ی "platform-tools" مسقر می باشد.
سپس دستور "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

پرکاربرد ترین دستورات SQLite به شرح زیر می باشند:

شرح
دستور
تمامی دستورات و آپشن ها را لیست می کند.
.help
از دستور sqlite3 خارج می شود.
.exit
دستورات CREATE که برای ایجاد جداول دیتابیس جاری فراخوانی شدند را لیست می کند.
.schema

بهینه سازی اپلیکیشن

تغییراتی که در SQLite اعمال می شوند همگی ACID هستند (Atomicity: تراکنش ها باید کامل اجرا شوند. Consistency: تمامی تراکنش ها باید دیتابیس را از یک حالت کامل و قابل قبول به یک حالت بی عیب و کامل دیگر منتقل کند. isolation: تراکنش هایی که موازی اجرا می شوند باید کاملا از هم مجزا بوده و در جامعیت دیتابیس اختلالی ایجاد نکند. Durability: کلیه ی تغییراتی که تراکنش ها بر روی دیتابیس اعمال می کنند همگی باید ذخیره شده و در صورت رخداد خطای ناگهانی آن ها را به حالت اول بازگرداند). در نتیجه تمامی عملیات update، insert و delete ای که بر روی پایگاه داده اجرا می شوند، از این چهار ویژگی برخوردار بوده و از آن ها تبعیت می کنند. در پی این ویژگی، به ناگذیر مقداری overhead و سربار در پردازش های دیتابیس وارد شده و کارایی را پایین می آورد. به همین جهت توصیه می شود عملیات update در SQLite را در قالب یک تراکنش (transaction) گنجانده (wrap) و پس از انجام موفقیت آمیز تمامی عملیات لازم تراکنش را ثبت نهایی (commit) نمایید. با این اقدام سرعت و کارایی اپلیکیشن به طور چشم گیری افزایش می یابد.
کد زیر این افزایش کارایی را به نمایش می گذارد.

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();
}

برای آپدیت های زیاد می بایست متد yieldIfContendedSafely() را فراخوانی نمایید. SQLite در طول اجرای تراکنش بر روی دیتابیس قفل می گذارد. حال اگر سرویس گیرنده ی دیگری از داده های مورد نظر کوئری بگیرد، با فراخوانی این متد، اندروید تراکنش را متوقف کرده و یک تراکنش جدید باز می کند. از این طریق فرایند دوم می تواند به داده ها دسترسی پیدا کند.

1395/12/05 20401 3352
رمز عبور : tahlildadeh.com یا www.tahlildadeh.com
نظرات شما

نظرات خود را ثبت کنید...