1.ContentProvider简单介绍

1.1.定义

  ContentProvider,即内容提供者属于Android的四大组件之一。

1.2.作用

  进程间进行数据交互&共享,即跨进程通信。

  

1.3.原理

  ContentProvider的底层是采用Android中的Binder机制。

2.ContentProvider具体使用

2.0.关于ContentProvider的使用主要介绍一下内容:

  

2.1.统一资源标识符(URI)

  定义:Uniform Resource Identifier,即统一资源标识符

  

  作用:唯一标识 ContentProvider & 其中的数据

    外界进程通过 URI 找到对应的ContentProvider & 其中的数据,

    再进行数据操作。

  具体使用:

    URI分为 系统预置 & 自定义,

    分别对应系统内置的数据(如通讯录、日程表等等)和自定义数据库

    i.关于 系统预置URI 此处不作过多讲解,需要的同学可自行查看

    ii.此处主要讲解自定义URI

  

  

  

// 设置URI
Uri uri = Uri.parse("content://com.carson.provider/User/1")
// 上述URI指向的资源是:名为 `com.carson.provider`的`ContentProvider` 中表名 为`User` 中的 `id`为1的数据 // 特别注意:URI模式存在匹配通配符* & # // *:匹配任意长度的任何有效字符的字符串
// 以下的URI 表示 匹配provider的任何内容
content://com.example.app.provider/*
// #:匹配任意长度的数字字符的字符串
// 以下的URI 表示 匹配provider中的table表的所有行
content://com.example.app.provider/table/#

2.2.MIME数据类型

  解释:MIME:全称Multipurpose Internet Mail Extensions,

    多功能Internet 邮件扩充服务。

    它是一种多用途网际邮件扩充协议,

    在1992年最早应用于电子邮件系统,但后来也应用到浏览器。 

    MIME类型就是设定某种扩展名的文件用一种应用程序来打开的方式类型,

    当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。

    多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。

  作用:

    指定某个扩展名的文件用某种应用程序来打开 如指定

    .html文件采用text应用程序打开、指定.pdf文件采用flash应用程序打开

  具体使用:

    1.ContentProvider根据URI返回MIME类型

      ContentProvider.getType(uri);

    2.MIME类型组成

      每种MIME类型由2部分组成=类型+子类型

      MIME类型是一个包含2部分的字符串

      

text / html
// 类型 = text、子类型 = html text/css
text/xml
application/pdf

  

    3.MIME类型形式

      MIME类型有2种形式:

      

// 形式1:单条记录
vnd.android.cursor.item/自定义
// 形式2:多条记录(集合)
vnd.android.cursor.dir/自定义 // 注:
// 1. vnd:表示父类型和子类型具有非标准的、特定的形式。
// 2. 父类型已固定好(即不能更改),只能区别是单条还是多条记录
// 3. 子类型可自定义

    实例说明:

    

<-- 单条记录 -->
// 单个记录的MIME类型
vnd.android.cursor.item/vnd.yourcompanyname.contenttype // 若一个Uri如下
content://com.example.transportationprovider/trains/122
// 则ContentProvider会通过ContentProvider.geType(url)返回以下MIME类型
vnd.android.cursor.item/vnd.example.rail <-- 多条记录 -->
// 多个记录的MIME类型
vnd.android.cursor.dir/vnd.yourcompanyname.contenttype
// 若一个Uri如下
content://com.example.transportationprovider/trains
// 则ContentProvider会通过ContentProvider.geType(url)返回以下MIME类型
vnd.android.cursor.dir/vnd.example.rail

2.3.ContentProvider类

  

  1.组织数据方式

    ContentProvider主要以表格的形式组织数据

    同时也支持文件数据,只是表格形式用得比较多

    

    每个表格中包含多张表,每张表包含行 & 列,分别对应记录 & 字段

    同数据库

  2.主要方法

    进程间共享数据的本质是:添加、删除、获取 & 修改(更新)数据

    所以ContentProvider的核心方法也主要是上述4个作用

    

