שלום שוב,
למי ששכח, הוקרה קצרה; הבלוג הזה מתבסס לחלוטין על עבודתי המשותפת עם +Ran Nachmany ו- +Amir Lazarovich על Developer lab בשם AndconLab שהעברנו בכנס MultiScreenX השנה.
בפוסט הקודם עסקנו במתן הסברים ודוגמאות לפעולת התקשורת עם השרת בעצמה ולכן קצת שיקרתי לכם, משום שנתתי לכם מימוש חלקי של ה-TransactionJob, הנה המימוש המלא:
private class TransactionJob implements Runnable {
private static final String TAG = "Service";
@Override
public void run() {
try {
HttpGet httpGet = new HttpGet(GET_EVENTS);
String responseBody = null;
try {
responseBody = mHttpClient.execute(httpGet, mResponseHandler);
} catch(Exception ex) {
ex.printStackTrace();
}
List<Event> events = null;
if(responseBody != null) {
events = JacksonUtils.sReadValue(responseBody, new TypeReference<List<Event>>() {}, false);
if (events != null) {
SQLiteDatabase db = new DatabaseHelper(CommunicationService.this.getApplicationContext(), DatabaseHelper.DB_NAME, null, DatabaseHelper.DB_VERSION).getWritableDatabase();
DBUtils.storeEvents(db, events);
//fire an intent
Intent intent = new Intent();
intent.setAction(RESULTS_ARE_IN);
CommunicationService.this.sendBroadcast(intent);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
למי שאין לו כח לחפש ע"מ להבין מה אני רוצה אז ההבדל הוא בתוספת של השורות האלה:
SQLiteDatabase db = new DatabaseHelper(CommunicationService.this.getApplicationContext(), DatabaseHelper.DB_NAME, null, DatabaseHelper.DB_VERSION).getWritableDatabase();
DBUtils.storeEvents(db, events);
לחלקיכם בוודאי עולה השאלה: מה זה משנה? ואולי אפילו המחשבה אתה צוחק עלי? למה לי להאט את הביצוע עם התעסקות ב-data base?
ובכן, על מנת לענות על השאלה הזו אני אקח צעד אחד אחורה.
בדרך כלל במדריכים מעין אלה אתם רגילים לראות את הדוגמא הטקטית, כלומר: AsyncTask שמבצע פעולת הבאה ופרסור של הבקשה מהשרת ופחות או יותר זהו, מה שאני רוצה להראות בסדרת הפוסטים הללו זו הדוגמא האסטרטגית שיכולה להיות מתוארת בצורה הבאה: "אל אילו קרבות עלי לוותר מראש על מנת לנצח במלחמה?"
זהו מקרה קלאסי בו רוב המפתחים בוחרים לוותר על שימוש ב-ContentProviders ע"מ להאיץ את ההורדה של המידע אבל בכך הם למעשה כופים על עצמם להוריד את המידע כל פעם מחדש ולכן האפליקציה עלולה להרגיש איטית ומסורבלת יותר ובנוסף הם גם מבזבזים את סוללת המכשיר של המשתמש תוך כדי שהם משתמשים ביותר תעבורת אינטרנט (שעלולה גם לעלות כסף ממשי למשתמש).
לכן, בואו נתחיל במתי אני קורא לתהליך הזה ומתי אני פשוט לוקח את המידע הזמין לי, לשם כך הנה מימוש ה-Activity:
public class MainActivity extends SherlockActivity implements OnItemClickListener{
private ListView mList;
private ProgressDialog mProgressDialog;
private BroadcastReceiver mUpdateReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
mList = (ListView) findViewById(R.id.list);
mList.setOnItemClickListener(this);
}
@Override
protected void onPause() {
super.onPause();
if (null != mUpdateReceiver) {
unregisterReceiver(mUpdateReceiver);
mUpdateReceiver = null;
}
}
@Override
protected void onResume() {
super.onResume();
mUpdateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equalsIgnoreCase(CommunicationService.RESULTS_ARE_IN)) {
new lecturesLoader().execute((Void) null);
}
if (null != mProgressDialog)
mProgressDialog.dismiss();
}
};
final IntentFilter filter = new IntentFilter();
filter.addAction(CommunicationService.RESULTS_ARE_IN);
registerReceiver(mUpdateReceiver, filter);
new lecturesLoader().execute((Void)null);
}
@Override
public void onItemClick(AdapterView<?> list, View view, int position, long id) {
Intent i = new Intent(this,SingleLectureActivity.class);
i.putExtra(SingleLectureActivity.EXTRA_LECTURE_ID, id);
startActivity(i);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getSupportMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_refresh:
refreshList(true);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void refreshList(boolean showProgressbar) {
if (showProgressbar) {
mProgressDialog = ProgressDialog.show(this, getString(R.string.progress_dialog_starting_title), getString(R.string.progress_dialog_starting_message));
}
Intent i = new Intent(this,CommunicationService.class);
startService(i);
}
////////////////////////////////
// Async task that queries the DB in background
////////////////////////////////
private class lecturesLoader extends AsyncTask<Void, Void, Cursor> {
private SQLiteDatabase db;
@Override
protected Cursor doInBackground(Void... params) {
db = new DatabaseHelper(MainActivity.this.getApplicationContext(), DatabaseHelper.DB_NAME,null , DatabaseHelper.DB_VERSION).getReadableDatabase();
return DBUtils.getAllLectures(db);
}
@Override
protected void onPostExecute(Cursor result) {
if (0 == result.getCount()) {
//we don't have anything in our DB, force network refresh
refreshList(true);
}
else {
LecturesAdapter adapter = (LecturesAdapter) mList.getAdapter();
if (null == adapter) {
adapter = new LecturesAdapter(MainActivity.this.getApplicationContext(), result);
mList.setAdapter(adapter);
}
else {
adapter.changeCursor(result);
}
}
db.close();
}
}
}
הסבר קצר למה שאתם רואים הוא כזה:
לא הבנתם כל-כך את החלק של התפעול של ה-database? ובכן... בשביל זה יהיה הפוסט הבא :)
אני רוצה להודות שוב ל- +Ran Nachmany ו- +Amir Lazarovich אשר רוב הקוד שראיתם הוא שלהם ולהפנות אתכם לקוד המקור המלא של AndConLab כאן: https://github.com/RanNachmany/AndconLab
חוצמזה אני אשמח להשתמש בבמה הזו ע"מ להזמין אתכם להצטרף לרשת קבוצות ה-GDG שיש לנו ברחבי הארץ:
- האפליקציה עולה.
- ה-Broadcast intent עבור הפעולה "RESULTS_ARE_IN" נרשם.
- ה-AsyncTask בשם LectureLoader מאותחל והוא בודק האם יש לנו מידע שמור ב-database כאשר אם כן האפליקציה מאותחלת מהמידע הקיים אחרת היא קוראת ל-refreshList שמתחילה את ה-Service עליו דיברתי בפוסט הקודם.
- (באם אותחל ה-Service העונה לשם CommunicationService אזי בקבלת המידע הוא אינו מטריח את ה-UI כי אם מעדכן את ה-database ושולח את ה-Broadcast intent שנרשם בסעיף #2.
עכשיו אחרי שהבנתם למה אז הנה המימוש של הטיפול ב-database:
public class DBUtils {
private static final String TAG = "DBUtils";
//select lecturerImage from assets where lectureVideoId='Y4UMzOWcgGQ';
/**
* Create DB table
*
* @param db Reference to the underlying database
* @param sb Clears any existing values before starting to append new values
* @param tableName The name of the DB table
* @param columns Tuples of column names and their corresponding type and properties. This field must be even for that same
* reason. I.e. "my_column", "INTEGER PRIMARY KEY AUTOINCREMENT", "my_second_column", "VARCHAR(255)"
*/
public static void createTable(SQLiteDatabase db, StringBuilder sb, String tableName, String... columns) {
if (columns.length % 2 != 0) {
throw new IllegalArgumentException(
"Columns length should be even since each column is followed by its corresponding type and properties");
}
StringUtils.clearBuffer(sb);
// Prepare table
sb.append("CREATE TABLE ");
sb.append(tableName);
sb.append(" (");
// Parse all columns
int length = columns.length;
for (int i = 0; i < length; i += 2) {
sb.append(columns[i]);
sb.append(" ");
sb.append(columns[i + 1]);
if (i + 2 < length) {
// Append comma only if this isn't the last column
sb.append(", ");
}
}
sb.append(");");
// Create table
db.execSQL(sb.toString());
}
/**
* Drop table if exists in given database
*
* @param db Reference to the underlying database
* @param tableName The table name of which we try to drop
*/
public static void dropTable(SQLiteDatabase db, String tableName) {
dropTable(db, new StringBuilder(), tableName);
}
/**
* Drop table if exists in given database
*
* @param db Reference to the underlying database
* @param sb Clears any existing values before starting to append new values
* @param tableName The table name of which we try to drop
*/
public static void dropTable(SQLiteDatabase db, StringBuilder sb, String tableName) {
StringUtils.clearBuffer(sb);
sb.append("DROP TABLE IF EXISTS ");
sb.append(tableName);
// Drop table
db.execSQL(sb.toString());
}
/**
* Stores events and their lectures and speakers in db
* @param db - Writeable SQLITE DB
* @param events - events to be stored
* @return
*/
public static boolean storeEvents(SQLiteDatabase db, List<Event> events) {
db.beginTransaction();
ContentValues cv;
List<Lecture> lectures;
List<Speaker> speakers;
long eventId;
for (Event event : events) {
//store event
cv = event.getContentValues();
db.replace(Event.TABLE_NAME, null, cv);
eventId = event.getId();
//loop through all lectures
lectures = event.getLectures();
for (Lecture lecture : lectures) {
//set event id
lecture.setEventId(eventId);
cv = lecture.getContentValues();
db.replace(Lecture.TABLE_NAME, null, cv);
//remove all speakers from this lecture
clearLectureSpeakers(db, lecture);
//loop through all the speakers
speakers = lecture.getSpeakers();
for (Speaker speaker : speakers) {
//store speaker in db
cv = speaker.getContentValues();
db.replace(Speaker.TABLE_NAME, null, cv);
//add speaker to this lecture
addSpeakerToLecture(db, speaker, lecture);
}
}
}
db.setTransactionSuccessful();
db.endTransaction();
return true;
}
private static void addSpeakerToLecture(SQLiteDatabase db, Speaker speaker, Lecture lecture) {
ContentValues cv = new ContentValues();
cv.put(DatabaseHelper.PAIR_LECTURE_ID, lecture.getId());
cv.put(DatabaseHelper.PAIR_SPEAKER_ID, speaker.getId());
db.insert(DatabaseHelper.LECTURE_SPEAKER_PAIT_TABLE, null, cv);
}
private static void clearLectureSpeakers(SQLiteDatabase db, Lecture lecture) {
db.delete(DatabaseHelper.LECTURE_SPEAKER_PAIT_TABLE, DatabaseHelper.PAIR_LECTURE_ID + "=" + lecture.getId(), null);
}
/**
* Fetch all events from DB
* @param db
* @return cursor holding id, name, description and logo url
*/
public static Cursor getEventsCurosr(SQLiteDatabase db) {
String[] cols = new String[] {
Event.COLUMN_NAME_ID,
Event.COLUMN_NAME_NAME,
Event.COLUMN_NAME_DESCRIPTION,
Event.COLUMN_NAME_LOGO_URL
};
Cursor c;
c = db.query(Event.TABLE_NAME, cols, null, null, null, null, Event.COLUMN_NAME_START_DATE + " DESC");
return c;
}
public static Cursor getLecturesByEventId(SQLiteDatabase db, long eventId) {
String[] cols = new String[] {
Lecture.COLUMN_NAME_ID,
Lecture.COLUMN_NAME_NAME,
Lecture.COLUMN_NAME_DESCRIPTION,
};
Cursor c;
c = db.query(Lecture.TABLE_NAME, cols, Lecture.COLUMN_NAME_EVENT_ID +" = " +eventId, null, null, null, Lecture.COLUMN_NAME_NAME);
return c;
}
public static Cursor getAllLectures (SQLiteDatabase db) {
String[] cols = new String[] {
Lecture.COLUMN_NAME_ID,
Lecture.COLUMN_NAME_NAME,
Lecture.COLUMN_NAME_DESCRIPTION,
Lecture.COLUMN_NAME_DURATION
};
return db.query(Lecture.TABLE_NAME, cols, null, null, null, null, Lecture.COLUMN_NAME_EVENT_ID + " DESC");
}
/**
* Fetches a lecture from db
* @param db
* @param id
* @return Lecture object or null if no lecture found.
*/
public static Lecture getLectureById (SQLiteDatabase db, long id) {
Cursor c = db.query(Lecture.TABLE_NAME, null, Lecture.COLUMN_NAME_ID + "=" + id, null, null, null, null);
Lecture lecture = new Lecture();
if (c.moveToNext()) {
lecture.buildFromCursor(c);
c.close();
return lecture;
}
return null;
}
public static ArrayList<Speaker> getSpeakersByLectureId (SQLiteDatabase db, long id) {
ArrayList<Speaker> speakers = new ArrayList<Speaker>();
String select = "SELECT * FROM " + Speaker.TABLE_NAME +" WHERE " + Speaker.COLUMN_NAME_ID +" IN ("+
" SELECT " + DatabaseHelper.PAIR_SPEAKER_ID + " FROM " + DatabaseHelper.LECTURE_SPEAKER_PAIT_TABLE + " WHERE " + DatabaseHelper.PAIR_LECTURE_ID + " = " +id +")";
Cursor c = db.rawQuery(select, null);
Speaker speaker;
while (c.moveToNext()) {
speaker = new Speaker();
speaker.buildFromCursor(c);
speakers.add(speaker);
}
c.close();
return speakers;
}
}
והנה ה-database helper:
public class DatabaseHelper extends SQLiteOpenHelper{
public static final String DB_NAME = "db";
public static final int DB_VERSION = 7;
public static final String LECTURE_SPEAKER_PAIT_TABLE = "lecture_speaker_pair";
public static final String PAIR_LECTURE_ID = "lecture_id";
public static final String PAIR_SPEAKER_ID = "speaker_id";
public DatabaseHelper(Context context, String name, CursorFactory factory,
int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
// create events table
StringBuilder sb = new StringBuilder();
try {
DBUtils.createTable(db, sb,
Event.TABLE_NAME,
Event.COLUMN_NAME_ID, "INTEGER PRIMARY KEY ",
Event.COLUMN_NAME_NAME, "TEXT",
Event.COLUMN_NAME_DESCRIPTION, "TEXT",
Event.COLUMN_NAME_START_DATE, "TEXT",
Event.COLUMN_NAME_END_DATE, "TEXT",
Event.COLUMN_NAME_LOGO_URL, "TEXT",
Event.COLUMN_NAME_WEBSITE_URL, "TEXT");
//create lectures table
DBUtils.createTable(db, sb,
Lecture.TABLE_NAME,
Lecture.COLUMN_NAME_ID, "INTEGER PRIMARY KEY ",
Lecture.COLUMN_NAME_DESCRIPTION, "TEXT",
Lecture.COLUMN_NAME_DURATION, "TEXT",
Lecture.COLUMN_NAME_EVENT_ID, "INTEGER",
Lecture.COLUMN_NAME_NAME, "TEXT",
Lecture.COLUMN_NAME_SLIDES_URL, "TEXT",
Lecture.COLUMN_NAME_VIDEO_URL, "TEXT",
Lecture.COLUMN_NAME_YOUTUBE_ASSET_ID, "TEXT");
//create speakers table
DBUtils.createTable(db, sb,
Speaker.TABLE_NAME,
Speaker.COLUMN_NAME_ID, "INTEGER PRIMARY KEY",
Speaker.COLUMN_FIRST_NAME, "TEXT",
Speaker.COLUMN_LAST_NAME, "TEXT",
Speaker.COLUMN_NAME_BIO, "TEXT",
Speaker.COLUMN_NAME_IMAGE_URL, "TEXT");
//create lecture <-> speaker pair table
DBUtils.createTable(db, sb, LECTURE_SPEAKER_PAIT_TABLE,
PAIR_LECTURE_ID, "TEXT" ,
PAIR_SPEAKER_ID, "TEXT");
}
catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
StringBuilder sb = new StringBuilder();
DBUtils.dropTable(db,sb , Event.TABLE_NAME);
DBUtils.dropTable(db,sb , Lecture.TABLE_NAME);
DBUtils.dropTable(db,sb , Speaker.TABLE_NAME);
DBUtils.dropTable(db,sb , LECTURE_SPEAKER_PAIT_TABLE);
onCreate(db);
}
}
לא הבנתם כל-כך את החלק של התפעול של ה-database? ובכן... בשביל זה יהיה הפוסט הבא :)
אני רוצה להודות שוב ל- +Ran Nachmany ו- +Amir Lazarovich אשר רוב הקוד שראיתם הוא שלהם ולהפנות אתכם לקוד המקור המלא של AndConLab כאן: https://github.com/RanNachmany/AndconLab
חוצמזה אני אשמח להשתמש בבמה הזו ע"מ להזמין אתכם להצטרף לרשת קבוצות ה-GDG שיש לנו ברחבי הארץ:
- http://www.meetup.com/GDG-Herzeliya/
- http://www.meetup.com/GDG-Tel-Aviv/
- http://www.meetup.com/GDG-Beer-Sheva/
- http://www.meetup.com/GDG-Haifa/
- חברי GDG נצרת! אנא שלחו לי קישור :)
הבלוג פורסם גם כאן - http://iandroid.co.il/dr-iandroid/archives/14998
No comments:
Post a Comment