Android之Content Provider学习使用
来源:互联网 发布:用手充电软件下载 编辑:程序博客网 时间:2024/06/07 03:47
一、SQLite数据库使用
public class MyHoardDatabase { /** * SQLiteOpenHelper是一个抽象类,它是用来创建、打开和升级数据库的最佳实践模式 */ private static class HoardDBOpenHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "myDatabase.db";// 数据库名 private static final String DATABASE_TABLE = "GoldHoards"; // 数据库表名 private static final int DATABASE_VERSION = 1; // 数据库版本 // 创建数据库语句,SQLite数据库支持的数据类型:NULL,INTEGER,REAL(浮点数),TEXT(字符串文本),BLOB(二进制文件) private static final String DATABASE_CREATE = "create table " + DATABASE_TABLE + " (" + KEY_ID + " integer primary key autoincrement, " + KEY_GOLD_HOARD_NAME_COLUMN + " text not null, " + KEY_GOLD_HOARDED_COLUMN + " float, " + KEY_GOLD_HOARD_ACCESSIBLE_COLUMN + " integer);"; /** 构造函数*/ public HoardDBOpenHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); } /** onCreate初始化函数,创建数据库*/ @Override public void onCreate(SQLiteDatabase db) { db.execSQL(DATABASE_CREATE); } /** 数据库版本更新函数*/ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 这里采取的方法时delete原来的数据库,然后再重新创建数据库 db.execSQL("DROP TABLE IF IT EXISTS " + DATABASE_TABLE); onCreate(db); } } // 主键 public static final String KEY_ID = "_id"; // 表名 public static final String KEY_GOLD_HOARD_NAME_COLUMN = "GOLD_HOARD_NAME_COLUMN"; public static final String KEY_GOLD_HOARD_ACCESSIBLE_COLUMN = "OLD_HOARD_ACCESSIBLE_COLUMN"; public static final String KEY_GOLD_HOARDED_COLUMN = "GOLD_HOARDED_COLUMN"; // SQliteOpenHelper实例 private HoardDBOpenHelper hoardDBOpenHelper; /** 构造函数*/ public MyHoardDatabase(Context context) { // 创建SQLiteOpenHelper实例 hoardDBOpenHelper = new HoardDBOpenHelper(context, HoardDBOpenHelper.DATABASE_NAME, null, HoardDBOpenHelper.DATABASE_VERSION); } /** * 关闭数据库(这里仅需将SQLiteOpenHelper close掉即可) * */ public void closeDatabase() { hoardDBOpenHelper.close(); } /** * 查询数据库,返回查询结果Cursor * */ private Cursor getAccessibleHoard() { // 需要获取的字段名列表 String[] result_columns = new String[] { KEY_ID, KEY_GOLD_HOARD_ACCESSIBLE_COLUMN, KEY_GOLD_HOARDED_COLUMN }; // Where条件语句 String where = KEY_GOLD_HOARD_ACCESSIBLE_COLUMN + "=" + 1; // 其他一些限制参数 String whereArgs[] = null; String groupBy = null; String having = null; String order = null; // 根据SQLiteOpenHelper获取DataBase实例 SQLiteDatabase db = hoardDBOpenHelper.getWritableDatabase(); /** 根据语句查询数据库*/ Cursor cursor = db.query(HoardDBOpenHelper.DATABASE_TABLE, result_columns, where, whereArgs, groupBy, having, order); // 返回结果Cursor return cursor; } /** * 解析查询获取到的结果Cursor * */ public float getAverageAccessibleHoardValue() { // 掉用查询函数 Cursor cursor = getAccessibleHoard(); /** 这里是以获取到所有对应GOLD_HOARDED_COLUMN的字段值,并计算平均数*/ float totalHoard = 0f; float averageHoard = 0f; // 找出使用获取的数据列GOLD_HOARDED_COLUMN对应的索引 int GOLD_HOARDED_COLUMN_INDEX = cursor.getColumnIndexOrThrow(KEY_GOLD_HOARDED_COLUMN); /** 对Cursor进行遍历*/ while (cursor.moveToNext()) { // 获取每一行对应的值 float hoard = cursor.getFloat(GOLD_HOARDED_COLUMN_INDEX); totalHoard += hoard; } // 计算平均数 float cursorCount = cursor.getCount(); // 这里是考虑除数为0的特殊情况 averageHoard = cursorCount > 0 ? (totalHoard / cursorCount) : Float.NaN; // 使用完结果Cursor注意close cursor.close(); return averageHoard; } /** * 向数据库中插入添加数据操作 * */ public void addNewHoard(String hoardName, float hoardValue, boolean hoardAccessible) { // 每插入一个新行,需要构造一个ContentValues对象,并使用put方法来填充每个键值对 ContentValues newValues = new ContentValues(); // ContentValues填充键值对 newValues.put(KEY_GOLD_HOARD_NAME_COLUMN, hoardName); newValues.put(KEY_GOLD_HOARDED_COLUMN, hoardValue); newValues.put(KEY_GOLD_HOARD_ACCESSIBLE_COLUMN, hoardAccessible); // 插入记录的用法 SQLiteDatabase db = hoardDBOpenHelper.getWritableDatabase(); db.insert(HoardDBOpenHelper.DATABASE_TABLE, null, newValues); } /** * 更新数据库操作 * */ public void updateHoardValue(int hoardId, float newHoardValue) { // 同理创建ContentValues对象 ContentValues updatedValues = new ContentValues(); // 对应需要更新的键值对进行赋值 updatedValues.put(KEY_GOLD_HOARDED_COLUMN, newHoardValue); // 填写Where条件语句 String where = KEY_ID + "=" + hoardId; String whereArgs[] = null; // 实现更新数据库的操作 SQLiteDatabase db = hoardDBOpenHelper.getWritableDatabase(); db.update(HoardDBOpenHelper.DATABASE_TABLE, updatedValues, where, whereArgs); } /** * 删除数据库中的行的操作 * */ public void deleteEmptyHoards() { // 条件语句 String where = KEY_GOLD_HOARDED_COLUMN + "=" + 0; String whereArgs[] = null; // 删除数据库对应操作 SQLiteDatabase db = hoardDBOpenHelper.getWritableDatabase(); db.delete(HoardDBOpenHelper.DATABASE_TABLE, where, whereArgs); }}
事务(Transaction)是一个对数据库执行工作单元。事务(Transaction)是以逻辑顺序完成的工作单位或序列,可以是由用户手动操作完成,也可以是由某种数据库程序自动完成。
事务(Transaction)是指一个或多个更改数据库的扩展。例如,如果您正在创建一个记录或者更新一个记录或者从表中删除一个记录,那么您正在该表上执行事务。重要的是要控制事务以确保数据的完整性和处理数据库错误。
实际上,您可以把许多的 SQLite 查询联合成一组,把所有这些放在一起作为事务的一部分进行执行。
1>事务的属性
事务(Transaction)具有以下四个标准属性,通常根据首字母缩写为 ACID:
原子性(Atomicity):确保工作单位内的所有操作都成功完成,否则,事务会在出现故障时终止,之前的操作也会回滚到以前的状态。
一致性(Consistency):确保数据库在成功提交的事务上正确地改变状态。在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。
隔离性(Isolation):使事务操作相互独立和透明。隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作。如果有两个事务,运行在相同的时间内,执行相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。
持久性(Durability):确保已提交事务的结果或效果在系统发生故障的情况下仍然存在。
2>事务控制
使用下面的命令来控制事务:
BEGIN TRANSACTION:开始事务处理。
COMMIT:保存更改,或者可以使用 END TRANSACTION 命令。
ROLLBACK:回滚所做的更改。但只能撤销前一个COMMIT或者ROLLBACK事务。
事务控制命令只与 DML 命令 INSERT、UPDATE 和 DELETE 一起使用。他们不能在创建表或删除表时使用,因为这些操作在数据库中是自动提交的。
1>什么是 SQLite?
SQLite是一个进程内的库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。它是一个零配置的数据库,这意味着与其他数据库一样,您不需要在系统中配置。
就像其他数据库,SQLite 引擎不是一个独立的进程,可以按应用程序需求进行静态或动态连接。SQLite 直接访问其存储文件。
2>为什么要用 SQLite?
不需要一个单独的服务器进程或操作的系统(无服务器的)。
SQLite 不需要配置,这意味着不需要安装或管理。
一个完整的 SQLite 数据库是存储在一个单一的跨平台的磁盘文件。
SQLite 是非常小的,是轻量级的,完全配置时小于 400KiB,省略可选功能配置时小于250KiB。
SQLite 是自给自足的,这意味着不需要任何外部的依赖。
SQLite 事务是完全兼容 ACID 的,允许从多个进程或线程安全访问。
SQLite 支持 SQL92(SQL2)标准的大多数查询语言的功能。
SQLite 使用 ANSI-C 编写的,并提供了简单和易于使用的 API。
SQLite 可在 UNIX(Linux, Mac OS-X, Android, iOS)和 Windows(Win32, WinCE, WinRT)中运行。
1>SQLite 存储类
每个存储在 SQLite 数据库中的值都具有以下存储类之一:
SQLite 的存储类稍微比数据类型更普遍。INTEGER 存储类,例如,包含 6 种不同的不同长度的整数数据类型。
2>SQLite Affinity 及类型名称
下表列出了当创建 SQLite3 表时可使用的各种数据类型名称,同时也显示了相应的应用 Affinity:
INT
INTEGER
TINYINT
SMALLINT
MEDIUMINT
BIGINT
UNSIGNED BIG INT
INT2
INT8
CHARACTER(20)
VARCHAR(255)
VARYING CHARACTER(255)
NCHAR(55)
NATIVE CHARACTER(70)
NVARCHAR(100)
TEXT
CLOB
BLOB
no datatype specified
REAL
DOUBLE
DOUBLE PRECISION
FLOAT
NUMERIC
DECIMAL(10,5)
BOOLEAN
DATE
DATETIME
3>Boolean 数据类型
SQLite 没有单独的 Boolean 存储类。相反,布尔值被存储为整数 0(false)和 1(true)。
4>Date 与 Time 数据类型
SQLite 没有一个单独的用于存储日期和/或时间的存储类,但 SQLite 能够把日期和时间存储为 TEXT、REAL 或 INTEGER 值。
无论数据的来源是什么,ContentProvider都会认为是一种表,然后把数据组织成表格
2)ContentProvider提供的方法
query:查询
insert:插入
update:更新
delete:删除
getType:得到数据类型
onCreate:创建数据时调用的回调函数
3)每个ContentProvider都有一个公共的URI,这个URI用于表示这个ContentProvider所提供的数据。Android所提供的ContentProvider都存放在android.provider包当中
<!-- 注册ContentProvider --><!-- 使用authorities标记来设定Content Provider的基本URI,Content Provider的授权如同一个地址, Content Resolver使用它找到想要交互的数据库 --><!-- 一般URI的常用格式为com.<CompanyNanme>.provider.<ApplicationName> --><provider android:name=".MyContentProvider" android:authorities="com.paad.skeletondatabaseprovider"/>3)实现ContentProvider,为外界提供一些列接口
/** * 实现Content Provider,提供query,insert,update等一系列数据库操作供 * Content Resolver来使用 * * 其实实现上有前面使用SQLite数据库模式相仿,这里是为外界提供一系列操作数据库的接口 * */public class MyContentProvider extends ContentProvider { /** 每个Content Provider都应该使用一个公有的静态CONTENT_URI属性来公开它的授权,使其容易被找到 * 而这个Content Provider应该包含一个主要内容的数据路径*/ public static final Uri CONTENT_URI = Uri.parse("content://com.paad.skeletondatabaseprovider/elements"); // 不同查询方式的标志 private static final intALLROWS = 1; private static final intSINGLE_ROW = 2; // 使用URriMatcher来区分不同的URI请求:是针对所有数据进行请求,还是仅请求单行数据 private static final UriMatcher uriMatcher; // 添加匹配规则 static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI("com.paad.skeletondatabaseprovider", "elements", ALLROWS); uriMatcher.addURI("com.paad.skeletondatabaseprovider", "elements/#", SINGLE_ROW); } public static final String KEY_ID = "_id"; public static final String KEY_COLUMN_1_NAME = "KEY_COLUMN_1_NAME"; private MySQLiteOpenHelper myOpenHelper; @Override public boolean onCreate() { // 创建SQLiteOpenHelper实例 myOpenHelper = new MySQLiteOpenHelper(getContext(), MySQLiteOpenHelper.DATABASE_NAME, null, MySQLiteOpenHelper.DATABASE_VERSION); return true; } /** 提供query操作 * 数据库查询操作 */ @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // 获得数据库实例 SQLiteDatabase db; try { db = myOpenHelper.getWritableDatabase(); } catch (SQLiteException ex) { db = myOpenHelper.getReadableDatabase(); } // WHERE条件语句 String groupBy = null; String having = null; /** 使用SQLiteQueryBuilder来简化构造数据查询的过程**/ SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); // 通过URIMatcher来匹配URI,并对URI进行解析 // 如果是单行查询,则需要获得查询的是哪一行 switch (uriMatcher.match(uri)) { case SINGLE_ROW: String rowID = uri.getPathSegments().get(1); queryBuilder.appendWhere(KEY_ID + "=" + rowID); default: break; } // 设置需要查询的table的名称 queryBuilder.setTables(MySQLiteOpenHelper.DATABASE_TABLE); /** 执行查询操作语句*/ Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, groupBy, having, sortOrder); return cursor; } /** 提供getType操作 * 根据URI类型返回一个正确的Content Provider MIME类型数据 */ @Override public String getType(Uri uri) { switch (uriMatcher.match(uri)) { case ALLROWS: return "vnd.android.cursor.dir/vnd.paad.elemental"; case SINGLE_ROW: return "vnd.android.cursor.item/vnd.paad.elemental"; default: throw new IllegalArgumentException("Unsupported URI: " + uri); } } /** 提供delete操作 * 执行删除操作 */ @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // 打开数据库 SQLiteDatabase db = myOpenHelper.getWritableDatabase(); // 匹配URI switch (uriMatcher.match(uri)) { case SINGLE_ROW: String rowID = uri.getPathSegments().get(1); selection = KEY_ID + "=" + rowID + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); default: break; } if (selection == null) selection = "1"; // 执行删除操作 int deleteCount = db.delete(MySQLiteOpenHelper.DATABASE_TABLE, selection, selectionArgs); // 通知有数据 getContext().getContentResolver().notifyChange(uri, null); return deleteCount; } /** * 提供insert操作 * */ @Override public Uri insert(Uri uri, ContentValues values) { SQLiteDatabase db = myOpenHelper.getWritableDatabase(); String nullColumnHack = null; // 执行insert操作 long id = db.insert(MySQLiteOpenHelper.DATABASE_TABLE, nullColumnHack, values); // 若擦如操作成功 if (id > -1) { // 返回CONTENT_URI+最新插入的行ID Uri insertedId = ContentUris.withAppendedId(CONTENT_URI, id); // 通知数据变更 getContext().getContentResolver().notifyChange(insertedId, null); return insertedId; } else return null; } /** 提供insert操作*/ @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { SQLiteDatabase db = myOpenHelper.getWritableDatabase(); // 判断URI是否匹配 switch (uriMatcher.match(uri)) { case SINGLE_ROW: String rowID = uri.getPathSegments().get(1); selection = KEY_ID + "=" + rowID + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); default: break; } int updateCount = db.update(MySQLiteOpenHelper.DATABASE_TABLE, values, selection, selectionArgs); getContext().getContentResolver().notifyChange(uri, null); return updateCount; } /** * 同前面提到的使用SQLiteOpenHelper来操作数据库 **/ private static class MySQLiteOpenHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "myDatabase.db"; private static final int DATABASE_VERSION = 1; private static final String DATABASE_TABLE = "mainTable"; private static final String DATABASE_CREATE = "create table " + DATABASE_TABLE + " (" + KEY_ID + " integer primary key autoincrement, " + KEY_COLUMN_1_NAME + " text not null);"; public MySQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); } @Override public void onCreate(SQLiteDatabase _db) { _db.execSQL(DATABASE_CREATE); } @Override public void onUpgrade(SQLiteDatabase _db, int _oldVersion, int _newVersion) { _db.execSQL("DROP TABLE IF IT EXISTS " + DATABASE_TABLE); onCreate(_db); } }}
ContentResolver contentResolver = this.getContentResolver();
public class DatabaseSkeletonActivity extends Activity { static final String TAG = "DATABASESKELETONACTIVITY"; private SimpleCursorAdapter adapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Use the Search Manager to find the SearchableInfo related // to this Activity. SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); SearchableInfo searchableInfo = searchManager .getSearchableInfo(getComponentName()); // Bind the Activity's SearchableInfo to the Search View SearchView searchView = (SearchView) findViewById(R.id.searchView); searchView.setSearchableInfo(searchableInfo); } /** * 查询数据库,查询所有行的情况 **/ private String getLargestHoardName() { // 获得ContentResolver实例 ContentResolver cr = getContentResolver(); // 需要查询的字段名 String[] result_columns = new String[] { MyHoardContentProvider.KEY_ID, MyHoardContentProvider.KEY_GOLD_HOARD_ACCESSIBLE_COLUMN, MyHoardContentProvider.KEY_GOLD_HOARDED_COLUMN }; // 查询限制语句 String where = MyHoardContentProvider.KEY_GOLD_HOARD_ACCESSIBLE_COLUMN + "=" + 1; String whereArgs[] = null; String order = null; /** 执行查询语句*/ Cursor resultCursor = cr.query(MyHoardContentProvider.CONTENT_URI, result_columns, where, whereArgs, order); /** 解析查询结果Cursor,类似于前面使用SQLite数据库查询的流程*/ float largestHoard = 0f; String hoardName = "No Hoards"; int GOLD_HOARDED_COLUMN_INDEX = resultCursor .getColumnIndexOrThrow(MyHoardContentProvider.KEY_GOLD_HOARDED_COLUMN); int HOARD_NAME_COLUMN_INDEX = resultCursor .getColumnIndexOrThrow(MyHoardContentProvider.KEY_GOLD_HOARD_NAME_COLUMN); while (resultCursor.moveToNext()) { float hoard = resultCursor.getFloat(GOLD_HOARDED_COLUMN_INDEX); if (hoard > largestHoard) { largestHoard = hoard; hoardName = resultCursor.getString(HOARD_NAME_COLUMN_INDEX); } } // 使用完毕注意关闭Cursor resultCursor.close(); return hoardName; } /** * 查询单一某一行的情况 **/ private Cursor getRow(long rowId) { ContentResolver cr = getContentResolver(); String[] result_columns = new String[] { MyHoardContentProvider.KEY_ID, MyHoardContentProvider.KEY_GOLD_HOARD_NAME_COLUMN, MyHoardContentProvider.KEY_GOLD_HOARDED_COLUMN }; // 填充URI Uri rowAddress = ContentUris.withAppendedId( MyHoardContentProvider.CONTENT_URI, rowId); String where = null; String whereArgs[] = null; String order = null; /** 执行查询操作**/ Cursor resultCursor = cr.query(rowAddress, result_columns, where, whereArgs, order); return resultCursor; } /** 数据库添加insert操作*/ private Uri addNewHoard(String hoardName, float hoardValue, boolean hoardAccessible) { ContentValues newValues = new ContentValues(); // 填充键值对 newValues.put(MyHoardContentProvider.KEY_GOLD_HOARD_NAME_COLUMN, hoardName); newValues.put(MyHoardContentProvider.KEY_GOLD_HOARDED_COLUMN, hoardValue); newValues.put(MyHoardContentProvider.KEY_GOLD_HOARD_ACCESSIBLE_COLUMN, hoardAccessible); /** 通过ContentResolver来操作数据库*/ ContentResolver cr = getContentResolver(); Uri myRowUri = cr.insert(MyHoardContentProvider.CONTENT_URI, newValues); return myRowUri; } /** 删除操作*/ private int deleteEmptyHoards() { String where = MyHoardContentProvider.KEY_GOLD_HOARDED_COLUMN + "=" + 0; String whereArgs[] = null; ContentResolver cr = getContentResolver(); int deletedRowCount = cr.delete(MyHoardContentProvider.CONTENT_URI, where, whereArgs); return deletedRowCount; } /** 更新操作*/ private int updateHoardValue(int hoardId, float newHoardValue) { ContentValues updatedValues = new ContentValues(); updatedValues.put(MyHoardContentProvider.KEY_GOLD_HOARDED_COLUMN, newHoardValue); Uri rowURI = ContentUris.withAppendedId(MyHoardContentProvider.CONTENT_URI, hoardId); String where = null; String whereArgs[] = null; ContentResolver cr = getContentResolver(); int updatedRowCount = cr.update(rowURI, updatedValues, where, whereArgs); return updatedRowCount; } /** 使用Cursor Loader异步查询内容 * 数据库操作可能是非常耗时的,所以对于任何数据库和ContentProvider查询而言,最好不要在应用程序的主线程中执行 * 较好的实现方式是使用Loader类,Loader被设计用来异步加载数据和监控底层数据源的变化 **/ LoaderManager.LoaderCallbacks<Cursor> loaderCallback = new LoaderManager.LoaderCallbacks<Cursor>() { /** 当Loader初始化时会被调用,最终创建并返回一个新的CursorLoader对象*/ public Loader<Cursor> onCreateLoader(int id, Bundle args) { String[] projection = null; String where = null; String[] whereArgs = null; String sortOrder = null; Uri queryUri = MyContentProvider.CONTENT_URI; return new CursorLoader(DatabaseSkeletonActivity.this, queryUri, projection, where, whereArgs, sortOrder); } /** 当Loader Manager完成异步查询后,调用*/ public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { // @value SimpleCursorAdapter adapter // 用一个新的结果集代替Cursor Adapter所显示的结果Cursor adapter.swapCursor(cursor); // 这个处事程序不会和UI线程同步,因此在修改任意UI元素之前需要同步它 } /** 当Loader Manager重置Cursor Loader时候,调用*/ public void onLoaderReset(Loader<Cursor> loader) { // 在List Adapter中将现有的结果Cursor移除 adapter.swapCursor(null); // 这个处事程序也不会和UI线程同步,因此在修改任意UI元素之前需要同步它 } };}
常见面试题:
Activity: 活动,是最基本的android应用程序组件。一个活动就是一个用户可以操作的可视化用户界面,每一个活动都被实现为一个独立的类,并且从活动基类继承而来。
Intent: 意图,描述应用想干什么。最重要的部分是动作和动作对应的数据。
Content Provider:内容提供器,android应用程序能够将它们的数据保存到文件、SQLite数据库中,甚至是任何有效的设备中。当你想将你的应用数据和其他应用共享时,内容提供器就可以发挥作用了。
Service:服务,具有一段较长生命周期且没有用户界面的程序组件。
- Android之Content Provider学习使用
- Android之Content Provider学习
- 《老罗Android》学习之Content Provider
- Android学习笔记之Content Provider
- Android 四大组件之Content provider使用
- Android之Content provider
- android之Content Provider
- android content provider 使用
- Android Content Provider使用
- Android Content Provider使用
- Android开发学习之路--Content Provider之初体验
- android组件之Content Provider
- Android基础之Content Provider
- Android温故之-Content Provider
- Android系列之Content Provider
- Android组件之Content Provider
- Android下Content Provider使用
- android Content Provider的使用
- django_openstack_auth源码分析与集成
- 面试常见问题及回答 面试技巧及注意事项
- linux随笔-2
- iOS开发之判断手机号
- git的一些命令
- Android之Content Provider学习使用
- Linux配置DHCP服务器
- CreatePipe匿名管道通信
- 4D打印:改变未来商业生态——互动出版网
- NSLog打印日志技巧
- 可变参数的使用和foreach的输出
- 错误集锦
- EXCEL 中如何将数字转换成英文
- 如何产生各种随机数