<-- 4个核心方法 -->
public Uri insert(Uri uri, ContentValues values)
// 外部进程向 ContentProvider 中添加数据 public int delete(Uri uri, String selection, String[] selectionArgs)
// 外部进程 删除 ContentProvider 中的数据 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
// 外部进程更新 ContentProvider 中的数据 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 
// 外部应用 获取 ContentProvider 中的数据 // 注:
// 1. 上述4个方法由外部进程回调,并运行在ContentProvider进程的Binder线程池中(不是主线程)
// 2. 存在多线程并发访问,需要实现线程同步
// a. 若ContentProvider的数据存储方式是使用SQLite & 一个,则不需要,因为SQLite内部实现好了线程同步,若是多个SQLite则需要,因为SQL对象之间无法进行线程同步
// b. 若ContentProvider的数据存储方式是内存,则需要自己实现线程同步 <-- 2个其他方法 -->
public boolean onCreate()
// ContentProvider创建后 或 打开系统后其它进程第一次访问该ContentProvider时 由系统进行调用
// 注:运行在ContentProvider进程的主线程,故不能做耗时操作 public String getType(Uri uri)
// 得到数据类型,即返回当前 Url 所代表数据的MIME类型

  Android为常见的数据(如通讯录、日程表等)提供了内置了默认的ContentProvider

  但也可根据需求自定义ContentProvider,但上述6个方法必须重写

    本文主要讲解自定义ContentProvider

  ContentProvider类并不会直接与外部进程交互,而是通过ContentResolver

2.4.ContentResolver类

  1.作用

    统一管理不同 ContentProvider间的操作

    通过URI即可操作不同的ContentProvider中的数据

    外部进程通过ContentResolver类从而与ContentProvider类进行交互

  2.为什么要使用通过ContentResolver类交互,而不直接访问ContentProvider类?

    一般来说,一款应用要使用多个ContentProvider

    若需要了解每个ContentProvider的不同实现从而再完成数据交互,操作成本高 & 难度大

  3.具体使用

    ContentResolver 类提供了与ContentProvider类相同名字 & 作用的4个方法

    

// 外部进程向 ContentProvider 中添加数据
public Uri insert(Uri uri, ContentValues values)  // 外部进程 删除 ContentProvider 中的数据
public int delete(Uri uri, String selection, String[] selectionArgs) // 外部进程更新 ContentProvider 中的数据
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)  // 外部应用 获取 ContentProvider 中的数据
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

  4.实例说明

    

// 使用ContentResolver前,需要先获取ContentResolver
// 可通过在所有继承Context的类中 通过调用getContentResolver()来获得ContentResolver
ContentResolver resolver = getContentResolver(); // 设置ContentProvider的URI
Uri uri = Uri.parse("content://cn.scu.myprovider/user"); // 根据URI 操作 ContentProvider中的数据
// 此处是获取ContentProvider中 user表的所有记录
Cursor cursor = resolver.query(uri, null, null, null, "userid desc");

  Android 提供了3个用于辅助ContentProvide的工具类:

  ContentUris

  UriMatcher

  ContentObserver

2.5.ContentUris类

  作用:操作URI

  具体使用:核心方法有两个==>withAppendedId() & parseId()

  

// withAppendedId()作用:向URI追加一个id
Uri uri = Uri.parse("content://cn.scu.myprovider/user")
Uri resultUri = ContentUris.withAppendedId(uri, 7);
// 最终生成后的Uri为:content://cn.scu.myprovider/user/7 // parseId()作用:从URL中获取ID
Uri uri = Uri.parse("content://cn.scu.myprovider/user/7")
long personid = ContentUris.parseId(uri);
//获取的结果为:7

2.6.UriMatcher类

  作用:

    i.在ContentProvider中注册URI

    ii.根据URI匹配ContentProvider中对应的数据库表

  

  具体使用:

    

// 步骤1:初始化UriMatcher对象
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
//常量UriMatcher.NO_MATCH = 不匹配任何路径的返回码
// 即初始化时不匹配任何东西 // 步骤2:在ContentProvider 中注册URI(addURI())
int URI_CODE_a = 1;
int URI_CODE_b = 2;
matcher.addURI("cn.scu.myprovider", "user1", URI_CODE_a);
matcher.addURI("cn.scu.myprovider", "user2", URI_CODE_b);
// 若URI资源路径 = content://cn.scu.myprovider/user1 ,则返回注册码URI_CODE_a
// 若URI资源路径 = content://cn.scu.myprovider/user2 ,则返回注册码URI_CODE_b // 步骤3:根据URI 匹配 URI_CODE,从而匹配ContentProvider中相应的资源(match()) @Override
public String getType (Uri uri){
Uri uri = Uri.parse(" content://cn.scu.myprovider/user1"); switch (matcher.match(uri)) {
// 根据URI匹配的返回码是URI_CODE_a
// 即matcher.match(uri) == URI_CODE_a
case URI_CODE_a:
return tableNameUser1;
// 如果根据URI匹配的返回码是URI_CODE_a,则返回ContentProvider中的名为tableNameUser1的表
case URI_CODE_b:
return tableNameUser2;
// 如果根据URI匹配的返回码是URI_CODE_b,则返回ContentProvider中的名为tableNameUser2的表
}
}

