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;
}
}
我們看一下效果.

點選儲存參數,出現儲存完成則說明我們已經儲存成功了,在下次登入的時候可以看到這些參數還在。因為記錄檔案是在内置空間中的,是以我們在SD卡中找不到該檔案,
如果有root權限的手機可以下載下傳個RE檔案管理,我們可以再/data/data/的路徑找到很多應用程式的内置檔案夾,我們可以在這些檔案夾中看到一個shared_prefs檔案夾,
裡面就有我們剛剛設定而産生的xml檔案。
操作空間
我們先來考慮這樣一個問題:
打開手機設定,選擇應用管理,選擇任意一個App,然後你會看到兩個按鈕,一個是清除緩存,另一個是清除資料,那麼當我們點選清除緩存的時候清除的是哪裡的資料?當我們點選清除資料的時候又是清除的哪裡的資料?讀完本文相信你會有答案。
在android開發中我們常常聽到這樣幾個概念,記憶體,内部存儲,外部存儲,很多人常常将這三個東西搞混,那麼我們今天就先來詳細說說這三個東西是怎麼回事?
記憶體,我們在英文中稱作memory,内部存儲,我們稱為InternalStorage,外部存儲我們稱為ExternalStorage,這在英文中本不會産生歧義,但是當我們翻譯為中文之後,前兩個都簡稱為記憶體,于是,混了。
那麼究竟什麼是内部存儲什麼是外部存儲呢?
首先我們打開DDMS,有一個File Explorer,如下:
徹底了解android中的内部存儲與外部存儲0
這裡有三個檔案夾需要我們重視,一個是data,一個是mnt,一個是storage,我們下面就詳細說說這三個檔案夾。
1.内部存儲
data檔案夾就是我們常說的内部存儲,當我們打開data檔案夾之後(沒有root的手機不能打開該檔案夾),裡邊有兩個檔案夾值得我們關注,如下:
一個檔案夾是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目錄下建立了一個應用的檔案夾,那麼當你删除應用的時候,這個檔案夾就不會被删除。
經過以上的介紹,我們可以總結出下面一個表格:
一目了然,什麼是内部存儲,什麼是外部存儲。
如果按照路徑的特征,我們又可以将檔案存儲的路徑分為兩大類,一類是路徑中含有包名的,一類是路徑中不含有包名的,含有包名的路徑,因為和某個App有關,是以對這些檔案夾的通路都是調用Context裡邊的方法,而不含有包名的路徑,和某一個App無關,我們可以通過Environment中的方法來通路。如下圖:
操作資料庫
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