天天看點

20189200餘超 2018-2019-2 移動平台應用開發實踐第十周作業

20189200餘超 2018-2019-2 移動平台應用開發實踐第十周作業

偏好

在Android應用中,我們常需要記錄使用者設定的一些偏好參數,,此時我們就需要用SharedPreferences和Editor将這些資訊儲存下來,在下次登入時讀取。

SharedPreferences儲存的資料主要類似于配置資訊格式的資料,是以它儲存資料的形式為key-value對,下面我們來看下執行個體代碼。

首先是界面布局,比較簡單,就是一個普通的登陸界面.

<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=".MainActivity" >
<EditText 
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:id="@+id/account"
    />
<EditText 
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:id="@+id/password"
    android:layout_below="@id/account"
    />
<Button 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/password"
        android:text="儲存參數"
        android:id="@+id/save"
        android:onClick="save"
 />
</RelativeLayout>
           

這是自定義的Preferences 類,用來實作資料的儲存 ,可在Android的内置存儲空間産生一檔案。

import android.R.integer;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.widget.EditText;

public class Preferences {

    private Context context;
    public Preferences(Context context)
    {
        this.context=context;
    }
    
    
    public void save(String name, Integer valueOf) 
    {
        //儲存檔案名字為"shared",儲存形式為Context.MODE_PRIVATE即該資料隻能被本應用讀取
        SharedPreferences preferences=context.getSharedPreferences("shared",Context.MODE_PRIVATE);
        
        Editor editor=preferences.edit();
        editor.putString("name", name);
        editor.putInt("age", valueOf);
        
        editor.commit();//送出資料
    }
}
           

下面是Mainactivity的代碼。在activity的oncreate階段我們加載本地的資料。

import java.util.HashMap;
import java.util.Map;

import android.R.integer;
import android.os.Bundle;
import android.app.Activity;
import android.content.SharedPreferences;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity {

    private EditText account,passworad;
    Preferences prefer;//自定義的類
    SharedPreferences preference; 

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        account=(EditText)findViewById(R.id.account);
        passworad=(EditText)findViewById(R.id.password);
        
        //擷取本地的資料
       preference=getSharedPreferences("shared", MODE_PRIVATE);
        Map<String, String> map=new HashMap<String, String>();
        map.put("name",preference.getString("name",""));
        map.put("age", String.valueOf(preference.getInt("age", 0)));        
        account.setText(map.get("name"));
        passworad.setText(map.get("age"));
        
    }

    //儲存檔案的方法
    public void save(View v) {
    String name=account.getText().toString();
    String age=passworad.getText().toString();
    prefer=new Preferences(this);
    prefer.save(name,Integer.valueOf(age));
    Toast.makeText(getApplicationContext(), "儲存完成", Toast.LENGTH_SHORT).show();

    }
    
     @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

}
           

我們看一下效果.

20189200餘超 2018-2019-2 移動平台應用開發實踐第十周作業

點選儲存參數,出現儲存完成則說明我們已經儲存成功了,在下次登入的時候可以看到這些參數還在。因為記錄檔案是在内置空間中的,是以我們在SD卡中找不到該檔案,

如果有root權限的手機可以下載下傳個RE檔案管理,我們可以再/data/data/的路徑找到很多應用程式的内置檔案夾,我們可以在這些檔案夾中看到一個shared_prefs檔案夾,

裡面就有我們剛剛設定而産生的xml檔案。

20189200餘超 2018-2019-2 移動平台應用開發實踐第十周作業

操作空間

我們先來考慮這樣一個問題:

打開手機設定,選擇應用管理,選擇任意一個App,然後你會看到兩個按鈕,一個是清除緩存,另一個是清除資料,那麼當我們點選清除緩存的時候清除的是哪裡的資料?當我們點選清除資料的時候又是清除的哪裡的資料?讀完本文相信你會有答案。

在android開發中我們常常聽到這樣幾個概念,記憶體,内部存儲,外部存儲,很多人常常将這三個東西搞混,那麼我們今天就先來詳細說說這三個東西是怎麼回事?

記憶體,我們在英文中稱作memory,内部存儲,我們稱為InternalStorage,外部存儲我們稱為ExternalStorage,這在英文中本不會産生歧義,但是當我們翻譯為中文之後,前兩個都簡稱為記憶體,于是,混了。

那麼究竟什麼是内部存儲什麼是外部存儲呢?

首先我們打開DDMS,有一個File Explorer,如下:

徹底了解android中的内部存儲與外部存儲0