2.7.ContentObserver类

  1.定义:内容观察者

  

  2.作用:观察Uri引起ContentProvider中的数据变化&通知外界(即访问该数据访问者)

    当ContentProvider 中的数据发生变化(增、删 & 改)时,就会触发该 ContentObserver

  3.具体使用

    

// 步骤1:注册内容观察者ContentObserver
getContentResolver().registerContentObserver(uri);
// 通过ContentResolver类进行注册,并指定需要观察的URI // 步骤2:当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
public class UserContentProvider extends ContentProvider {
public Uri insert(Uri uri, ContentValues values) {
db.insert("user", "userid", values);
getContext().getContentResolver().notifyChange(uri, null);
// 通知访问者
}
} // 步骤3:解除观察者
getContentResolver().unregisterContentObserver(uri);
// 同样需要通过ContentResolver类进行解除

  至此,关于ContentProvider的使用已经讲解完毕

3.ContentProvider实例说明

  由于ContentProvider不仅常用于进程间通信,同时也适用于进程内通信

  所以本实例会采用ContentProvider讲解

    i.进程内通信

    ii.进程间通信

  实例说明:采用的数据源是Android中的SQLite数据库

3.1.进程内通信

  步骤说明:

    i.创建数据库类

    ii.自定义ContentProvider类

    iii.注册创建的ContentProvider类

    iv.进程内访问ContentProvider的数据

  具体使用:

    步骤1:创建数据库类DBHelper.java

    

public class DBHelper extends SQLiteOpenHelper {

    // 数据库名
private static final String DATABASE_NAME = "finch.db"; // 表名
public static final String USER_TABLE_NAME = "user";
public static final String JOB_TABLE_NAME = "job"; private static final int DATABASE_VERSION = 1;
//数据库版本号 public DBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
} @Override
public void onCreate(SQLiteDatabase db) { // 创建两个表格:用户表 和职业表
db.execSQL("CREATE TABLE IF NOT EXISTS " + USER_TABLE_NAME + "(_id INTEGER PRIMARY KEY AUTOINCREMENT," + " name TEXT)");
db.execSQL("CREATE TABLE IF NOT EXISTS " + JOB_TABLE_NAME + "(_id INTEGER PRIMARY KEY AUTOINCREMENT," + " job TEXT)");
} @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { }
}

    步骤2:自定义ContentProvider类

    

public class MyProvider extends ContentProvider {

    private Context mContext;
DBHelper mDbHelper = null;
SQLiteDatabase db = null; public static final String AUTOHORITY = "cn.scu.myprovider";
// 设置ContentProvider的唯一标识 public static final int User_Code = 1;
public static final int Job_Code = 2; // UriMatcher类使用:在ContentProvider 中注册URI
private static final UriMatcher mMatcher; static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 初始化
mMatcher.addURI(AUTOHORITY, "user", User_Code);
mMatcher.addURI(AUTOHORITY, "job", Job_Code);
// 若URI资源路径 = content://cn.scu.myprovider/user ,则返回注册码User_Code
// 若URI资源路径 = content://cn.scu.myprovider/job ,则返回注册码Job_Code
} // 以下是ContentProvider的6个方法 /**
* 初始化ContentProvider
*/
@Override
public boolean onCreate() { mContext = getContext();
// 在ContentProvider创建时对数据库进行初始化
// 运行在主线程,故不能做耗时操作,此处仅作展示
mDbHelper = new DBHelper(getContext());
db = mDbHelper.getWritableDatabase(); // 初始化两个表的数据(先清空两个表,再各加入一个记录)
db.execSQL("delete from user");
db.execSQL("insert into user values(1,'Carson');");
db.execSQL("insert into user values(2,'Kobe');"); db.execSQL("delete from job");
db.execSQL("insert into job values(1,'Android');");
db.execSQL("insert into job values(2,'iOS');"); return true;
} /**
* 添加数据
*/
@Override
public Uri insert(Uri uri, ContentValues values) { // 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名
// 该方法在最下面
String table = getTableName(uri); // 向该表添加数据
db.insert(table, null, values); // 当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
mContext.getContentResolver().notifyChange(uri, null); // // 通过ContentUris类从URL中获取ID
// long personid = ContentUris.parseId(uri);
// System.out.println(personid); return uri;
} /**
* 查询数据
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名
// 该方法在最下面
String table = getTableName(uri); // // 通过ContentUris类从URL中获取ID
// long personid = ContentUris.parseId(uri);
// System.out.println(personid); // 查询数据
return db.query(table, projection, selection, selectionArgs, null, null, sortOrder, null);
} /**
* 更新数据
*/
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// 由于不展示,此处不作展开
return 0;
} /**
* 删除数据
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// 由于不展示,此处不作展开
return 0;
} @Override
public String getType(Uri uri) { // 由于不展示,此处不作展开
return null;
} /**
* 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名
*/
private String getTableName(Uri uri) {
String tableName = null;
switch (mMatcher.match(uri)) {
case User_Code:
tableName = DBHelper.USER_TABLE_NAME;
break;
case Job_Code:
tableName = DBHelper.JOB_TABLE_NAME;
break;
}
return tableName;
}
}

    步骤3:注册创建的ContentProvider类AndroidManifest.xml

    

