【读书笔记《Android游戏编程之从零开始》】20.游戏开发基础(游戏数据存储)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介:

对于数据的存储,Android 提供了4种保存方式。

(1)SharedPreference

此方法适用于简单数据的保持,文如其名,属于配置性质的保存,不适合比较大的情况,默认存放在手机内存里

(2)FileInputStream/FileOutputStream

此方式比较适合游戏的保存和使用,流文件数据存储可以保持较大的数据,而且通过此方式不仅能把数据存储在手机内存中,也能将数据保存到手机额SDcard中。

(3)SQLite

此方式也适合游戏的保存和使用,不仅可以保存较大的数据,而且可以将自己的数据存储到文件系统或者数据库当中,如SQLite数据库,也能将数据保存到SDcard 中。

(4)ContentProvider

此方式不推荐用于游戏保存,虽然此方式能存储较大数据,还支持多个程序之间的数据进行交换,但游戏中基本就不可能去访问外部应用程序的数据。

 

1.SharedPreference

SharedPreference 实例是通过Context 对象得到的:

Context.getSharePreference(String name,int mode)

作用:利用Context 对象获取一个SharedPreference 实例

参数1:生成保持记录的文件名

参数2:操作模式

 

SharedPreference 实例的操作模式一共有四种:

Context.MODE_PRIVATE:新内容覆盖原内容。

Context.MODE_APPEND:新内容追加到原内容后。

Context.MODE_WORLD_READABLE:允许其他应用程序读取。

Context.MODE_WORLD_WRITEABLE:允许其他应用程序写入,会覆盖原数据。

 

SharedPreference 常用函数:

getFloat(String key,float defValue)

getInt(String key,int defValue)

getLong(String key,long defValue)

getString(String key,String defValue)

getBoolean(String key,boolean defValue)

 

SharedPreference 常用函数的作用是获取存储文件中的值,根据方法不同获取不同对应的类型值,一般第一个参数为索引Key值,第二个参数为在存储文件中找不到对应 value 值时,默认的返回值。

 

对应的,在对存储文件的数据进行存入操作时,首先需要利用 SharedPreference 实例得到一个编辑对象:

SharedPreference.Editor edit;

得到编辑对象后就可以对 SharedPreference 中的数据进行操作。

SharedPreference.Editor.putFloat(arg0,arg1)

SharedPreference.Editor.putInt(arg0,arg1)

SharedPreference.Editor.putLong(arg0,arg1)

SharedPreference.Editor.putString(arg0,arg1)

SharedPreference.Editor.putBoolean(arg0,arg1)

以上方法的作用是对存储的数据进行操作(写入、保存),其中第一个参数是需要保存数据额Key值索引,第二个参数是需要保存的数据。 

到此进行了保存和修改,还需要将其编辑的数据进行提交方可完成存入和修改:

SharedPreference.Editor.commit() 

如果想删除存储文件中的一条数据,可以使用以下函数:

SharedPreference.Editor.clear()

 

下面用一个简单小游戏进行说明,先看下效果图:

 

 新建项目,游戏框架为 SurfaceView 游戏框架,修改 MySurfaceView 类如下:

  View Code

 

2.流文件存储

利用前面 SharedPreference 的例子,去除 SharedPreference 存储部分,改用流文件形式进行保存,只需要修改其中“保存”和“读取”操作,也就是修改“按键事件”:

