同步适配器模式(一)

此篇文章为《打造高质量的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》的文章,可能不充分,再补充。





评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值