<provider android:name="MyProvider"
android:authorities="cn.scu.myprovider"/>

    步骤4:进程内访问ContentProvider中的数据

    

public class MainActivity extends AppCompatActivity {

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); /**
* 对user表进行操作
*/ // 设置URI
Uri uri_user = Uri.parse("content://cn.scu.myprovider/user"); // 插入表中数据
ContentValues values = new ContentValues();
values.put("_id", 3);
values.put("name", "Iverson"); // 获取ContentResolver
ContentResolver resolver = getContentResolver();
// 通过ContentResolver 根据URI 向ContentProvider中插入数据
resolver.insert(uri_user,values); // 通过ContentResolver 向ContentProvider中查询数据
Cursor cursor = resolver.query(uri_user, new String[]{"_id","name"}, null, null, null);
while (cursor.moveToNext()){
System.out.println("query book:" + cursor.getInt(0) +" "+ cursor.getString(1));
// 将表中数据全部输出
}
cursor.close();
// 关闭游标 /**
* 对job表进行操作
*/
// 和上述类似,只是URI需要更改,从而匹配不同的URI CODE,从而找到不同的数据资源
Uri uri_job = Uri.parse("content://cn.scu.myprovider/job"); // 插入表中数据
ContentValues values2 = new ContentValues();
values2.put("_id", 3);
values2.put("job", "NBA Player"); // 获取ContentResolver
ContentResolver resolver2 = getContentResolver();
// 通过ContentResolver 根据URI 向ContentProvider中插入数据
resolver2.insert(uri_job,values2); // 通过ContentResolver 向ContentProvider中查询数据
Cursor cursor2 = resolver2.query(uri_job, new String[]{"_id","job"}, null, null, null);
while (cursor2.moveToNext()){
System.out.println("query job:" + cursor2.getInt(0) +" "+ cursor2.getString(1));
// 将表中数据全部输出
}
cursor2.close();
// 关闭游标
}
}

    结果:

    

    

3.2.进程间进行数据共享

  实例说明:本文需要创建2个进程,即创建两个工程,作用如下

  

  

  进程1

    使用步骤如下:

      1.创建数据库类

      2.自定义ContentProvider类

      3.注册创建的ContentProvider类

    前两个步骤同上例,此处不作过多描述,此处主要讲解步骤3.

  步骤3:注册创建的ContentProvider类AndroidManifest.xml

    

<provider
android:name="MyProvider"
android:authorities="scut.carson_ho.myprovider" // 声明外界进程可访问该Provider的权限(读 & 写)
android:permission="scut.carson_ho.PROVIDER" // 权限可细分为读 & 写的权限
// 外界需要声明同样的读 & 写的权限才可进行相应操作,否则会报错
// android:readPermisson = "scut.carson_ho.Read"
// android:writePermisson = "scut.carson_ho.Write" // 设置此provider是否可以被其他进程使用
android:exported="true" /> // 声明本应用 可允许通信的权限
<permission android:name="scut.carson_ho.Read" android:protectionLevel="normal"/>
// 细分读 & 写权限如下,但本Demo直接采用全权限
// <permission android:name="scut.carson_ho.Write" android:protectionLevel="normal"/>
// <permission android:name="scut.carson_ho.PROVIDER" android:protectionLevel="normal"/>

  进程2

  步骤1:声明可访问的权限

  AndroidManifest.xml

  

