四大组件之ContentProvider
前言
ContentProvider作为Android的四大组件之一,是属于需要掌握的基础知识,可能在我们的应用中,对于Activity和Service这两个组件用的很常见,了解的也很多,但是对ContentProvider所知却甚少,所以有必要去整理归纳下其中的内容,讲讲为什么要用ContentProvider这个组件、ContentProvider是什么、ContentProvider用法如何,让大家对ContentProvider有个整体上的理解,方便以后在开发过程中如果忘记了可以及时回顾。
目录
- 为什么要用ContentProvider
- ContentProvider是什么
- ContentProvider用法
- 小结
为什么要用ContentProvider
我们都知道在Android中有数据持久化技术,常见的几种方式比如:括文件存储、SharedPreferences 存 储、以及数据库存储。但这几个存储方式有个共性,那就是只能在应用内访问存储的数据,如果有需要共享的数据呢,就不能对外提供了,虽然SharedPreferences 存储中提供了MODE_WORLD_READABLE 和MODE_WORLD_WRITEABLE这两种操作模式,但这两种模式在Android 4.2的版本已经被废弃了。
那该如何实现跨程序的数据共享呢?此时,就引出了ContentProvider内容提供器,或许你会问,我们为什么要实现跨程序的数据共享,很简单,如果我们想在基础系统上进行二次开发,想引用Android系统本身的数据,就需要一些程序的数据共享,比如,你想做基于通讯录的二次开发,基于短信系统的二次开发,就需要用Android系统提供的数据,因为这些基础数据本身被封装到系统内,你也不太可能自己去设置吧,如果这些数据都不允许第三方的程序进行 访问的话,恐怕很多应用的功能都要大打折扣了。
ContentProvider是什么
内容提供器(ContentProvider)主要用于在不同的应用程序之间实现数据共享的功能, 它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据 的安全性。目前,使用内容提供器是 Android实现跨程序共享数据的标准方式。
ContentProvider的用法一般有两种,一种是使用现有的ContentProdiver来读取和操作相应程序中的数据,另一种是创建自己的ContentProvider给我们程序的数据提供外部访问接口。
ContentProvider用法
- 利用现有的ContentProvider来读取和操作
- 自己创建ContentProvider提供数据
利用现有的ContentProvider来读取和操作
对于每一个应用程序来说,如果想要访问内容提供器中共享的数据,就一定要借助 ContentResolve 类,可以通过 Context 中的 getContentResolver()方法获取到该类的实例。 ContentResolver 中提供了一系列的方法用于对数据进行 CRUD 操作,其中 insert()方法用于 添加数据,update()方法用于更新数据,delete()方法用于删除数据,query()方法用于查询数 据。
有没有似曾相识的感觉?没错,SQLiteDatabase中也是使用的这几个方法来进行 CRUD 操作的,只不过它们在方法参数上稍微有一些区别。 不同于 SQLiteDatabase,ContentResolver中的增删改查方法都是不接收表名参数的,而 是使用一个 Uri参数代替,这个参数被称为内容 URI。。内容 URI给内容提供器中的数据建立 了唯一标识符,它主要由两部分组成,权限(authority)和路径(path)。权限是用于对不同 的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式来进行命名。比如某个 程序的包名是 com.example.app,那么该程序对应的权限就可以命名为 com.example.app. provider。路径则是用于对同一应用程序中不同的表做区分的,通常都会添加到权限的后面。 比如某个程序的数据库里存在两张表,table1和 table2,这时就可以将路径分别命名为/table1 和/table2,然后把权限和路径进行组合,内容 URI就变成了 com.example.app.provider/table1 和 com.example.app.provider/table2。
内容 URI最标准的格式写法:
content://com.example.app.provider/table1
content://com.example.app.provider/table2
在得到了内容 URI字符串之后,我们还需要将它解析成 Uri对象才可以作为参数传入。 解析的方法也相当简单,代码如下所示:
Uri uri = Uri.parse("content://com.example.app.provider/table1")
只需要调用 Uri.parse()方法,就可以将内容 URI字符串解析成 Uri对象了。
现在我们就可以使用这个 Uri对象来查询 table1表中的数据了,代码如下所示:
Cursor cursor = getContentResolver().query( uri, projection, selection, selectionArgs, sortOrder);
那么我们可以看看getContentResolver()这个方法跟Sql部分的对应关系吧
查询完成后返回的仍然是一个 Cursor 对象,这时我们就可以将数据从 Cursor 对象中逐 个读取出来了。读取的思路仍然是通过移动游标的位置来遍历 Cursor的所有行,然后再取出每一行中相应列的数据,代码如下所示:
if (cursor != null) {
while (cursor.moveToNext()) {
String column1 = cursor.getString(cursor.getColumnIndex("column1"));
int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
}
cursor.close();
}
剩下的增加、修改、删除操作就简单多了。
我们先来看看如何向 table1表中添加一条数据,代码如下所示:
ContentValues values = new ContentValues();
values.put("column1", "text");
values.put("column2", 1);
getContentResolver().insert(uri, values);
可以看到,仍然是将待添加的数据组装到 ContentValues 中,然后调用 ContentResolver。
在table1中更新一条数据
ContentValues values = new ContentValues();
values.put("column1", "");
getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new String[] {"text", "1"});
在table1中删除一条数据
getContentResolver().delete(uri, "column2 = ?", new String[] { "1" });
整体来说,我们可以利用现有的ContentProvider来获取数据,比如读取联系人的信息:
public class MainActivity extends Activity {
ListView contactsView;
ArrayAdapter<String> adapter;
List<String> contactsList = new ArrayList<String>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
contactsView = (ListView) findViewById(R.id.contacts_view);
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, contactsList);
contactsView.setAdapter(adapter);
readContacts();
}
private void readContacts() {
Cursor cursor = null;
try {
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null, null, null, null);
while (cursor.moveToNext()) {
String displayName = cursor.getString(cursor
.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String number = cursor.getString(cursor
.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
contactsList.add(displayName + "\n" + number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
cursor.close();
}
}
}
}
同时需要要注意的是在注册清单中加上读取联系人的权限,表示允许应用访问联系人信息。
<uses-permission android:name="android.permission.READ_CONTACTS" />
自己创建ContentProvider提供数据
如果想要实现跨程序共享数据的功能,官方推荐的方式就是使用内容 提供器,可以通过新建一个类去继承 ContentProvider的方式来创建一个自己的内容提供器。 ContentProvider类中有六个抽象方法,我们在使用子类继承它的时候,需要将这六个方法全部重写。
比如新写一个MyProvider类,需要继承ContentProvider。
public class MyProvider extends ContentProvider {
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder) {
return null;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}
在这六个方法中,相信大多数你都已经非常熟悉了。
- onCreate() 初始化内容提供器的时候调用。通常会在这里完成对数据库的创建和升级等操作, 返回 true 表示内容提供器初始化成功,返回 false 则表示失败。注意,只有当存在 ContentResolver尝试访问我们程序中的数据时,内容提供器才会被初始化。
- query() 从内容提供器中查询数据。使用 uri参数来确定查询哪张表,projection参数用于确 定查询哪些列,selection和 selectionArgs参数用于约束查询哪些行,sortOrder参数用于 对结果进行排序,查询的结果存放在 Cursor对象中返回。
- insert() 向内容提供器中添加一条数据。使用 uri 参数来确定要添加到的表,待添加的数据 保存在 values参数中。添加完成后,返回一个用于表示这条新记录的 URI。
- update() 更新内容提供器中已有的数据。使用 uri 参数来确定更新哪一张表中的数据,新数 据保存在 values参数中,selection和 selectionArgs参数用于约束更新哪些行,受影响的 行数将作为返回值返回。
- delete() 从内容提供器中删除数据。使用 uri 参数来确定删除哪一张表中的数据,selection和 selectionArgs参数用于约束删除哪些行,被删除的行数将作为返回值返回。
- getType() 根据传入的内容 URI来返回相应的 MIME类型。
可以看到,几乎每一个方法都会带有Uri这个参数,这个参数也正是调用 ContentResolver 的增删改查方法时传递过来的。而现在我们需要对传入的 Uri参数进行解析,从中分析出 调用方期望访问的表和数据。 回顾一下,一个标准的内容 URI写法是这样的:
content://com.example.app.provider/table1
这就表示调用方期望访问的是 com.example.app 这个应用的 table1 表中的数据。除此之 外,我们还可以在这个内容 URI的后面加上一个 id,如下所示:
content://com.example.app.provider/table1/1
这就表示调用方期望访问的是 com.example.app这个应用的 table1表中 id 为 1的数据。
内容 URI的格式主要就只有以上两种,以路径结尾就表示期望访问该表中所有的数据, 以 id 结尾就表示期望访问该表中拥有相应 id 的数据。我们可以使用通配符的方式来分别匹 配这两种格式的内容 URI,规则如下。
- "*":表示匹配任意长度的任意字符
- "#" :表示匹配任意长度的数字
content://com.example.app.provider/* 表示一个能够匹配任意表的内容 URI格式content://com.example.app.provider/table1/# 表示一个能够匹配 table1表中任意一行数据的内容 URI格式
此时,我们需要引出一个UriMatcher这个类,这个类就可以轻松地实现匹配内容URI的功能。UriMatcher 中提供了一个 addURI()方法,这个方法接收三个参数,可以分别把权限、路径和一个自定义 代码传进去。这样,当调用 UriMatcher 的 match()方法时,就可以将一个 Uri 对象传入,返回值是某个能够匹配这个 Uri对象所对应的自定义代码,利用这个代码,我们就可以判断出调用方期望访问的是哪张表中的数据了。
public static final int TABLE1_DIR = 0;
public static final int TABLE1_ITEM = 1;
public static final int TABLE2_DIR = 2;
public static final int TABLE2_ITEM = 3;
private static UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.example.app.provider", "table1", TABLE1_DIR);
uriMatcher.addURI("com.example.app.provider ", "table1/#", TABLE1_ITEM);
uriMatcher.addURI("com.example.app.provider ", "table2", TABLE2_ITEM);
uriMatcher.addURI("com.example.app.provider ", "table2/#", TABLE2_ITEM);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder) {
switch (uriMatcher.match(uri)) {
case TABLE1_DIR: //查询table1表中的所有数据
break;
case TABLE1_ITEM: //查询table1表中的单条数据
break;
case TABLE2_DIR: //查询table2表中的所有数据
break;
case TABLE2_ITEM: //查询table2表中的单条数据
break;
default:
break;
}
return null;
}
可以看到,MyProvider 中新增了四个整型常量,其中 TABLE1_DIR 表示访问 table1 表 中的所有数据,TABLE1_ITEM 表示访问 table1 表中的单条数据,TABLE2_DIR 表示访问 table2 表中的所有数据,TABLE2_ITEM 表示访问 table2 表中的单条数据。接着在静态代码块里我们创建了 UriMatcher 的实例,并调用 addURI()方法,将期望匹配的内容 URI 格式传 递进去,注意这里传入的路径参数是可以使用通配符的。然后当 query()方法被调用的时候, 就会通过 UriMatcher的 match()方法对传入的 Uri 对象进行匹配,如果发现 UriMatcher 中某 个内容 URI格式成功匹配了该 Uri对象,则会返回相应的自定义代码,然后我们就可以判断 出调用方期望访问的到底是什么数据了。
除此之外,还有一个方法你会比较陌生,即 getType()方法。它是所有的内容提供器都必 须提供的一个方法,用于获取 Uri对象所对应的 MIME类型。一个内容 URI所对应的 MIME 字符串主要由三部分组分,Android对这三个部分做了如下格式规定。
- 必须以 vnd开头。
- 如果内容 URI 以路径结尾,则后接 android.cursor.dir/,如果内容 URI 以 id 结尾, 则后接 android.cursor.item/。
- 最后接上 vnd..。
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.example.app.provider.table1";
case CATEGORY_DIR:
return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2";
case CATEGORY_ITEM:
return "vnd.android.cursor.item/vnd.com.example.app.provider.table2";
}
return null;
}
到这里,一个完整的内容提供器就创建完成了,现在任何一个应用程序都可以使用 ContentResolver来访问我们程序中的数据。那么前面所提到的,如何才能保证隐私数据不会 泄漏出去呢?其实多亏了内容提供器的良好机制,这个问题在不知不觉中已经被解决了。因 为所有的 CRUD 操作都一定要匹配到相应的内容 URI 格式才能进行的,而我们当然不可能 向 UriMatcher中添加隐私数据的 URI,所以这部分数据根本无法被外部程序访问到,安全问 题也就不存在了。
下面比如我们自己创建一个内容提供器
public class DatabaseProvider extends ContentProvider {
public static final int BOOK_DIR = 0;
public static final int BOOK_ITEM = 1;
public static final int CATEGORY_DIR = 2;
public static final int CATEGORY_ITEM = 3;
public static final String AUTHORITY = "com.example.databasetest.provider";
private static UriMatcher uriMatcher;
private MyDatabaseHelper dbHelper;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
}
@Override
public boolean onCreate() {
dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = null;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
cursor = db.query("Book", projection, selection, selectionArgs, null, null, sortOrder);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
cursor = db.query("Book", projection, "id = ?", new String[] { bookId }, null, null,sortOrder);
break;
case CATEGORY_DIR:
cursor = db.query("Category", projection, selection, selectionArgs, null, null,
sortOrder);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
cursor = db.query("Category", projection, "id = ?", new String[] { categoryId }, null,null, sortOrder);
break;
default:
break;
}
return cursor;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
Uri uriReturn = null;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
case BOOK_ITEM:
long newBookId = db.insert("Book", null, values);
uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId);
break;
case CATEGORY_DIR:
case CATEGORY_ITEM:
long newCategoryId = db.insert("Category", null, values);
uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" + newCategoryId);
break;
default:
break;
}
return uriReturn;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int updatedRows = 0;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
updatedRows = db.update("Book", values, selection, selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
updatedRows = db.update("Book", values, "id = ?", new String[] { bookId });
break;
case CATEGORY_DIR:
updatedRows = db.update("Category", values, selection, selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
updatedRows = db.update("Category", values, "id = ?", new String[] { categoryId });
break;
default:
break;
}
return updatedRows;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int deletedRows = 0;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
deletedRows = db.delete("Book", selection, selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
deletedRows = db.delete("Book", "id = ?", new String[] { bookId });
break;
case CATEGORY_DIR:
deletedRows = db.delete("Category", selection, selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
deletedRows = db.delete("Category", "id = ?", new String[] { categoryId });
break;
default:
break;
}
return deletedRows;
}
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.book";
case CATEGORY_DIR:
return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category";
case CATEGORY_ITEM:
return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.category";
}
return null;
}
}
这里结合了SQLiteDatabase数据库的操作,把想把共享的数据开放出去。
不过同样需要注意的是,需要在清单中注册。
<provider
android:name="com.example.databasetest.DatabaseProvider"
android:authorities="com.example.databasetest.provider" >
</provider>
小结
所以总的来说,ContentProvider是四大组件之一,这也是我们需要掌握的基础知识之一,从通过分析为何需要ContentProvider,以及ContentProvider是什么,并最后是如何使用,对这一个过程是不是清楚很多,当然这些都是基本用法,如果有兴趣的话,可以具体去看源码,了解其中实现的原理,很多时候我们要做到知其然而之所以然,这样使用起来了,以后有什么问题,可以迅速定位到其中原因。
阅读扩展
源于对掌握的Android开发基础点进行整理,罗列下已经总结的文章,从中可以看到技术积累的过程。
1,Android系统简介
2,ProGuard代码混淆
3,讲讲Handler+Looper+MessageQueue关系
4,Android图片加载库理解
5,谈谈Android运行时权限理解
6,EventBus初理解
7,Android 常见工具类
8,对于Fragment的一些理解
9,Android 四大组件之 " Activity "
10,Android 四大组件之" Service "
11,Android 四大组件之“ BroadcastReceiver "
12,Android 四大组件之" ContentProvider "
13,讲讲 Android 事件拦截机制
14,Android 动画的理解
15,Android 生命周期和启动模式
16,Android IPC 机制
17,View 的事件体系
18,View 的工作原理
19,理解 Window 和 WindowManager
20,Activity 启动过程分析
21,Service 启动过程分析
22,Android 性能优化
23,Android 消息机制
24,Android Bitmap相关
25,Android 线程和线程池
26,Android 中的 Drawable 和动画
27,RecylerView 中的装饰者模式
28,Android 触摸事件机制
29,Android 事件机制应用
30,Cordova 框架的一些理解
31,有关 Android 插件化思考
32,开发人员必备技能——单元测试
四大组件之ContentProvider的更多相关文章
- Android实训案例(五)——四大组件之一ContentProvider的使用,通讯录的实现以及ListView的优化
Android实训案例(五)--四大组件之一ContentProvider的使用,通讯录的实现 Android四大组件是啥这里就不用多说了,看图吧,他们之间通过intent通讯 我们后续也会一一的为大 ...
- Android 四大组件之" ContentProvider "
前言 ContentProvider作为Android的四大组件之一,是属于需要掌握的基础知识,可能在我们的应用中,对于Activity和Service这两个组件用的很常见,了解的也很多,但是对Con ...
- Android四大组件之——ContentProvider(一)
Android四大组件之--ContentProvider(一) 本人邮箱:JohnTsai.Work@gmail.com,欢迎交流讨论. 欢迎转载,转载请注明网址:http://www.cnblog ...
- 【Android开发日记】之入门篇(九)——Android四大组件之ContentProvider
数据源组件ContentProvider与其他组件不同,数据源组件并不包括特定的功能逻辑.它只是负责为应用提供数据访问的接口.Android内置的许多数据都是使用ContentProvider形式,供 ...
- 初学android:四大组件之contentprovider
一.ContentProvider的概念ContentProvider:为存储和获取数据提供统一的接口.可以在不同的应用程序之间共享数据.Android已经为常见的一些数据提供了默认的ContentP ...
- Android四大组件之contentProvider
Activity,Service,broadcast and Contentprovider android 4 大组件. ContentProvider:使用: public class Image ...
- Android 四大组件之四(ContentProvider)
ContentProvider调用关系: ContentProvider(数据提供者)是应用程序之间共享数据的一种接口机制,是一种更为高级的数据共享方法. ContentProvider可以指定需要共 ...
- android四大组件之ContentProvider(二)
ContentProvider学习笔记 上一章节我们编写了自定义的一个StudentProvider,他提供了两种供外界访问数据的方式,content://come.demo.sqlite.stude ...
- android四大组件之ContentProvider(一)
ContentProvider学习笔记 1. ContentProvider基本概念 ContentProvider向我们提供了我们在应用程序之间共享数据的一种机制,虽然采用文件和SharedPref ...
随机推荐
- css伪类制作三角箭头
<meta charset="utf-8"> <style type="text/css"> .tip{ padding: 5px 10 ...
- Unity3D UGUI之fbx模型导入问题
在UI层添加了一个fbx的模型,但是在game模式和运行中无法看到这个fbx. Canvas上面有个Render Mode :Screen Space - Overlay.Screen Space ...
- requireJs--简单的使用方法
简单使用: <!-- index.html部分 data-main 为入口 --> <script data-main="js/app.js" src=" ...
- c#控制打印机杂项
因项目中需要用到控制打印机的相关信息,此贴将网络寻找的资料做了些整理 1. C# 如何设置系统的默认打印机 using System.Runtime.InteropServices; [DllIm ...
- 分分钟用上C#中的委托和事件
每一个初学C#的程序猿,在刚刚碰到委托和事件的概念时,估计都是望而却步,茫然摸不到头脑的.百度一搜,关于概念介绍的文章大把大把的,当然也不乏深入浅出的好文章.可看完这些文章,大多数新手,估计也只是信心 ...
- 可在广域网部署运行的QQ高仿版 -- GGTalk总览
(最新版本:V5.5,2016.12.06 增加对MySQL数据库的支持.) (android移动端:2015.09.24 最初发布 ,2016.11.25 最后更新) GGTalk(简称GG)是 ...
- [.net 面向对象编程基础] (1) 开篇
[.net 面向对象编程基础] (1)开篇 使用.net进行面向对象编程也有好长一段时间了,整天都忙于赶项目,完成项目任务之中.最近偶有闲暇,看了项目组中的同学写的代码,感慨颇深.感觉除了定义个类,就 ...
- 仅此一文让你明白ASP.NET MVC 之Model的呈现(仅此一文系列三)
本文目的 我们来看一个小例子,在一个ASP.NET MVC项目中创建一个控制器Home,只有一个Index: public class HomeController : Controller { pu ...
- HTTPS那些事(二)SSL证书(转载)
原创地址:http://www.guokr.com/post/116169/ 从第一部分HTTP工作原理中,我们可以了解到HTTPS核心的一个部分是数据传输之前的握手,握手过程中确定了数据加密的密 ...
- 设计模式之里氏代换原则(LSP)
里氏代换原则(Liskov Substitution Principle, LSP) 1 什么是里氏代换原则 里氏代换原则是由麻省理工学院(MIT)计算机科学实验室的Liskov女士,在1987年的O ...