SQLite数据库与Contentprovider(2)
ContentProvider:
在创建ContentProvider时,需要首先使用数据库、文件系统或网络实现底层存储功能,
然后在继承ContentProvider的类中实现基本数据操作的接口函数,包括添加、删除、查找和更新等功能。
调用者不能够直接调用ContentProvider的接口函数,而需要使用ContentResolver对象,
通过URI间接调用ContentProvider。下图是ContentProvider调用关系。

URI:
URI是通用资源标志符(Uniform Resource Identifier),用来定位任何远程或本地的可用资源
ContentProvider使用的URI语法结构如下
content://<authority>/<data_path>/<id>
content://是通用前缀,表示该URI用于ContentProvider定位资源,无需修改。
<authority>是授权者名称,用来确定具体由哪一个ContentProvider提供资源。因此,一般<authority>都由类的小写全称组成,以保证唯一性。
<data_path>是数据路径,用来确定请求的是哪个数据集。
例如:
content://edu.hrbeu.peopleprovider/people/3
可以省略id(/3)部分那么意味着整个数据。
UriMatcher:
在新构造的ContentProvider类中,通过构造一个UriMatcher,判断URI是单条数据还是多条数据。
public void addURI (String authority, String path, int code)
authority表示匹配的授权者名称
path表示数据路径
#可以代表任何数字 (content://<authority>/<data_path>/#)
code表示返回代码(uriMatcher.match(uri))的返回值)
注册ContentProvider :
<provider android:name = ".PeopleProvider" android:authorities = "edu.hrbeu.peopleprovider"/>
实例:
ContentProvider一般用于两个不同的进程之间的数据共享。
- 假设我们有一个新的工程(app),在此工程中创建一个people.db数据库,然后通过自定义了一个ContentProvider来共享数据库中的data。
- 我们可以通过注册provider时用到的authority在配合db path之类的来连接(content://<authority>/<data_path>/#)读取内容提供者(不同进程)的数据。
首先是步骤1:
*只要在AndroidManifast中注册provider就会执行对应的provider类。无需再MainActivity中调用或无需直接调用自定义的new provider。
即系统自动会加载调用PeopleProvider类。
<provider android:name = ".PeopleProvider" android:authorities = "edu.hrbeu.peopleprovider"/>
继承ContentProvider时代码如下:
import android.content.*;
import android.database.Cursor;
import android.net.Uri; public class PeopleProvider extends ContentProvider{
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
} @Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
} @Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
return null;
} @Override
public boolean onCreate() {
// TODO Auto-generated method stub
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
return null;
} @Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}
}
类似数据库操作。可以这么理解,相对来说好理解。
开始贴代码。。。
package edu.hrbeu.contentproviderdemo; 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.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri; public class PeopleProvider extends ContentProvider { private static final String DB_NAME = "people.db";
private static final String DB_TABLE = "peopleinfo";
private static final int DB_VERSION = 1; private SQLiteDatabase db;
private DBOpenHelper dbOpenHelper; private static final int MULTIPLE_PEOPLE = 1;
private static final int SINGLE_PEOPLE = 2;
private static final UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(People.AUTHORITY, People.PATH_MULTIPLE, MULTIPLE_PEOPLE);
uriMatcher.addURI(People.AUTHORITY, People.PATH_SINGLE, SINGLE_PEOPLE);
} @Override
public boolean onCreate() {
// TODO Auto-generated method stub
Context context = getContext();
dbOpenHelper = new DBOpenHelper(context, DB_NAME, null, DB_VERSION);
db = dbOpenHelper.getWritableDatabase(); if (db == null)
return false;
else
return true; } @Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(DB_TABLE);
switch(uriMatcher.match(uri)){
case SINGLE_PEOPLE:
qb.appendWhere(People.KEY_ID + "=" + uri.getPathSegments().get(1));
break;
default:
break;
}
Cursor cursor = qb.query(db,
projection,
selection,
selectionArgs,
null,
null,
sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor; } @Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
switch(uriMatcher.match(uri)){
case MULTIPLE_PEOPLE:
return People.MINE_TYPE_MULTIPLE;
case SINGLE_PEOPLE:
return People.MINE_TYPE_SINGLE;
default:
throw new IllegalArgumentException("Unkown uri:"+uri);
} } @Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
long id = db.insert(DB_TABLE, null, values);
if ( id > 0 ){
Uri newUri = ContentUris.withAppendedId(People.CONTENT_URI, id);
getContext().getContentResolver().notifyChange(newUri, null);
return newUri;
}
throw new SQLException("Failed to insert row into " + uri); } @Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
int count = 0;
switch(uriMatcher.match(uri)){
case MULTIPLE_PEOPLE:
count = db.delete(DB_TABLE, selection, selectionArgs);
break;
case SINGLE_PEOPLE:
String segment = uri.getPathSegments().get(1);
count = db.delete(DB_TABLE, People.KEY_ID + "=" + segment, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unsupported URI:" + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count; } @Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
int count;
switch(uriMatcher.match(uri)){
case MULTIPLE_PEOPLE:
count = db.update(DB_TABLE, values, selection, selectionArgs);
break;
case SINGLE_PEOPLE:
String segment = uri.getPathSegments().get(1);
count = db.update(DB_TABLE, values, People.KEY_ID+"="+segment, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknow URI:" + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count; } private static class DBOpenHelper extends SQLiteOpenHelper { public DBOpenHelper(Context context, String name, CursorFactory factory, int version) {
super(context, name, factory, version);
}
private static final String DB_CREATE = "create table " +
DB_TABLE + " (" + People.KEY_ID + " integer primary key autoincrement, " +
People.KEY_NAME+ " text not null, " + People.KEY_AGE+ " integer," + People.KEY_HEIGHT + " float);";
@Override
public void onCreate(SQLiteDatabase _db) {
// TODO Auto-generated method stub
_db.execSQL(DB_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase _db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
_db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);
onCreate(_db); } } }
package edu.hrbeu.contentproviderdemo;
import android.net.Uri; public class People{ public static final String MIME_DIR_PREFIX = "vnd.android.cursor.dir";
public static final String MIME_ITEM_PREFIX = "vnd.android.cursor.item";
public static final String MINE_ITEM = "vnd.hrbeu.people"; public static final String MINE_TYPE_SINGLE = MIME_ITEM_PREFIX + "/" + MINE_ITEM;
public static final String MINE_TYPE_MULTIPLE = MIME_DIR_PREFIX + "/" + MINE_ITEM;
public static final String AUTHORITY = "edu.hrbeu.peopleprovider";
public static final String PATH_SINGLE = "people/#";
public static final String PATH_MULTIPLE = "people";
public static final String CONTENT_URI_STRING = "content://" + AUTHORITY + "/" + PATH_MULTIPLE;
public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_STRING); public static final String KEY_ID = "_id";
public static final String KEY_NAME = "name";
public static final String KEY_AGE = "age";
public static final String KEY_HEIGHT = "height";
}
MainActivity是空的。。。不贴出来了。
运行结果没有任何内容,因为仅是添加了一个provider。