<uses-permission android:name="scut.carson_ho.PROVIDER"/>

    // 细分读 & 写权限如下,但本Demo直接采用全权限
// <uses-permission android:name="scut.carson_ho.Read"/>
// <uses-permission android:name="scut.carson_ho.Write"/> // 注:声明的权限必须与进程1中设置的权限对应

  步骤2:访问ContentProvider的类

  

public class MainActivity extends AppCompatActivity {

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); /**
* 对user表进行操作
*/ // 设置URI
Uri uri_user = Uri.parse("content://scut.carson_ho.myprovider/user"); // 插入表中数据
ContentValues values = new ContentValues();
values.put("_id", 4);
values.put("name", "Jordan"); // 获取ContentResolver
ContentResolver resolver = getContentResolver();
// 通过ContentResolver 根据URI 向ContentProvider中插入数据
resolver.insert(uri_user,values); // 通过ContentResolver 向ContentProvider中查询数据
Cursor cursor = resolver.query(uri_user, new String[]{"_id","name"}, null, null, null);
while (cursor.moveToNext()){
System.out.println("query book:" + cursor.getInt(0) +" "+ cursor.getString(1));
// 将表中数据全部输出
}
cursor.close();
// 关闭游标 /**
* 对job表进行操作
*/
// 和上述类似,只是URI需要更改,从而匹配不同的URI CODE,从而找到不同的数据资源
Uri uri_job = Uri.parse("content://scut.carson_ho.myprovider/job"); // 插入表中数据
ContentValues values2 = new ContentValues();
values2.put("_id", 4);
values2.put("job", "NBA Player"); // 获取ContentResolver
ContentResolver resolver2 = getContentResolver();
// 通过ContentResolver 根据URI 向ContentProvider中插入数据
resolver2.insert(uri_job,values2); // 通过ContentResolver 向ContentProvider中查询数据
Cursor cursor2 = resolver2.query(uri_job, new String[]{"_id","job"}, null, null, null);
while (cursor2.moveToNext()){
System.out.println("query job:" + cursor2.getInt(0) +" "+ cursor2.getString(1));
// 将表中数据全部输出
}
cursor2.close();
// 关闭游标
}
}

  结果展示

  在进程展示时,需要先运行准备数据的进程1,在运行需要访问数据的进程2

  1.运行准备数据的进程1在进程1中,我们准备好了一系列数据

  

  2.运行需要访问数据的进程2在进程2中,我们先想ContentProvider中插入数据,再查询数据

  

  

4.ContentProvider优点和总结

4.1.优点

  1.安全

    ContentProvider为应用间的数据交互提供了一个安全的环境:

    允许把自己的应用数据根据需求开放给 其他应用 进行 增、删、改、查,

    而不用担心因为直接开放数据库权限而带来的安全问题。

  2.访问简单&高效

    对比于其他对外共享数据的方式,数据访问方式会因数据存储的方式而不同:

   采用 文件方式 对外共享数据,需要进行文件操作读写数据;

    采用 Sharedpreferences 共享数据,需要使用sharedpreferences API读写数据

    

    这使得访问数据变得复杂 & 难度大。

    而采用ContentProvider方式,其解耦了底层数据的存储方式,

    使得无论底层数据存储采用何种方式,外界对数据的访问方式都是统一的,

    这使得访问简单 & 高效 

    如一开始数据存储方式 采用 SQLite 数据库,后来把数据库换成 MongoDB,

    也不会对上层数据ContentProvider使用代码产生影响

    

    这张图,非常666.

    一目了然。

4.2.总结

  用一张图总结本文内容

  

5.参考网站

