【Android遊戲開發十二】(保存遊戲數據 [上文])詳解SharedPreference 與 FIleInputStream/FileOutputStream將數據存儲到SD卡中!

 李華明Himi 原創,轉載務必在明顯處註明:
轉載自 【黑米GameDev街區】 原文連接:  http://www.himigame.com/android-game/327.html

 

不少童鞋說個人代碼運行後,點擊home或者back後會程序異常,若是你也這樣遇到過,那麼你確定沒有仔細讀完Himi的博文,第十九篇Himi專門寫了關於這些錯誤的緣由和解決方法,這裏我在博客都補充說明下,省的童鞋們總疑惑這一塊;請點擊下面聯繫進入閱讀:css

【Android遊戲開發十九】(必看篇)SurfaceView運行機制詳解—剖析Back與Home按鍵及切入後臺等異常處理!html


     對於遊戲中的數據進行保存方式,在Android中經常使用的有四種保存方式,這裏我先給你們統一先簡單的介紹下:java

 

1.  SharedPreferenceandroid

此保存方式試用於簡單數據的保存,文如其名屬於配置性質的保存,不適合數據比較大的保存方式;數據庫

 

2. 文件存儲 (FIleInputStream/FileOutputStream)api

此保存方式比較適合遊戲的保存和使用,能夠保存較大的數據,由於相對於SQLite來講更容易讓童鞋們接受,此方式不只能把數據存儲在系統中也能將數據保存到SDcard中;數組

 

3.SQLite app

此保存方式比較適合遊戲的保存和使用,能夠保存較大的數據,而且能夠將本身的數據存儲到文件系統或者數據庫當中,也能夠將本身的數據存儲到SQLite數據庫當中,也能將數據保存到SDcard中;ide

 

4.ContentProvider (不推薦用於遊戲保存)學習

此保存方式不推薦用於遊戲保存,由於此方式不只能存儲較大數據,還支持多個程序之間就的數據進行交換!!! 可是因爲遊戲中基本就不可能去訪問外部應用的數據,因此對於此方式我不予講解, 有興趣的能夠去自行百度 google 學習;

 

以上簡單的對幾種經常使用的保存方式進行的概述,那麼,下面會詳細的去分析每一個的優缺點以及每種保存的實現和須要注意的地方!

 

下面我首先向你們介紹第一種保存方式: 

 

             保存方式之:  《SharedPreference》


           優勢: 簡單、方便、適合簡單數據的快速保存

           缺點:1.存數的文件只能在同一包內使用,不能在不一樣包之間使用!

            2.默認將數據存放在系統路徑下 /data/data/com.himi/  ,沒有找到放SD卡上的方法。

           總結:其實本保存方式如同它的名字同樣是個配置保存,雖然方便,但只適合存儲比較簡單的數據!

 

main.xml  :

 

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical" android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<TextView android:layout_width="fill_parent"
		android:layout_height="wrap_content" android:text="保存數據練習!"
		android:textSize="20sp" android:textColor="#ff0000" android:id="@+id/tv_title" />
	<TextView android:layout_width="fill_parent"
		android:layout_height="wrap_content" android:text="請輸入賬號" />
	<EditText android:layout_width="fill_parent"
		android:layout_height="wrap_content" android:id="@+id/editText_Login"
		android:text=""></EditText>
	<TextView android:layout_width="fill_parent"
		android:layout_height="wrap_content" android:text="請輸入密碼" />
	<EditText android:layout_width="fill_parent"
		android:layout_height="wrap_content" android:id="@+id/editText_Password"
		android:text=""></EditText>
	<Button android:id="@+id/button_save" android:layout_width="wrap_content"
		android:layout_height="wrap_content" android:text="保存"></Button>
	<Button android:id="@+id/button_load" android:layout_width="wrap_content"
		android:layout_height="wrap_content" android:text="取出數據"
		android:visibility="invisible"></Button>
</LinearLayout>
 

先把xml文件放上來的緣由是由於我在此篇中介紹的 SharedPreference 和 文件存儲 (FIleInputStream/FileOutputStream),都共用此xml,很簡單,兩個textview 兩個 editview 以及兩個button,這裏就很少說了;

 

下面是SharedPreference 的代碼實現和詳細講解:

 

  

