同步适配器模式(一)

来源:互联网 发布:家电售后服务软件 编辑:程序博客网 时间:2024/05/27 20:52
此篇文章为《打造高质量的Android应用 Android开发必知的50个诀窍》中的Hank23-同步适配器模式,主要模拟Gmail可以很好的处理在线和离线状态,给用户带来良好的用户体验。

Gmail通过同步适配器(AyncAdapter)实现上述功能,遗憾的是,尽管同步适配器是android提供的最好特性之一,但是却缺乏相应文档。

本文以what to do为例,实现前后端的代码,模拟离线和在线的状态,基本效果如下图:


这一部分先说客户端的离线实现方式。

1. 实现上面的ui效果,数据源为空时,显示暂无数据,虽然这样的效果实现方式有多种,这里还是提供一种高大上的方式,代码如下:
listView.setEmptyView(findViewById(R.id.kong));

<TextView    android:id="@+id/kong"    android:layout_width= "match_parent"    android:layout_height= "match_parent"    android:gravity="center"    android:text="暂无数据"    android:textSize="26sp" />

2. listView中的数据从数据库来,实现数据库的代码如下:
public class DatabaseHelper extends SQLiteOpenHelper {     public static final String DATABASE_NAME = "todo.db";     public static final int DATAASE_VERSION = 1;     public DatabaseHelper(Context context) {            super(context, DATABASE_NAME, null, DATAASE_VERSION);     }     @Override     public void onCreate(SQLiteDatabase db) {           String sql = "CREATE TABLE " + ToDoContentProvider.TODO_TABLE_NAME                     + " (" + ToDoContentProvider. COLUMN_ID                     + " INTEGER PRIMARY KEY AUTOINCREMENT, "                     + ToDoContentProvider. COLUMN_SERVER_ID + " INTEGER, "                     + ToDoContentProvider. COLUMN_TITLE + " LONGTEXT, "                     + ToDoContentProvider. COLUMN_STATUS_FLAG + " INTEGER);";           db.execSQL(sql);     }     @Override     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {           String sql = "drop table if exists todo";           db.execSQL(sql);           onCreate(db);     }}

3.Dao实现代码:dao中目前实现了增和删的操作,其关键代码是使用contentResolver操作内容提供者。dao其实可有可无,完全可以直接操作内容提供者。
public class ToDoDao {     private static final ToDoDao instance = new ToDoDao();     private ToDoDao() {     }     public static ToDoDao getInstance() {            return instance;     }     public void addNewToDo(ContentResolver contentResolver, ToDo todo, int flag) {           ContentValues cv = new ContentValues();           cv.put(ToDoContentProvider. COLUMN_SERVER_ID, todo.getId());           cv.put(ToDoContentProvider. COLUMN_TITLE, todo.getTitle());           cv.put(ToDoContentProvider. COLUMN_STATUS_FLAG, flag);          contentResolver.insert(ToDoContentProvider. CONTENT_URI, cv);     }     public void deleteToDo(ContentResolver contentResolver, int id) {          contentResolver.delete(ToDoContentProvider. CONTENT_URI,                     ToDoContentProvider. COLUMN_ID + "=" + id, null);     }}