這裡有三個檔案夾需要我們重視,一個是data,一個是mnt,一個是storage,我們下面就詳細說說這三個檔案夾。

1.内部存儲

data檔案夾就是我們常說的内部存儲,當我們打開data檔案夾之後(沒有root的手機不能打開該檔案夾),裡邊有兩個檔案夾值得我們關注,如下:

20189200餘超 2018-2019-2 移動平台應用開發實踐第十周作業

一個檔案夾是app檔案夾,還有一個檔案夾就是data檔案夾,app檔案夾裡存放着我們所有安裝的app的apk檔案,其實,當我們調試一個app的時候,可以看到控制台輸出的内容,有一項是uploading .....就是上傳我們的apk到這個檔案夾,上傳成功之後才開始安裝。另一個重要的檔案夾就是data檔案夾了,這個檔案夾裡邊都是一些包名,打開這些包名之後我們會看到這樣的一些檔案:

1.data/data/包名/shared_prefs

2.data/data/包名/databases

3.data/data/包名/files

4.data/data/包名/cache

如果打開過data檔案,應該都知道這些檔案夾是幹什麼用的,我們在使用sharedPreferenced的時候,将資料持久化存儲于本地,其實就是存在這個檔案中的xml檔案裡,我們App裡邊的資料庫檔案就存儲于databases檔案夾中,還有我們的普通資料存儲在files中,緩存檔案存儲在cache檔案夾中,存儲在這裡的檔案我們都稱之為内部存儲。

2.外部存儲

外部存儲才是我們平時操作最多的,外部存儲一般就是我們上面看到的storage檔案夾,當然也有可能是mnt檔案夾,這個不同廠家有可能不一樣。

一般來說,在storage檔案夾中有一個sdcard檔案夾,這個檔案夾中的檔案又分為兩類,一類是公有目錄,還有一類是私有目錄,其中的公有目錄有九大類,比如DCIM、DOWNLOAD等這種系統為我們建立的檔案夾,私有目錄就是Android這個檔案夾,這個檔案夾打開之後裡邊有一個data檔案夾,打開這個data檔案夾,裡邊有許多包名組成的檔案夾。

說到這裡,我想大家應該已經可以厘清楚什麼是内部存儲什麼是外部存儲了吧?好,厘清楚之後我們就要看看怎麼來操作内部存儲和外部存儲了。

3.操作存儲空間

首先,經過上面的分析,大家已經明白了,什麼是内部存儲,什麼是外部存儲,以及這兩種存儲方式分别存儲在什麼位置,一般來說,我們不會自己去操作内部存儲空間,沒有root權限的話,我們也沒法操作内部存儲空間,事實上内部存儲主要是由系統來維護的。不過在代碼中我們是可以通路到這個檔案夾的。由于内部存儲空間有限,在開發中我們一般都是操作外部存儲空間,Google官方建議我們App的資料應該存儲在外部存儲的私有目錄中該App的包名下,這樣當使用者解除安裝掉App之後,相關的資料會一并删除,如果你直接在/storage/sdcard目錄下建立了一個應用的檔案夾,那麼當你删除應用的時候,這個檔案夾就不會被删除。

經過以上的介紹,我們可以總結出下面一個表格:

20189200餘超 2018-2019-2 移動平台應用開發實踐第十周作業

一目了然,什麼是内部存儲,什麼是外部存儲。

如果按照路徑的特征,我們又可以将檔案存儲的路徑分為兩大類,一類是路徑中含有包名的,一類是路徑中不含有包名的,含有包名的路徑,因為和某個App有關,是以對這些檔案夾的通路都是調用Context裡邊的方法,而不含有包名的路徑,和某一個App無關,我們可以通過Environment中的方法來通路。如下圖:

20189200餘超 2018-2019-2 移動平台應用開發實踐第十周作業

操作資料庫

public class MainActivity extends AppCompatActivity {

    private MyDatabaseHelper dbHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 3); // 執行這句并不會建立資料庫檔案
        Button btnCreateDatabase = (Button) findViewById(R.id.button);
        btnCreateDatabase.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dbHelper.getWritableDatabase(); // 執行這句才會建立資料庫檔案
            }
        });

    }
}
           
public class MyDatabaseHelper extends SQLiteOpenHelper {

    public static final String CREATE_BOOK = "create table book (" +
            "id integer primary key autoincrement, " +
            "author text, " +
            "price real," +
            "pages integer, " +
            "name text)";

    private Context mContext;

