編輯:關於android開發
本文系原創博客,文中不妥煩請指出,如需轉載摘要請注明出處!
Alpha Dog
2016-04-13 10:27:06
首先,項目的地址:https://github.com/DarkPointK/MyContentProvider.git。
網上對於Android的Content Provider框架以及SQLite這款輕量級的嵌入式數據庫的介紹有很多,我不再復述,在此我將著重於如何實現對數據庫的操作。
工作之余開始系統性的自主學習,在閱讀了網絡上很多大牛的各類技術文章教程後,想試著寫一篇屬於自己的博客,以記錄一些心得成果還可給其他正在學習的朋友一點幫助,而對於文中存在的不妥之處還煩請指出。
這是一個布局很簡單的例子,主要內容在於後台對一個SQLite表的增刪改查一系列操作。
而數據庫的表在被創建後會存在於/data/data/package_name/databases文件夾中,如有需要在windows環境下查看表可以利用AS的Device Monitor導出:
我們建立一個自己的項目在AS中,在這個項目中我們至多需要3個類,分別為:繼承自SQLiteOpenHelper的類用於直接對數據庫操作,繼承自ContentProvider的類會被用於決定以何種方式操作數據庫,然後就是我們的Activity。
在繼承SQLiteOpenHelper後我們需要重寫它的onCreate()方法和onUpgrade()方法並且實現構造方法。當StringDataBase被初始化時,其構造方法將提交數據庫名及其版本號給父類的方法得到處理。首次打開數據庫onCreate()方法會被調用將會處理SQL_CREATE字串中的SQL語句,創建數據庫得到一個“string”表。當版本號得到提升將調用onUpgrade()方法。
import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQueryBuilder; import java.sql.SQLException; /** * Created by Alpha Dog on 2016/4/8. */ public class StringDataBase extends SQLiteOpenHelper { private static final String DATABASE_NAME = "StringDatabase"; private static final String TABLE_NAME = "string"; private static final String KEY_NAME = "str"; private static final String SQL_CREATE = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + "(_id INTEGER PRIMARY KEY AUTOINCREMENT, " + KEY_NAME + " TEXT)"; private static final String SQL_DROP = "DROP TABLE IS EXISTS" + TABLE_NAME; public StringDataBase(Context context) { super(context, DATABASE_NAME, null, 1); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(SQL_DROP); onCreate(db); } }
重寫完這兩個方法後,開始著手完善將會被StringProvider類用到的所有對數據庫增刪改查操作的方法。
在這一階段,你可能會發現這裡對數據庫的操作都是通過以getWritableDataBase()方法獲得一個可以操作數據庫的SQLiteDatabase實例,且有讀寫權(此外,有getReadableDatabase()方法可獲得只讀權實例)。在獲得SQLiteDatabase的實例後,調用相應的方法並將返回的結果作為返回值,當在StringProvider類中得到這些返回值時,可以進行下一步的處理。
public long addString(ContentValues values) throws SQLException { long id = getWritableDatabase().insert(TABLE_NAME, "", values); if (id <= 0) { throw new SQLException("Failed to add String"); } return id; } public int deleteString(String id) { if (id == null) { return getWritableDatabase().delete(TABLE_NAME, null, null); } else { return getWritableDatabase().delete(TABLE_NAME, "_id=?", new String[]{id}); } } public int updateString( ContentValues values,String id) { return getWritableDatabase().update(TABLE_NAME,values,"_id=?", new String[]{id}); } public Cursor getString(String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteQueryBuilder sqb = new SQLiteQueryBuilder(); sqb.setTables(TABLE_NAME); if (sortOrder == null || sortOrder == "") { sortOrder = "_id"; } return sqb.query(getReadableDatabase(), projection, selection, selectionArgs, null, null, sortOrder); }
對於SQLiteDatabase及SQLiteQueryBuilder在這裡調用到的那些增刪改查方法的參數列表有必要在這裡細說:
1.2繼承自ContentProvider的StringProvider類:
ContentProvider需要重寫的方法,這些方法將在獲取ContentProvider的實例後可以被利用URI調用:
由於需要URI連接的原因,我們需要在AndroidManifest.xml中注冊這個Provider:
<provider android:authorities="com.alphadog.mycontentprovider" android:name="com.alphadog.mycontentprovider.StringProvider"/> </application>
其中authorities中的字段將作為URI中關鍵的一部分,用於請求Provider權限: content://com.alphadog.mycontentprovider/
name中的參數指定的是這個Provider具體提供服務的類。
好了,注冊完Provider之後,現在來看看這個StringProvider裡究竟要寫些什麼:
import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.net.Uri; /** * Created by Alpha Dog on 2016/4/8. */ public class StringProvider extends ContentProvider { private static final String PROVIDER_NAME = "com.alphadog.mycontentprovider"; private static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/string"); private static final int STR = 1; private static final int STRS = 2; private StringDataBase sdb = null; private static final UriMatcher um = getUriMatcher(); private static UriMatcher getUriMatcher() { UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(PROVIDER_NAME, "string/#", STR); uriMatcher.addURI(PROVIDER_NAME, "string", STRS); return uriMatcher; } //初始化StringDatabase @Override public boolean onCreate() { Context context = getContext(); sdb = new StringDataBase(context); return true; } @Override public String getType(Uri uri) { switch (um.match(uri)) { case STR: return "vnd.android.cursor.item/vnd.com.alphadog.mycontentprovider.string"; case STRS: return "vnd.android.cursor.dir/vnd.com.alphadog.mycontentprovider.string"; } return ""; } //增 @Override public Uri insert(Uri uri, ContentValues values) { try { long id = sdb.addString(values); Uri returnUri = ContentUris.withAppendedId(CONTENT_URI, id); return returnUri; } catch (Exception e) { return null; } } //刪 @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return sdb.deleteString(selection); } //改 @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return sdb.updateString( values,selection); } //查 @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return sdb.getString(projection,selection,selectionArgs,sortOrder); } }
這裡的代碼為便於理解也非常簡潔,onCreate()裡顯示對StringDataBase進行了初始化,這將導致連接或創建數據庫,而其他的增刪改查方法也是調用這個初始化過sdb裡的方法對數據庫進行相應的操作。其中,getType()方法可以被外部調用,它會分析傳入的URI字串並返回一個MIME字串,關於MIME的具體意義可以參考這篇博客:http://blog.csdn.net/harvic880925/article/details/44620851 。
至此,一個自定義的Provide就算大功告成了,剩下的便是對這個Provider的使用方法了,這是個令人激動的階段,對此可以參考下我寫這個Activity,然後根據你的需求進行優化改動。
import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import butterknife.ButterKnife; import butterknife.InjectView; import butterknife.OnClick; import static android.widget.Toast.LENGTH_SHORT; public class MainActivity extends AppCompatActivity { private static final String PROVIDER_NAME = "com.alphadog.mycontentprovider"; private static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/string"); private Cursor c; private String s; private boolean fresh = true; private Uri seleUri; private int id; @InjectView(R.id.tv) TextView tv; @InjectView(R.id.et) EditText et; @InjectView(R.id.add) Button add; @InjectView(R.id.del) Button del; @InjectView(R.id.update) Button update; @InjectView(R.id.query) Button query; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.inject(this); update.setEnabled(false); del.setEnabled(false); et.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { fresh = true; } @Override public void afterTextChanged(Editable s) { } }); } @OnClick({R.id.add, R.id.del, R.id.update, R.id.query}) public void onClick(View view) { switch (view.getId()) { case R.id.add: add(); break; case R.id.del: del(); break; case R.id.update: update(); break; case R.id.query: query(); break; } } private void add() { ContentValues cv = new ContentValues(); cv.put("str", et.getText().toString()); Uri uri = getContentResolver().insert(CONTENT_URI, cv); if (uri != null) { Toast.makeText(getBaseContext(), uri.toString(), LENGTH_SHORT).show(); } } private void del() { // 條件只傳id,因為在StringDataBase中有完整處理where語句 int uri = getContentResolver().delete(CONTENT_URI, "" + id, null); Toast.makeText(getBaseContext(), "刪除成功" + id, LENGTH_SHORT).show(); update.setEnabled(false); del.setEnabled(false); fresh = true; } private void update() { ContentValues cv = new ContentValues(); if (!c.getString(1).equals(et.getText().toString())) { cv.put("str", et.getText().toString()); int uri = getContentResolver().update(CONTENT_URI, cv, "" + id, null); Log.i("修改id為" + id + "的值為", " " + et.getText().toString()); Toast.makeText(getBaseContext(), "修改成功" + id+"為"+et.getText().toString(), LENGTH_SHORT).show(); fresh = true; } else { Toast.makeText(this, "沒做任何更改!", LENGTH_SHORT).show(); fresh = false; } } private void query() { s = et.getText().toString(); if (fresh&& !s.equals("") ) { s = "str = '" + et.getText().toString() + "'"; Log.i("正在搜索的s為", s); Cursor cursor = getContentResolver().query(CONTENT_URI, null, s, null, null); if (cursor == null || cursor.getCount() <= 0) { Toast.makeText(this, "然而什麼都沒搜到", LENGTH_SHORT).show(); return; } c = cursor; } display(); } private void display() { if (c != null && c.moveToNext()) { update.setEnabled(true); del.setEnabled(true); Log.i("c", c.getString(0) + " " + c.getString(1)); tv.setText(c.getString(0)); id = c.getInt(0);
Toast.makeText(this, "正在查找:"+c.getString(1)+" = "+id, LENGTH_SHORT).show(); fresh = false; } else { tv.setText(""); update.setEnabled(false); del.setEnabled(false); Toast.makeText(this, "搜索結束,隊列將刷新並從頭開始", LENGTH_SHORT).show(); fresh = true; } } }
這一塊的內容主要是描述“增刪改查”操作時邏輯上的判斷,本文的開頭已經具體描述。每個操作都是先得到一個ContentResolver實例然後以URI及必要參數傳入調用方法。需要注意的是在query()方法查詢到數據後返回的是cursor類型,取值的方法具體參考:http://www.cnblogs.com/TerryBlog/archive/2010/07/05/1771459.html
今天有時間就學習了下在ListView、GridView列表項中清加ProgressBar,本文用最簡單的代碼實現可以通用的功能,人人都能看懂,哈哈
Fragment配合RadioGroup實現點擊切換布局,fragmentradiogroup 這裡用了 compile com.jakewharton:butter
Android項目:手機安全衛士(16)—— 復雜 ListView淺析 Android項目:手機安全衛士(16)—— 復雜 ListView 1
Android自定義控件來襲(Scroller) 先看看效果圖 實現方法繼承自ViewGroup需要我們自己來測量,布局,實現滑動的效果,處理滑動沖突, 自定義Vi