/** 
 * @author Himi 
 * @保存方式:SharedPreference 
 * @注意:SharedPreference 能夠跨程序包使用,多謝二樓童鞋提醒!
 * @操做模式: Context.MODE_PRIVATE:新內容覆蓋原內容 
 *            Context.MODE_APPEND:新內容追加到原內容後 
 *            Context.MODE_WORLD_READABLE:容許其餘應用程序讀取 
 *            Context.MODE_WORLD_WRITEABLE:容許其餘應用程序寫入,會覆蓋原數據。 
 */  
public class MainActivity extends Activity implements OnClickListener {  
    private EditText et_login, et_password;  
    private Button btn_save;  
    private TextView tv_title;  
    private SharedPreferences sp;  
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,  
                WindowManager.LayoutParams.FLAG_FULLSCREEN);  
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);  
        setContentView(R.layout.main);  
        btn_save = (Button) findViewById(R.id.button_save);  
        btn_save.setOnClickListener(this);  
        et_login = (EditText) findViewById(R.id.editText_Login);  
        et_password = (EditText) findViewById(R.id.editText_Password);  
        tv_title = (TextView) findViewById(R.id.tv_title);  
        // 這裏咱們先調用 getSharedPreferences()來實例化一個SharedPreferences,  
        // 第二個參數是指:操做模式(上面對各類操做模式已有解釋)  
        sp = getSharedPreferences("Setting_himi", MODE_PRIVATE);  
        /* 
         * 下面代碼是咱們要在程序剛啓動的時候咱們來讀取以前的數據,  
         * 固然咱們尚未保存任何數據因此確定找不到!! 
         * 若是找不到也不要緊會默認返回一個參數值,看下面的方法含義便知! 
         */  
        sp.getString("login", "");  
        // getString()相似哈希表,一個key 一個volue ,  
        // 這個方法若是找不到對應的第一個參數(key),那麼將以第二個參數做爲此key的返回值  
        et_login.setText(sp.getString("login", ""));  
        et_password.setText(sp.getString("password", ""));  
    }  
    @Override  
    public void onClick(View v) {  
        if (v == btn_save) {  
            if (et_login.getText().toString().equals(""))  
                tv_title.setText("請輸入賬號!");  
            else if (et_password.getText().toString().equals(""))  
                tv_title.setText("請輸入密碼!");  
            else {  
                sp.edit()  
                  .putString("login", et_login.getText().toString())  
                  .putString("password", et_password.getText().toString())  
                  .commit();  
                // 從sp.edit()開始進入編輯狀態,直到commit()提交!  
                tv_title.setText("保存成功!可從新打開此程序,測試是否已經保存數據!" +  
                    "/n(或者在'File Explorer'窗口下-data-data-com.himi路徑下" +  
                    "是否存在" +"了'Setting_himi.xml')");  
            }  
        }  
    }  
}
 

代碼中的註釋的很清楚了,比較簡單,很少說了。

 


             保存方式之:  《文件存儲 OutputStream/InputStream》

 

 

           優勢: 1.適合遊戲存儲,能存儲較大數據;

                     2.不只能存儲到系統中,也能存儲到SD卡中!

           總結:若是童鞋們對SQL不太熟習的話那麼選擇此種方式最爲合適的啦、嘿嘿

 

 

/**
 * @author Himi
 * @保存方式:Stream 數據流方式
 * @注意1:默認狀況下,使用openFileOutput 方法建立的文件只能被其調用的應用使用,
 *         其餘應用沒法讀取這個文件,若是須要在不一樣的應用中共享數據;
 *         
 * @注意2:由於android  os內部閃存有限,因此適合保存較少的數據,固然咱們也有解決的方法,
 *         就是把數據保存在SD開中,這樣就能夠了,後面我也會向你們講解   !
 *         
 * @提醒1 調用FileOutputStream 時指定的文件不存在,Android 會自動建立它。
 *        另外,在默認狀況下,寫入的時候會覆蓋原 文件內容,若是想把新寫入的內
 *        容附加到原文件內容後,則能夠指定其mode爲Context.MODE_APPEND。
 *        
 * @提醒2 啓動程序就初始化的時候必定要注意處理!代碼中有註釋!必定要仔細看!
 * 
 * @提醒3 這裏我給你們講兩種方式,一種是原生態file流來寫入/讀入,
 *        另一種是用Data流包裝file流進行寫入/讀入 其實用data流來包裝進行操做;
 *        緣由是:包裝後支持了更多的寫入/讀入操做,好比:file流寫入不支持
 *        writeUTF(String str); 可是用Data包裝後就會支持。
 *        
 * @操做模式: Context.MODE_PRIVATE:新內容覆蓋原內容
 *            Context.MODE_APPEND:新內容追加到原內容後
 *            Context.MODE_WORLD_READABLE:容許其餘應用程序讀取
 *            Context.MODE_WORLD_WRITEABLE:容許其餘應用程序寫入,會覆蓋原數據。
 */