    public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);

        mContext = context;
    }

    /**
     * 資料庫已經建立過了, 則不會執行到,如果不存在資料庫則會執行
     * @param db
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK); // 執行這句才會建立表

        Toast.makeText(mContext, "create succeeded", Toast.LENGTH_SHORT).show();

    }

    /**
     * 建立資料庫時不會執行,增大版本号更新時才會執行到
     * @param db
     * @param oldVersion
     * @param newVersion
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // 在這裡面可以把舊的表 drop掉 從新建立新表,
        // 但如果資料比較重要更好的做法還是把舊表的資料遷移到新表上,比如更新qq聊天記錄被删掉肯定招罵
        Toast.makeText(mContext, "onUpgrade oldVersion:" + oldVersion + " newVersion:" + newVersion, Toast.LENGTH_SHORT).show();
    }
}
           

驗證資料庫檔案是否存在的方法看最後部分

剩下的工作就是對資料庫表的增删改查了

首先通過下面的代碼獲得一個引用以便操作資料庫

1 SQLiteDatabase db = dbHelper.getWritableDatabase();

對于增删改都可以用 db.execSQL(String sql); 來執行sql語句。 例如增加一條記錄

1 db.execSQL("insert into book(name , author, pages, price) values("Android資料庫操作指南", "panda fang", 200, 35.5)");

遇到字元串要轉義 有沒有覺得很蛋疼, 用下面的方法就好多了

1 db.execSQL("insert into book(name , author, pages, price) values(?, ? ,? ,? )", new String[]{"Android資料庫操作指南", "panda fang", "200", "35.5"});

sql 中用 ? 占位 後面傳入真正的參數, 由于在建立表的時候已經約定pages 和 price字段的資料類型為integer和real, 雖然代碼中寫的是字元串并不影響,存入資料庫會自動處理的。數組嘛,必須與其他的元素類型一緻。 這第二個方式是 execSQL(String sql)的重載方法 api是 public void execSQL(String sql, Object[] bindArgs) throws SQLException

對于查詢則要使用 rawQuery(String sql, String[] selectionArgs) , 因為 execSQL傳回void ,而查詢需要通路查詢結果。方法如下:

Cursor cursor =  db.rawQuery("select * from book", null);
while (cursor.moveToNext())
{
    String name = cursor.getString(cursor.getColumnIndex("name"));
    String author =  cursor.getString(cursor.getColumnIndex("author"));
    Log.i(TAG, "name:" + name + " author:" + author);
}
cursor.close();
           

如何檢查資料庫檔案是否存在,以及檢查表中的資料呢。

前提是使用模拟器或者root過的真機。從 android studio 菜單中 Tools -> Android -> Android Device Monitor -> File Explorer 找到 data/data/程式包名/databases 目錄

檢視是否存在資料庫檔案。如果存在可以導出到電腦上, 用 以下工具檢視資料庫中的表

擷取圖檔

import java.io.FileNotFoundException;

import android.content.ContentResolver;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;

import com.maikefengchao.daixu.R;

public class WriteArticle_CompeterelayActivity extends BaseActivity {
    private ImageView im_upload_img;

    @Override
    public void initView(Bundle savedInstanceState){
        setContentView(R.layout.view_write_competerelay);

        im_upload_img = (ImageView)findViewById(R.id.write_competerelay_cover_iv);
    }

    @Override
    protected void setListener() {
        im_upload_img.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                /* 開啟Pictures畫面Type設定為image */
                intent.setType("image/*");
                /* 使用Intent.ACTION_GET_CONTENT這個Action */
                intent.setAction(Intent.ACTION_GET_CONTENT);
                /* 取得相片後傳回本畫面 */
                startActivityForResult(intent, 1);
            }
        });
    }

    @Override
    protected void processLogic(Bundle saveInstanceState) {

    }


    //擷取本地圖檔
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK) {
            Uri uri = data.getData();
            String img_url = uri.getPath();//這是本機的圖檔路徑
            ContentResolver cr = this.getContentResolver();
            try {
                Bitmap bitmap = BitmapFactory.decodeStream(cr.openInputStream(uri));
                ImageView imageView = (ImageView) findViewById(R.id.write_competerelay_cover_iv);
                /* 将Bitmap設定到ImageView */
                imageView.setImageBitmap(bitmap);
            } catch (FileNotFoundException e) {
                Log.e("Exception", e.getMessage(),e);
            }
        }
        super.onActivityResult(requestCode, resultCode, data);
    }
}
           

statistics.sh

20189200餘超 2018-2019-2 移動平台應用開發實踐第十周作業