Android面试收集录3 ContentProvider详解的更多相关文章

  1. Android面试收集录4 Fragment详解

    1.什么是Fragment? 你可以简单的理解为,Fragment是显示在Activity中的Activity. 它可以显示在Activity中,然后它也可以显示出一些内容. 因为它拥有自己的生命周期 ...

  2. Android面试收集录9 IntentService详解

    一. 定义 IntentService是Android里面的一个封装类,继承自四大组件之一的Service. 二.作用 处理异步请求,实现多线程 三. 工作流程 注意:若启动IntentService ...

  3. Android面试收集录8 HandlerThread详解

    1.前言 我们知道在Android系统中,我们执行完耗时操作都要另外开启子线程来执行,执行完线程以后线程会自动销毁. 想象一下如果我们在项目中经常要执行耗时操作,如果经常要开启线程,接着又销毁线程, ...

  4. Android面试收集录7 AsyncTask详解

    1.Android中的线程 在操作系统中,线程是操作系统调度的最小单元,同时线程又是一种受限的系统资源,即线程不可能无限制地产生, 并且 **线程的创建和销毁都会有相应的开销.**当系统中存在大量的线 ...

  5. Android开发数据存储之ContentProvider详解

    转载:十二.ContentProvider和Uri详解 一.使用ContentProvider(内容提供者)共享数据 ContentProvider在android中的作用是对外共享数据,也就是说你可 ...

  6. Android面试收集录1 Activity+Service

    1.Activity的生命周期 1.1.首先查看一下Activity生命周期经典图片. 在正常情况下,一个Activity从启动到结束会以如下顺序经历整个生命周期: onCreate()->on ...

  7. Android面试收集录18 Android Context详解

    Activity mActivity =new Activity() 作为Android开发者,不知道你有没有思考过这个问题,Activity可以new吗?Android的应用程序开发采用JAVA语言 ...

  8. Android面试收集录2 Broadcast Receiver详解

    1.Broadcast Receiver广播接收器简单介绍 1.1.定义 Broadcast Receiver(广播接收器),属于Android四大组件之一 在Android开发中,Broadcast ...

  9. Android面试收集录 Android组件

    1.请说出Android SDK支持哪些方式显示富文本信息? 使用TextView组件可以显示富文本信息,如果要实现图文混排,需实现ImageGetter接口 使用WebView组件显示HTML页面 ...

随机推荐

  1. Java设计模式—模板方法模式

    模板方法模式仅仅使用了Java的继承机制,但它是一个应用非常广泛的模式. 1.定义:           一个操作中的算法的框架,而将一些步骤延迟到子类中.使得子类可以不改变一个算法的结构即可重定义该 ...

  2. 用iSee图片专家制作淘宝店标教程

    普通的淘宝店铺都会有店标.店标都显示在店铺首页的显现位置,买家在逛淘宝店的时候,一眼都会瞄到店标.因此,如果可以制作一个专属于自己店铺的店标,可以吸引买家的眼光,也更好地宣传了店铺. 下面就用iSee ...

  3. OpenGL学习 Our First OpenGL Program

    This shows you how to create the main window with the book’s application framework and how to render ...

  4. CRUD全栈式编程架构之更精简的设计

    精简的程度 ViewModel精简 服务精简 控制器精简 Index.cshmtl精简 AddOrEdit.cshtml精简 效果:最精简的情况下,只需要写Entity这一个数据库实体然后加上一些简单 ...

  5. 二叉索引树,LA2191,LA5902,LA4329

    利用了二进制,二分的思想的一个很巧妙的数据结构,一个lowbit(x):二进制表示下的最右边的一个1开始对应的数值. 那么如果一个节点的为x左孩子,父亲节点就是 x + lowbit(x),如果是右孩 ...

  6. python 面向对象(四)--实例属性和类属性

    由于Python是动态语言,根据类创建的实例可以任意绑定属性. 给实例绑定属性的方法是通过实例变量,或者通过self变量: class Student(object): def __init__(se ...

  7. P1316 丢瓶盖

    题目描述 陶陶是个贪玩的孩子,他在地上丢了A个瓶盖,为了简化问题,我们可以当作这A个瓶盖丢在一条直线上,现在他想从这些瓶盖里找出B个,使得距离最近的2个距离最大,他想知道,最大可以到多少呢? 输入输出 ...

  8. PHP精度问题

    PHP 为任意精度数学计算提供了二进制计算器(Binary Calculator),它支持任意大小和精度的数字,以字符串形式描述 bcadd — 加法bccomp — 比较bcdiv — 相除bcmo ...

  9. AI-Info-Micron-Insight:5G、人工智能和即将到来的移动革命

    ylbtech-AI-Info-Micron-Insight:5G.人工智能和即将到来的移动革命 1.返回顶部 1. 5G.人工智能和即将到来的移动革命 人们都说自己的手机“智能”,但究竟有多智能?凡 ...

  10. 数据库可视化工具简介以及pymysql的使用

    1.可视化工具Navicat 我们自己开发测试时,可以使用该可视化工具,以图形界面的形式操作数据库 在生产环境中,为了显示自己的逼格,一般不建议使用它 官网下载:https://www.navicat ...