4. 内容提供者代码
public class ToDoContentProvider extends ContentProvider {     public static final String TODO_TABLE_NAME = "todos";     // getCanonicalName获取类全名(包名.类名)     public static final String AUTHORITY = ToDoContentProvider.class                .getCanonicalName();     public static final String COLUMN_ID = "_id";     public static final String COLUMN_SERVER_ID = "server_id";     public static final String COLUMN_TITLE = "title";     public static final String COLUMN_STATUS_FLAG = "status_flag";     private static final int TODO = 1;     private static final int TODO_ID = 2;     private static HashMap<String, String> projectionMap;     private static UriMatcher sUriMatcher;     // 带id表示单个,不带id表示多个     public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.androidhacks.todo" ;     public static final String CONTENT_TYPE_ID = "vnd.android.cursor.item/vnd.androidhacks.todo" ;     public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY                + "/" + TODO_TABLE_NAME);     private DatabaseHelper dbHelper;     static {            // 根据内容uri判断执行流程            sUriMatcher = new UriMatcher(UriMatcher. NO_MATCH);            sUriMatcher.addURI( AUTHORITY, TODO_TABLE_NAME, TODO);            sUriMatcher.addURI( AUTHORITY, TODO_TABLE_NAME + "/#", TODO_ID);            projectionMap = new HashMap<>();            projectionMap.put( COLUMN_ID, COLUMN_ID);            projectionMap.put( COLUMN_SERVER_ID, COLUMN_SERVER_ID);            projectionMap.put( COLUMN_TITLE, COLUMN_TITLE);            projectionMap.put( COLUMN_STATUS_FLAG, COLUMN_STATUS_FLAG);     }     @Override     public boolean onCreate() {            dbHelper = new DatabaseHelper(getContext());            return true;     }     @Override     public Cursor query(Uri uri, String[] projection, String selection,                String[] selectionArgs, String sortOrder) {           SQLiteQueryBuilder qb = new SQLiteQueryBuilder();            switch ( sUriMatcher.match(uri)) {            case TODO:                qb.setTables( TODO_TABLE_NAME);                qb.setProjectionMap( projectionMap);                 break;            case TODO_ID:                qb.setTables( TODO_TABLE_NAME);                qb.setProjectionMap( projectionMap);                qb.appendWhere( COLUMN_ID + "=" + uri.getPathSegments().get(1));                 break;            default:                 throw new RuntimeException( "Unknow uri");           }           SQLiteDatabase db = dbHelper.getReadableDatabase();           Cursor c = qb.query(db, projection, selection, selectionArgs, null,                      null, sortOrder);// 在cursor返回给调用者之前,我们注册了“ uri内容变动通知”,这样cursor会监控 uri内容变化,当发现 uri内容改变时,cursor会自动更新数据。          c.setNotificationUri(getContext().getContentResolver(), uri);            return c;     }     @Override     public String getType(Uri uri) {            int match = sUriMatcher.match(uri);            switch (match) {            case TODO:                 return CONTENT_TYPE;            case TODO_ID:                 return CONTENT_TYPE_ID;            default:                 throw new RuntimeException( "no matcher uri");           }     }     @Override     public Uri insert(Uri uri, ContentValues values) {            // 操作数据库           SQLiteDatabase db = dbHelper.getWritableDatabase();            long _id = db.insertOrThrow( TODO_TABLE_NAME, null, values);            // 通知数据已经改变           getContext().getContentResolver().notifyChange(uri, null);            return buildUri( CONTENT_URI, String. valueOf(_id));     }     public Uri buildUri(Uri uri, String id) {            return uri.buildUpon().appendPath(id).build();     }     @Override     public int delete(Uri uri, String selection, String[] selectionArgs) {            // 操作数据库           dbHelper.getWritableDatabase().delete( TODO_TABLE_NAME, selection,                     selectionArgs);            // 通知数据改变           getContext().getContentResolver().notifyChange(uri, null);            return 1;     }     @Override     public int update(Uri uri, ContentValues values, String selection,                String[] selectionArgs) {            return 0;     }     public class StatusFlag {            public static final int NORMAL = 0;            public static final int DELETE = 1;            public static final int CLEAN = 2;     }}

5. listview设置的adapter是cursorAdapter
public class ToDoAdapter extends CursorAdapter {     private Activity mActivity;     private Holder holder;     public ToDoAdapter(Context context, Cursor c) {            super(context, c);            mActivity = (Activity) context;     }     @Override     public View newView(Context context, Cursor cursor, ViewGroup parent) {            // 实例化view且处理逻辑           View view = View. inflate(context, R.layout.item_layout, null );            holder = new Holder();            holder. idView = (TextView) view.findViewById(R.id.id);            holder. titleView = (TextView) view.findViewById(R.id.title );            holder. delete = (Button) view.findViewById(R.id.delete );            int columnId = cursor.getColumnIndex(ToDoContentProvider.COLUMN_ID );            int columnTitle = cursor                     .getColumnIndex(ToDoContentProvider. COLUMN_TITLE);            final int id = cursor.getInt(columnId);           String title = cursor.getString(columnTitle);            holder. idView.setText(id + "");            holder. titleView.setText(title);            holder. delete.setOnClickListener( new OnClickListener() {                 @Override                 public void onClick(View v) {                     ToDoDao. getInstance().deleteToDo(                                 mActivity.getContentResolver(), id);                     ;                }           });           view.setTag( holder);            return view;     }     @Override     public void bindView(View view, Context context, Cursor cursor) {            holder = (Holder) view.getTag();            // view不为null,只处理逻辑            int columnId = cursor.getColumnIndex(ToDoContentProvider.COLUMN_ID );            int columnTitle = cursor                     .getColumnIndex(ToDoContentProvider. COLUMN_TITLE);            final int id = cursor.getInt(columnId);           String title = cursor.getString(columnTitle);            holder. idView.setText(id + "");            holder. titleView.setText(title);            holder. delete.setOnClickListener( new OnClickListener() {                 @Override                 public void onClick(View v) {                     ToDoDao. getInstance().deleteToDo(                                 mActivity.getContentResolver(), id);// 这里未调用notifyDataSetChanged,是因为之前调用过setNotificationUri方法,当通过provider更新数据库的时候, contentprovider返回的cursor也会被更新                                }           });     }     static class Holder {           TextView idView, titleView;           Button delete;     }}

6. 其实也可以不用内容提供者,直接使用数据库,这样做的好处是,通过cursor可以直接更新数据,还有2010年google开发者大会的一篇《rest》的文章,可能不充分,再补充。





0 0