看一下步骤2:
在另一个App中调用此ContentProvider。
无需再AndroidManifast中注册provider。
只需对应步骤1中的People类里的静态变量是完全匹配的就是可以了。
也就是说URI需要与我们自定义的ContentProvider保持一致。
package edu.hrbeu.contentresolverdemo;
import android.net.Uri; public class People{ public static final String MIME_DIR_PREFIX = "vnd.android.cursor.dir";
public static final String MIME_ITEM_PREFIX = "vnd.android.cursor.item";
public static final String MINE_ITEM = "vnd.hrbeu.people"; public static final String MINE_TYPE_SINGLE = MIME_ITEM_PREFIX + "/" + MINE_ITEM;
public static final String MINE_TYPE_MULTIPLE = MIME_DIR_PREFIX + "/" + MINE_ITEM;
public static final String AUTHORITY = "edu.hrbeu.peopleprovider";
public static final String PATH_SINGLE = "people/#";
public static final String PATH_MULTIPLE = "people";
public static final String CONTENT_URI_STRING = "content://" + AUTHORITY + "/" + PATH_MULTIPLE;
public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_STRING); public static final String KEY_ID = "_id";
public static final String KEY_NAME = "name";
public static final String KEY_AGE = "age";
public static final String KEY_HEIGHT = "height";
}
操作Contentpriver提供的数据我们需要用到ContentResolver:
package edu.hrbeu.contentresolverdemo; import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView; public class ContentResolverDemo extends Activity { private EditText nameText;
private EditText ageText;
private EditText heightText;
private EditText idEntry; private TextView labelView;
private TextView displayView; private ContentResolver resolver; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_content_resolver_demo);
nameText = (EditText) findViewById(R.id.name);
ageText = (EditText) findViewById(R.id.age);
heightText = (EditText) findViewById(R.id.hight);
idEntry = (EditText) findViewById(R.id.id); labelView = (TextView) findViewById(R.id.label);
displayView = (TextView) findViewById(R.id.display);
Button addButton = (Button) findViewById(R.id.button1);
Button queryAllButton = (Button) findViewById(R.id.button2);
Button clearButton = (Button) findViewById(R.id.button3);
Button queryButton = (Button) findViewById(R.id.button6);
Button deleteButton = (Button) findViewById(R.id.button5);
Button updateButton = (Button) findViewById(R.id.button7); resolver = this.getContentResolver();
addButton.setOnClickListener(new OnClickListener() { @Override
public void onClick(View v) {
// TODO Auto-generated method stub
ContentValues values = new ContentValues(); values.put(People.KEY_NAME, nameText.getText().toString());
values.put(People.KEY_AGE,
Integer.parseInt(ageText.getText().toString()));
values.put(People.KEY_HEIGHT,
Float.parseFloat(heightText.getText().toString())); Uri newUri = resolver.insert(People.CONTENT_URI, values);
labelView.setText("添加成功,URI:" + newUri); }
});
queryAllButton.setOnClickListener(new OnClickListener() { @Override
public void onClick(View v) {
// TODO Auto-generated method stub
Cursor cursor = resolver.query(People.CONTENT_URI,
new String[] { People.KEY_ID, People.KEY_NAME,
People.KEY_AGE, People.KEY_HEIGHT }, null,
null, null);
if (cursor == null) {
labelView.setText("数据库中没有数据");
return;
}
labelView.setText("数据库:" + String.valueOf(cursor.getCount())
+ "条记录"); String msg = "";
if (cursor.moveToFirst()) {
do {
msg += "ID:"
+ cursor.getInt(cursor
.getColumnIndex(People.KEY_ID)) + ",";
msg += "姓名:"
+ cursor.getString(cursor
.getColumnIndex(People.KEY_NAME)) + ",";
msg += "年龄:"
+ cursor.getInt(cursor
.getColumnIndex(People.KEY_AGE)) + ", ";
msg += "身高:"
+ cursor.getFloat(cursor
.getColumnIndex(People.KEY_HEIGHT))
+ "\n";
} while (cursor.moveToNext());
} displayView.setText(msg); }
}); clearButton.setOnClickListener(new OnClickListener() { @Override
public void onClick(View v) {
// TODO Auto-generated method stub
displayView.setText("");
}
}); queryButton.setOnClickListener(new OnClickListener() { @Override
public void onClick(View v) {
// TODO Auto-generated method stub
Uri uri = Uri.parse(People.CONTENT_URI_STRING + "/"
+ idEntry.getText().toString());
Cursor cursor = resolver.query(uri, new String[] {
People.KEY_ID, People.KEY_NAME, People.KEY_AGE,
People.KEY_HEIGHT }, null, null, null);
if (cursor == null) {
labelView.setText("数据库中没有数据");
return;
} String msg = "";
if (cursor.moveToFirst()) {
msg += "ID:"
+ cursor.getInt(cursor
.getColumnIndex(People.KEY_ID)) + ",";
msg += "姓名:"
+ cursor.getString(cursor
.getColumnIndex(People.KEY_NAME)) + ",";
msg += "年龄:"
+ cursor.getInt(cursor
.getColumnIndex(People.KEY_AGE)) + ", ";
msg += "身高:"
+ cursor.getFloat(cursor
.getColumnIndex(People.KEY_HEIGHT)) + "\n";
} labelView.setText("数据库:");
displayView.setText(msg); }
});
deleteButton.setOnClickListener(new OnClickListener() { @Override
public void onClick(View v) {
// TODO Auto-generated method stub
Uri uri = Uri.parse(People.CONTENT_URI_STRING + "/"
+ idEntry.getText().toString());
int result = resolver.delete(uri, null, null);
String msg = "删除ID为" + idEntry.getText().toString() + "的数据"
+ (result > 0 ? "成功" : "失败");
labelView.setText(msg);
}
}); updateButton.setOnClickListener(new OnClickListener() { @Override
public void onClick(View v) {
// TODO Auto-generated method stub
ContentValues values = new ContentValues();
values.put(People.KEY_NAME, nameText.getText().toString());
values.put(People.KEY_AGE,
Integer.parseInt(ageText.getText().toString()));
values.put(People.KEY_HEIGHT,
Float.parseFloat(heightText.getText().toString())); Uri uri = Uri.parse(People.CONTENT_URI_STRING + "/"
+ idEntry.getText().toString());
int result = resolver.update(uri, values, null, null); String msg = "更新ID为" + idEntry.getText().toString() + "的数据"
+ (result > 0 ? "成功" : "失败");
labelView.setText(msg); }
});
} @Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.content_resolver_demo, menu);
return true;
} @Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
事成相识的布局文件也附上:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="edu.hrbeu.contentresolverdemo.ContentResolverDemo" > <TextView android:id="@+id/Username"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:text="用户名:">
</TextView> <EditText
android:id="@+id/name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/Username" > </EditText> <TextView android:id="@+id/Userage"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_below="@id/name"
android:text="年龄:">
</TextView>
<EditText android:id="@+id/age"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_below="@id/Userage">
</EditText> <TextView android:id="@+id/Userhight"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_below="@id/age"
android:text="身高:">
</TextView>
<EditText android:id="@+id/hight"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_below="@id/Userhight">
</EditText> <Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/button1"
android:layout_alignBottom="@+id/button1"
android:layout_centerHorizontal="true"
android:text="全部显示" /> <Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/button2"
android:layout_alignBottom="@+id/button2"
android:layout_alignRight="@+id/hight"
android:text="清除显示" /> <TextView android:id="@+id/Userid"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_below="@id/button1"
android:text="ID:">
</TextView>
<EditText android:id="@+id/id"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_below="@id/Userid">
</EditText> <Button
android:id="@+id/button6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/button5"
android:layout_alignBottom="@+id/button5"
android:layout_alignLeft="@+id/button2"
android:text="ID查询" /> <Button
android:id="@+id/button7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/button6"
android:layout_alignBottom="@+id/button6"
android:layout_alignLeft="@+id/button3"
android:text="ID更新" /> <Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/hight"
android:text="添加数据" /> <Button
android:id="@+id/button5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/id"
android:layout_below="@+id/id"
android:text="ID删除" /> <TextView
android:id="@+id/display"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/label"
android:layout_below="@+id/label"
android:layout_marginTop="23dp" /> <TextView
android:id="@+id/label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/button5"
android:layout_below="@+id/button6"
android:layout_marginTop="14dp" /> </RelativeLayout>
运行结果:

