/*****************2016年5月4日 更新*******************************/
知乎:Android 沒有沙盒保護機制嗎,WhatsApp 信息為何可被隨意訪問?
pansz:
Android 對每個應用程序定義了私有的存儲區域,這個區域通過 Linux 的文件系統權限控制,僅僅應用自己可以隨意讀寫,問其他應用無法訪問不屬於自己的私有數據。私有目錄的路徑可以通過 Context->getFilesDir() 來獲取。
除了私有存儲區域以外,SD 卡上都是公共區域,所有人可讀寫。
一個 app 選擇將隱私數據保存在公共區域,那是 App 選擇取向問題。與系統其實沒有什么關系。當然可以問 android 為什么要允許讀寫 SD 卡上任意目錄,個人覺得這是歷史問題,如果現在禁止了,估計一大堆讀寫 SD 卡的應用程序會出現兼容性問題,為了保證這種兼容性,感覺 android 不會將讀寫 SD 卡這種功能禁止掉。
Kifile:
我覺得更應該是由於儲存空間的關系。 在以前,不是任何一台設備都擁有幾個g的系統儲存空間,他們很多都只有100~200m的位置來存放app文件。 android中私有文件放在/data/data/$pakage 中,但是/data屬於系統目錄,如果把文件儲存在里面,那勢必會減少存放app文件的空間,這是得不償失的。所以很多與系統文件無關的資源文件就只能放在sdcard中。 由於這種歷史原因,雖然很多最新版的設備已經大幅提升其系統儲存空間,但開發者們仍舊會將自身的資源文件放到sdcard中。 並且在最新的android4.4中,對於android程序的資源文件建議儲存在/sdcard/Android/$package 中,我覺得這是一個很好的進步,規范了文件的儲存位置,離它的訪問權限管理還會遠嗎?
知乎:為什么 Android 4.4 KitKat 限制第三方應用的 SD 卡讀寫權限?
pansz:
就目前而言,第二 SD 卡仍然是可以讀寫的,只是要讀寫到指定的目錄(具體應該在 /Android/data/)。這樣的規定意味着應用程序只能對 SD 卡的指定目錄進行讀寫,不能讀寫任意目錄。相當於 Google 出手對 SD 卡目錄結構進行了規范。之前 android 不限制目錄,所以各種應用就隨意的在 SD 卡上建一個目錄。然后 SD 卡上的目錄到處都是,用戶對這種現象早就深惡痛絕了!如果 Google 對這件事情下狠手,只能說是大快人心。
另外說一下,SD 卡上的指定目錄是這樣獲取的:
1,程序相關的 內置存儲目錄,這個目錄位於內置 flash,應用程序可以隨意讀寫:
getFilesDir();
2,程序相關的 SD 卡外部存儲目錄,這個目錄位於 SD 卡,應用程序可以隨意讀寫:
getExternalFilesDir(null);
3,SD 卡公共目錄,這些目錄仍然可以訪問,不受權限限制:
Environment.getExternalStoragePublicDirectory(x)
其中 x 可以是 Environment.DIRECTORY_ALARMS 等預定義的常量。可以查找 Environment 的幫助。
如果大家要存儲數據,可以用 1 或者 2 的方法,獲取正確的目錄,然后進行任意讀寫,這樣不會把 SD 卡的目錄寫亂。
/*****************************************************************************/
1. 界面的准備工作,普通登錄界面,采用線性布局和相對布局。
<Checkbox/>有個屬性 android:checked=”true”,默認選中狀態,相對布局里面<Button/>位於右邊android:layout_alignParentRight=”true”,位於父控件的右面。密碼框星號顯示android:inputType=”textPassword”
2. 遇到device not found等錯誤可以直接忽略掉,布局文件屬性里面綁定點擊方法,傳入的參數View對象代表當前按鈕,控件首先都聲明在Activity的成員屬性里面,在onCreate()方法里面初始化,初始化控件一定要在setContentView()方法加載完界面之后才行。
3. 復選框判斷是否選中使用CheckBox對象的isChecked()方法,判斷字符串是否相等用String對象的equals()方法,logcat如果無法打印日志,關閉logcat重開或者關閉eclipse
4. 保存文件javaSE里面是直接new File(“aaa.txt”),文件默認保存在工程的目錄下面,但是在android系統里面,這樣默認是創建在/data/app 目錄下面,這里是不允許創建文件的。Android下每一個應用都有自己的數據文件夾/data/data/包名/。
5. 新建一個業務類來處理保存信息的操作。這里的寫法和javaSE一樣,new File(“/data/data/包名/文件名”)對象,new FileOutputStream() 對象,此時會有異常拋出,因為我們這個方法有返回boolean值,所以我們捕獲掉,如果是無返回值那就throws Exception拋出去。字符串信息getBytes()轉成字節數組,調用fos的write()方法,關閉fos。當這個方法沒有使用類的成員屬性的時候,谷歌推薦把這個方法定義成static靜態的,效率更高
6. 文件路徑部分,如果按照上面所寫,靈活性很差。當我改變包名的時候,程序會報錯,R文件要從新導一下,並且android會認為是個新的應用。谷歌提供了一個api來獲取應用的數據目錄,調用Context上下文對象的getFilesDir()方法,返回的是/data/data/包名/files/。因此可以這樣new File(context.getFilesDir(),"info1.txt");來寫。
7. 數據目錄還有個文件夾是cache目錄,調用Context對象的getCacheDir()來獲取,這個目錄可以通過設置里面清除緩存清掉,這個目錄不能存放過大的文件
8. 上下文就是一個類提供了方便的api可以得到應有程序的環境,可以獲取包名,文件路徑,資源路徑,資產路徑等
9. 讀取保存文件的信息,同樣new File() ,new FileInputStream() ,
activity代碼:
package com.tsh.savefile; import java.util.Map; import org.w3c.dom.Text; import com.tsh.savefile.service.LoginService; import android.app.Activity; import android.os.Bundle; import android.text.TextUtils; import android.view.View; import android.widget.CheckBox; import android.widget.EditText; import android.widget.Toast; public class MainActivity extends Activity { private EditText et_username; private EditText et_password; private CheckBox cb_rember; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); et_username=(EditText) findViewById(R.id.et_username); et_password=(EditText) findViewById(R.id.et_password); cb_rember=(CheckBox) findViewById(R.id.cb_rember); //讀取 Map<String, String> info=LoginService.getSavedUserInfo(this); if(info != null){ et_username.setText(info.get("username")); et_password.setText(info.get("password")); } } /** * 登陸 * @param v */ public void login(View v){ String username=et_username.getText().toString().trim(); String password=et_password.getText().toString().trim(); if(TextUtils.isEmpty(username)||TextUtils.isEmpty(password)){ Toast.makeText(this, "用戶名和密碼不能為空", Toast.LENGTH_SHORT).show(); } //記住密碼 if(cb_rember.isChecked()){ Boolean res=LoginService.saveUserInfo(this,username, password); if(res){ Toast.makeText(this, "保存成功", Toast.LENGTH_SHORT).show(); }else{ Toast.makeText(this, "保存失敗", Toast.LENGTH_SHORT).show(); } } //驗證 if(username.equals("taoshihan")&&password.equals("1")){ Toast.makeText(this, "登陸成功", Toast.LENGTH_SHORT).show(); }else{ Toast.makeText(this, "用戶名或密碼錯誤", Toast.LENGTH_SHORT).show(); } } }
業務類代碼:
package com.tsh.savefile.service; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; import android.content.Context; public class LoginService { /** * 保存用戶名和方法的業務方法 * @param context 上下文 * @param username 用戶名 * @param password 方法 * @return */ public static boolean saveUserInfo(Context context,String username,String password){ File file=new File(context.getFilesDir(),"info1.txt"); try { FileOutputStream fos=new FileOutputStream(file); String info=username+"##"+password; fos.write(info.getBytes()); fos.close(); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 讀取 * @return */ public static Map<String,String> getSavedUserInfo(Context context){ File file=new File(context.getFilesDir(),"info1.txt"); try { FileInputStream fis=new FileInputStream(file); BufferedReader br=new BufferedReader(new InputStreamReader(fis)); String[] res=br.readLine().split("##"); Map<String, String> map=new HashMap<String,String>(); map.put("username", res[0]); map.put("password", res[1]); return map; } catch (Exception e) { e.printStackTrace(); return null; } } }
layout代碼:
<LinearLayout 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:orientation="vertical" tools:context="com.tsh.savefile.MainActivity" > <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="登錄名" /> <EditText android:id="@+id/et_username" android:layout_width="match_parent" android:layout_height="wrap_content" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="密碼" /> <EditText android:id="@+id/et_password" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="textPassword" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <CheckBox android:id="@+id/cb_rember" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:checked="true" android:text="記住密碼" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:onClick="login" android:text="登陸" /> </RelativeLayout> </LinearLayout>