public class MainActivity extends Activity implements OnClickListener {
	private EditText et_login, et_password;
	private Button btn_save;
	private TextView tv_title;
	private FileOutputStream fos;
	private FileInputStream fis;
	private DataOutputStream dos;
	private DataInputStream dis;
	@Override
	public void onCreate(Bundle savedInstanceState) {
		String temp = null;
		super.onCreate(savedInstanceState);
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
				WindowManager.LayoutParams.FLAG_FULLSCREEN);
		this.requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.main);
		btn_save = (Button) findViewById(R.id.button_save);
		btn_save.setOnClickListener(this);
		et_login = (EditText) findViewById(R.id.editText_Login);
		et_password = (EditText) findViewById(R.id.editText_Password);
		tv_title = (TextView) findViewById(R.id.tv_title);
		try {
			// openFileInput 不像 sharedPreference 中
			// getSharedPreferences的方法那樣找不到會返回默認值,
			// 這裏找不到數據文件就會報異常,因此finally裏關閉流尤其重要!!!
			if (this.openFileInput("save.himi") != null) {  
				// --------------單純用file來讀入的方式-----------------
				// fis = this.openFileInput("save.himi");
				// ByteArrayOutputStream byteArray = new
				// ByteArrayOutputStream();
				// byte[] buffer = new byte[1024];
				// int len = 0;
				// while ((len = fis.read(buffer)) > 0) {
				// byteArray.write(buffer, 0, len);
				// }
				// temp = byteArray.toString();
				// -------------- 用data流包裝後的讀入的方式------------
				fis = this.openFileInput("save.himi");//備註1 
				dis = new DataInputStream(fis);
				et_login.setText(dis.readUTF());
				et_password.setText(dis.readUTF());
				// 這裏也是在剛啓動程序的時候去讀入存儲的數據
				// 讀的時候要注意順序; 例如咱們寫入數據的時候
				//先寫的字符串類型,咱們也要先讀取字符串類型,一一對應!
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			// 在finally中關閉流!由於若是找不到數據就會異常咱們也能對其進行關閉操做 ;
			try {
				if (this.openFileInput("save.himi") != null) {
					// 這裏也要判斷,由於找不到的狀況下,兩種流也不會實例化。
					// 既然沒有實例化,還去調用close關閉它,確定"空指針"異常!!!
					fis.close();
				}
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	@Override
	public void onClick(View v) {
		if (Environment.getExternalStorageState() != null) {
			// 這個方法在試探終端是否有sdcard!
			Log.v("Himi", "有SD卡");
		}
		if (v == btn_save) {
			if (et_login.getText().toString().equals(""))
				tv_title.setText("請輸入賬號!");
			else if (et_password.getText().toString().equals(""))
				tv_title.setText("請輸入密碼!");
			else {
				try { 
					// ------單純用file來寫入的方式--------------
					//fos = new FileOutputStream(f);
					// fos.write(et_login.getText().toString().getBytes());
					// fos.write(et_password.getText().toString().getBytes());
					// ------data包裝後來寫入的方式--------------
					fos = this.openFileOutput("save.himi", MODE_PRIVATE);//備註2
					dos = new DataOutputStream(fos);
					dos.writeUTF(et_login.getText().toString());
					dos.writeUTF(et_password.getText().toString());
					tv_title.setText("保存成功!可從新打開此程序,測試是" +
							"否已經保存數據!/n(或者在'File Explorer'"	+
							"窗口下-data-data-com.himi-files路徑下" +
							"是否存在了'save.himi')");
				} catch (FileNotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} finally {
					// 在finally中關閉流 這樣即便try中有異常咱們也能對其進行關閉操做 ;
					try {
						dos.close();
						fos.close();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}
	}
}
 

以上代碼中實現了兩種流形式來完成寫入和讀入,這裏咱們爲何要使用Data流來包裝,其實不光是得到更多的操做方式,最主要的是方便快捷,你好比用file來讀入的時候,明顯的複雜了一些不說,它還一次性把全部數據都取出來了,不便於對數據的處理!

 

強調的有幾點:

1: 在一開始對數據的訪問再次提醒童鞋們,這個跟sharedPreference的獲取方式不同,sharedPreference 的獲取方式能夠獲得一個默認的值,可是你用我們獲取的是個文件 並且直接就去open這個文件,一旦不存在一定異常,因此這一塊的異常處理,以及finally的處理必定要處理得當。

2.其實在一開始用data包裝的時候發現寫入的字符串在讀入的時候發現字符亂碼了,查了api才發現,api規定當寫入字符串的時候必須寫入UTF-8格式的編碼,可是後來不知道怎麼了就沒事了。 - -、因此這裏若是童鞋們遇到此問題,我給出你們一個解決方法,就是在寫入的時候咱們不要去DataOutputStream 來包裝而是用,OutputStreamWriter ,由於在構造的能夠設定編碼!

                                 OutputStreamWriter osw = new OutputStreamWriter(fis,"UTF-8");

 String  content = EncodingUtils.getString(buffer, "UTF-8");  這個也能把字符數組轉碼制!

這樣寫入的就確定是UTF-8編碼的字符啦、

 

下面介紹如何把咱們的數據經過 OutputStream/InputStream 存入SD卡中!

 

   其實將咱們的數據放入SD卡中,無疑就須要對代碼進行兩處的修改:

 

注意:必定要有SD卡!對於如何建立SD卡在前一篇文章中已經說了兩種方式,不會的童鞋能夠去看下;

第一:檢查是否裝有SD卡;  

 

第二: 修改讀入的地方(備註1)

         fis = this.openFileInput("save.himi"); //這裏沒有路徑,路徑是默認的 data-data-com.himi-files下 

      替換成咱們的SD卡的路徑就能夠了:

          File path = new File("/sdcard/himi/save.himi");//這裏新建一個File目錄路徑

          fis = new FileInputStream(path);傳入路徑

 

第三 : 修改寫入的地方(備註2)

        fos = this.openFileOutput("save.himi", MODE_PRIVATE);這裏也是默認路徑,須要對其修改,

       注意:這裏修改了,那麼在finally中的斷定你們也要對應的適當修改;

 

注意:若是是系統路徑,當沒有此文件的時候,android 會默認建立一個!可是咱們放入SD卡的時候要本身建立目錄路徑和文件!

 


 

 

if (Environment.getExternalStorageState() != null) {// 這個方法在試探終端是否有sdcard!
				Log.v("Himi", "有SD卡");
				File path = new File("/sdcard/himi");// 建立目錄
				File f = new File("/sdcard/himi/save.himi");// 建立文件
				if (!path.exists()) {// 目錄不存在返回false
					path.mkdirs();// 建立一個目錄
				}
				if (!f.exists()) {// 文件不存在返回false
					f.createNewFile();// 建立一個文件
				}
				fos = new FileOutputStream(f);// 將數據存入sd卡中
			}

 

第四: 由於咱們要在SD卡中進行寫入的操做,因此要在配置文件中聲明權限!

 

 

AndroidMainfest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.himi"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".MainActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity> 
    </application>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 
    <uses-sdk android:minSdkVersion="4" />
</manifest>
 

 

 這一句就是啦~

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

爲了讓你們看到所放的位置,因此把整個xml放出來供參考;

 

 

那麼當建立路徑和文件的時候,咱們對其檢查SD卡中是否已經存在exists()方法 ,若是已經存在就不去建立,這樣避免下次再次寫入數據的時候又新建了文件和路徑、

 

其實咱們在能夠在啓動程序的時候判斷若是沒有此文件,咱們能夠直接緊接着建立一個文件,這些都屬於優化上的了,我主要是讓你們引入,學會,那麼其餘的簡化啦,優化啦,其餘方式去實現啦都留給各位同窗本身了、


OK、今天就先介紹到這裏,後面會單獨剖析SQLite如何存入數據,以及對數據操做的! 但願你們繼續關注!

 

(推薦你們訂閱本博客,由於咱的更新速度但是很快的~娃哈哈)

源碼下載地址: http://www.himigame.com/android-game/327.html

 

 

新的一年了小明祝福你們新的一年裏,事業順利,身體健康,全家幸福美滿!

上張本項目的截圖:

 

                         

 

 



原文連接: http://blog.csdn.net/xiaominghimi/article/details/6113019
相關文章
相關標籤/搜索