SQLite数据库与Contentprovider(2)的更多相关文章
- Android开发8:数据存储(二)——SQLite数据库和ContentProvider的使用
前言 啦啦啦各位小伙伴们许久不见了~学期末和过年期间自己忙着做其他事没能及时更新Android开发系列课程的博客,实在是罪过罪过~ 好啦~废话不多说,进入我们今天的主题.今天我们将和大家学习其他的数据 ...
- Android开发8:数据存储(二)——SQLite数据库和ContentProvider的使用
前言 啦啦啦各位小伙伴们许久不见了~学期末和过年期间自己忙着做其他事没能及时更新Android开发系列课程的博客,实在是罪过罪过~ 好啦~废话不多说,进入我们今天的主题.今天我们将和大家学习其他的数据 ...
- SQLite数据库与Contentprovider(1)
SQlite:类似mysql的数据库.把数据保存到.db文件夹中. Contentprovider:一般用于不同进程之间的数据共享(两个APP). 手动建库:http://www.runoob.com ...
- 利用SQLiteOpenHelper来管理SQLite数据库 (转)
转载自 利用SQLiteOpenHelper来管理SQLite数据库 http://blog.csdn.net/conowen/article/details/7306545 Android学习笔记( ...
- Android基础总结+SQlite数据库【申明:来源于网络】
Android基础总结+SQlite数据库[申明:来源于网络] 基础总结篇之一:Activity生命周期:http://blog.csdn.net/liuhe688/article/details/6 ...
- SQLite数据库学习小结——Frameworks层实现
3. SQLite的Frameworks层实现 3.1 Frameworks层架构 Android系统方便应用使用,在Frameworks层中封装了一套Content框架,之所以叫Content框架而 ...
- Android数据存储之SQLite 数据库学习
Android提供了五种存取数据的方式 (1)SharedPreference,存放较少的五种类型的数据,只能在同一个包内使用,生成XML的格式存放在设备中 (2) SQLite数据库,存放各种数据, ...
- Android 创建SQLite数据库(一)
Android内置了轻量级的数据库SQLite,这里将自己理解作个记录,方便自己复习. 一.首先,创建SQLite数据库比较常见的方式是通过Android提供的SQLiteOpenHelper来实现, ...
- Android中数据存储(三)——SQLite数据库存储数据
当一个应用程序在Android中安装后,我们在使用应用的过程中会产生很多的数据,应用都有自己的数据,那么我们应该如何存储数据呢? 数据存储方式 Android 的数据存储有5种方式: 1. Share ...
随机推荐
- Java基础类库
1 main方法 运行java程序的参数: 下面详细讲解main 方法为什么采用这个方法签名 1.public 修饰符:Java类由jvm调用,为了让jvm可以自由调用这个main()方 ...
- PAT1038. Recover the Smallest Number
//意识到一个重要错误,一直以为atoi,itoa是windows独有的,linux下不可用,直到刚刚... //string+=比strcat好用多了,字符比较也方便的多,但是用scanf读入str ...
- 用代码给TABLE 添加字段,设置属性并编译
AOTTableFieldList AOTTableFieldList ,TableFieldNode; TableName tableName = "SML_InventTableExt& ...
- sql基础查询语句
数据库文件百度云地址:www.pan.baidu.com 脚步:下载博客园文件:select_learn.rar 1.TOP限制返回行数[percent] * from book_info --显示前 ...
- Git入门详解
查看分支:git branch 创建分支:git branch <name> 切换分支:git checkout <name> 创建+切换分支:git checkout -b ...
- WeChat 6.3 wipe deleted chat messages as well as LINE 5.3 and above
Let me show you the WeChat version first. It is 6.3. What will happen to WeChat deleted chat message ...
- EnCase v7 search hits in compound files?
I used to conduct raw search in EnCase v6, and I'd like to see if EnCase v7 raw search could hit key ...
- VirtualBox是什么
VirtualBox 是一款 x86 虚拟机软件.原由德国innotek公司开发,2008年Sun收购了Innotek,而Sun于2010年被Oracle收购,2010年1月21日改 名成 Oracl ...
- WPF拖动DataGrid中的数据到ListBox
1.效果图: 2.XAML <Window x:Class="WpfApplication2.MainWindow" xmlns="http://schemas.m ...
- 商业模拟游戏:<柠檬汁杰克>ios游戏源码
首先柠檬汁杰克是我个人的首个cocos2d-x开发的游戏,本人虽然混迹编程十几年从未开发过游戏,这是首例. 我选这个游戏因为逻辑比较简单,也是一款苹果上的经典游戏.开发中我用到了CocoStudio, ...