复制代码
    /**
     * 按键事件监听
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // 用到的读出、写入流
        FileOutputStream fos = null;
        FileInputStream fis = null;
        DataOutputStream dos = null;
        DataInputStream dis = null;
        // 上键保存游戏状态
        if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
            try {
                // 利用Activity 实例打开流文件得到一个写入流
                fos = MainActivity.instance.openFileOutput("save.yc",
                        Context.MODE_PRIVATE);
                // 将写入流封装在数据写入流中
                dos = new DataOutputStream(fos);
                //写入一个int 类型(将圆形所在格子的下表写入流文件中)
                dos.writeInt(creentTileIndex);
            } catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }finally{
                //即使保存时发生异常也要关闭流
                try {
                    if(fos!=null) fos.close();
                    if(dos!=null)dos.close();
                } catch (Exception e) {
                    // TODO: handle exception
                    e.printStackTrace();
                }
            }
            // 下键读取游戏状态
        } else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
            try {
                if(MainActivity.instance.openFileInput("save.yc")!=null)
                {
                    try {
                        //利用Activity 实例打开流文件得到一个读出流
                        fis = MainActivity.instance.openFileInput("save.yc");
                        //将读出流封装在数据读入流中
                        dis = new DataInputStream(fis);
                        //读出一个int 类型赋值与圆形所在格子的下标
                        creentTileIndex =dis.readInt();
                    } catch (Exception e) {
                        // TODO: handle exception
                    }finally{
                        if(fis!=null)fis.close();
                        if(dis!=null)dis.close();
                    }
                }
            } catch (Exception e) {
                // TODO: handle exception
            }
            // 圆形的移动
        } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
            if (creentTileIndex > 0) {
                creentTileIndex -= 1;
            }
        } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
            if (creentTileIndex < 8) {
                creentTileIndex += 1;
            }
        }
        return super.onKeyDown(keyCode, event);
    }
复制代码

不管是读入还是写入,都是通过Activity 打开流文件得到输入输出流。当需要写入流文件时,如果打开的流文件不存在,那么Android 会自动生成对应的流文件;而当需要读入流文件时,首先应判断流文件是否存在,一旦流文件不存在,就会抛出异常。

这里的流形式的保存操作比较简单,需要注意的是:

● 读流时,一定要记得判断是否存在需要操作的流文件;

● 写入和读入的数据类型要配对,顺序也不能错;例如:写入时,先写入了一个 Int值,然后又写入了一个String 值;那么读入时,也应该先读 Int 类型,然后再读 String 类型;

● 流一旦打开一定要关闭,为了避免流操作出现异常,需确保正常关闭流,应该将关闭操作写在finally 语句中;

● file 流使用 Data 流进行了封装,这样做的原因是可以获得更多的操作方式,便于对数据的处理。

以上是使用流文件的保存方式,但是也只是将保存后的流文件默认放在了系统内存里。一般游戏的数据可能会有很多,所以不应该放在手机内存中,而是放在 SDCard 中,这样就不要担心系统因游戏保存的数据过多导致内存不足等问题。

将流文件保存在 SDCard 中的详细步骤如下:

(1)声明读取权限:

Android 中的一些操作,比如:读取通讯录信息、发送信息、使用联网、GPRS等功能都需要在项目 AndroidManifest.xml 中声明使用权限,然后才可以正常使用其功能。

当然在很多时候,是不知道是否需要声明添加权限的,其实这个也不用知道,因为如果用到这些需要声明权限的功能,且恰好没有声明的情况下,在LogCat 中是会报异常的,其异常则提醒需要添加对应的权限。

写入权限如下:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

(2)创建目录和存储文件

 使用 SDCard 的方式进行存储数据,写入的时候 Android 不会跟存储系统默认路径那样默认生成存储文件,所以必须自己来创建;如果存储的文件有自定义路径的话,那么这个路径也需要手动添加。

假定存储文件在 SDCard 的路径为 /sdcard/yc/save.yc 。

① 首先需要创建路径 /sdcard/yc

File path = new File(" /sdcard/yc");// 创建目录  
if(!path.exists())// 目录存在返回true
{
  path.mkdirs();// 创建一个目录                      
}

boolean File.exists()

 作用:判断是否存在当前目录

返回值:当前目录存在返回true

boolean File.mkdirs()

作用:创建一个目录

返回值:当创建成功返回true

 

②然后创建存储文件:/sdcard/yc/save.yc

File path_File = new File(" /sdcard/yc/save.yc");// 创建文件  
if(!path_File.exists())// 文件存在返回true
{
   path_File.createNewFile();// 创建一个文件                      
}

boolean File.createNewFile()

作用:创建一个文件

返回值:创建成功返回true

 

(3)通过加载指定路径的存储文件获取输入输出流

● 输入流:FileInputStream fis = new FileInputStream(File file);

● 输出流:FileOutputStream fos = new FileOutputStream(File file);

 

除此之外还需要知道一点,因为有时手机并没有安装 SDCard ,或者当前SDCard 处于被移除的状态时,为了避免这两种情况带来的异常,需要通过下面方法获取当前手机设备SDCard 的状态:

String Environment.getExternalStorageStorageState()

作用:获取当前SDCard的状态

返回值:当前 SDCard 不存在时,返回null ;当 SDCard 处于移除状态时,返回“removed”;

 

所以当默认手机设备存在 SDCard 时,保存在 SDCard 中 ;当 SDCard 不存在或者正处于被移除的状态时,默认将数据保存在手机内存中。上面的“按键事件”修改如下:

  View Code

 

3.SQLite 轻量级数据库

此方式也适合游戏的保存和使用,不仅可以保存较大的数据,而且可以将自己的数据存储到文件系统或者数据库当中,如SQLite数据库,也能将数据保存到SDcard 中。

SQLite是一款轻量级数据库,它的设计目的是嵌入式,而且它占用的资源非常少,在嵌入式设备中,只需要几百KB!

SQLite的特性:
轻量级:使用 SQLite 只需要带一个动态库,就可以享受它的全部功能,而且那个动态库的尺寸想当小。
独立性:SQLite 数据库的核心引擎不需要依赖第三方软件,也不需要所谓的“安装”。
隔离性:SQLite 数据库中所有的信息(比如表、视图、触发器等)都包含在一个文件夹内,方便管理和维护。
跨平台:SQLite 目前支持大部分操作系统,不至电脑操作系统更在众多的手机系统也是能够运行,比如:Android。
多语言接口:SQLite 数据库支持多语言编程接口。
安全性:SQLite 数据库通过数据库级上的独占性和共享锁来实现独立事务处理。这意味着多个进程可以在同一时间从同一数据库读取数据,但只能有一个可以写入数据.

优点:1.能存储较多的数据。2.能将数据库文件存放到SD卡中! 


什么是 SQLiteDatabase? 

     一个 SQLiteDatabase 的实例代表了一个SQLite 的数据库,通过SQLiteDatabase 实例的一些方法,我们可以执行SQL 语句,对数        据库进行增、删、查、改的操作。需要注意的是,数据库对于一个应用来说是私有的,并且在一个应用当中,数据库的名字也是惟一的。

 
什么是 SQLiteOpenHelper ?

     根据这名字,我们可以看出这个类是一个辅助类。这个类主要生成一个数据库,并对数据库的版本进行管理。当在程序当中调用这个类的方法getWritableDatabase(),或者getReadableDatabase()方法的时候,如果当时没有数据,那么Android 系统就会自动生成一个数据库。SQLiteOpenHelper 是一个抽象类,我们通常需要继承它,并且实现里边的3 个函数

什么是 ContentValues 类?

     ContentValues 类和Hashmap/Hashtable 比较类似,它也是负责存储一些名值对,但是它存储的名值对当中的名是一个String 类型,而值都是基本类型。
 
什么是 Cursor ?

     Cursor 在Android 当中是一个非常有用的接口,通过Cursor 我们可以对从数据库查询出来的结果集进行随机的读写访问。

同样利用前面 SharedPreference 的例子,去除 SharedPreference 存储部分,改用SQLite进行保存。

① 首先新建一个类MySQLiteOpenHelper继承SQLiteOpenHelper;写一个构造,重写两个函数:

  View Code

② 在MySurfaceView类中创建对应的实例

private MySQLiteOpenHelper myOpenHelper;// 创建一个继承SQLiteOpenHelper类实例
private SQLiteDatabase db;

③ 在SurfaceView初始化函数中实例一个数据库辅助器

// 实例一个数据库辅助器
myOpenHelper = new MySQLiteOpenHelper(MainActivity.instance);

④ 最后修改按键监听事件,其实只需要修改其中“保存”和“读取”操作,代码如下:

复制代码
    /**
     * 按键事件监听
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        db = myOpenHelper.getWritableDatabase(); // 实例数据库
        // 上键保存游戏状态
        if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
            // ---------------------- SQL语句删除--------------
            String DELETE_DATA = "DELETE FROM " + MySQLiteOpenHelper.TABLE_NAME;
            db.execSQL(DELETE_DATA);
            
            // ---------------------- SQL语句插入--------------
            String INSERT_DATA = "INSERT INTO " + MySQLiteOpenHelper.TABLE_NAME
                    + "(" + MySQLiteOpenHelper.Param2 + ") values ("
                    + creentTileIndex + ")";
            db.execSQL(INSERT_DATA);
            // 下键读取游戏状态
        } else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
            // 数据查询
            Cursor cur = db.rawQuery("SELECT * FROM "
                    + MySQLiteOpenHelper.TABLE_NAME, null);
            if (cur != null) {
                cur.moveToNext();
                creentTileIndex = cur.getInt(1);
            }
        } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
            if (creentTileIndex > 0) {
                creentTileIndex -= 1;
            }
        } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
            if (creentTileIndex < 8) {
                creentTileIndex += 1;
            }
        }
        return super.onKeyDown(keyCode, event);
    }
复制代码

说明:

在Android中查询数据是通过Cursor类来实现的,当我们使用SQLiteDatabase.query()方法时,会得到一个Cursor对象,Cursor指向的就是每一条数据。它提供了很多有关查询的方法,具体方法如下:

 以下是方法和说明: 

 move    以当前的位置为参考,将Cursor移动到指定的位置,成功返回true, 失败返回false 

 moveToPosition 将Cursor移动到指定的位置,成功返回true,失败返回false 

 moveToNext  将Cursor向前移动一个位置,成功返回true,失败返回false 

 moveToLast    将Cursor向后移动一个位置,成功返回true,失败返回 false。 

 movetoFirst  将Cursor移动到第一行,成功返回true,失败返回false 

 isBeforeFirst     返回Cursor是否指向第一项数据之前 

 isAfterLast  返回Cursor是否指向最后一项数据之后 

 isClosed          返回Cursor是否关闭

 isFirst    返回Cursor是否指向第一项数据 

 isLast       返回Cursor是否指向最后一项数据 

 isNull       返回指定位置的值是否为null 

 getCount     返回总的数据项数 

 getInt          返回当前行中指定的索引数据






本文转自叶超Luka博客园博客,原文链接:http://www.cnblogs.com/yc-755909659/p/4223999.html,如需转载请自行联系原作者
相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
1月前
|
存储 Java 数据库
Android数据存储:什么是Room Persistence Library?
Android数据存储:什么是Room Persistence Library?
68 0
|
1月前
|
存储 数据库连接 数据库
Android数据存储:解释SQLite数据库在Android中的使用。
Android数据存储:解释SQLite数据库在Android中的使用。
54 0
|
8月前
|
数据库连接 Android开发
安卓android期末项目之纸牌游戏之三
安卓android期末项目之纸牌游戏之三
68 0
|
8月前
|
存储 Android开发 数据库管理
Android安卓项目猜纸牌游戏之二 实体类
Android安卓项目猜纸牌游戏之二 实体类
31 0
|
1月前
|
存储 XML Android开发
Android Studio App开发入门之数据存储中共享参数SharedPreferneces的讲解及使用(附源码 超详细必看)
Android Studio App开发入门之数据存储中共享参数SharedPreferneces的讲解及使用(附源码 超详细必看)
39 0
|
1月前
|
算法 Java 定位技术
分享104个益智休闲安卓游戏源码,总有一款适合你
分享104个益智休闲安卓游戏源码,总有一款适合你
272 1
|
1月前
|
存储 XML 文件存储
Android数据存储的五种方式1
Android数据存储的五种方式
71 2
|
1月前
|
存储 安全 Android开发
Android数据存储:请解释ContentProvider是什么,它的主要作用是什么?
ContentProvider是Android的四大组件之一,主要负责结构化数据的管理与共享。它封装数据并提供安全的访问接口,通过URI实现应用间数据的标准化共享。ContentResolver与ContentProvider协作,处理数据的CRUD操作,使得其他应用能方便地调用和操作数据。
27 0
|
1月前
|
存储 Android开发
Android数据存储的五种方式3
Android数据存储的五种方式
11 0
|
1月前
|
存储 SQL 数据库
Android数据存储的五种方式2
Android数据存储的五种方式
45 0