기록장
[안드로이드] 내부(Internal), 외부(External) 저장소, File read/write 본문
안드로이드 기기의 파일 저장 공간은 내부(Internal) 저장소와 외부(External) 저장소로 나뉜다.
내부 저장소는 안드로이드 시스템 도구, 앱 데이터 등을 위한 공간으로 안드로이드의 보안 모델에 따라 보호를 받는 공간이다. 그래서 저장소 내부는 관련된 하나의 앱만 접근이 가능하고 파일을 읽고 쓰기가 가능하다. 당연히 앱을 지우게 될 경우 내부 저장소 데이터들도 함께 지워진다.
외부 저장소는 기기에 따라 SD카드, 기기 자체 저장소로 제공한다. 그래서 모든 기기에서 외부 저장소를 사용할 수 있는 것은 아니다. 이 공간을 사용할 시 다른 앱에서도 이 앱에서 만든 외부 저장소에 접근할 수 있고, 사용자가 기기 폴더에 직접 접근도 가능하다. 그리고 앱 삭제 시 앱과 함께 외부 저장소 파일은 지워지지 않는다. 쉽게 생각하면 도서관에 내 책을 기부했다고 가정했을 때 그 책을 누구나 볼 수 있고 내가 도서관을 가지 않아도 책은 그대로 남아있다.
파일을 읽고 쓰는데 있어서 필요한 메소드이다.
- File: 파일 및 디렉터리를 지칭하는 클래스
- FileInputStream: 파일에서 바이트 데이터를 읽기 위한 함수 제공
- FileOutputStream: 파일에 바이트 데이터를 쓰기 위한 함수 제공
- FileReader: 파일에서 문자열 데이터를 읽기 위한 함수 제공
- FileWriter: 파일에 문자열 데이터를 쓰기 위한 함수 제공
저장소와 관련된 정보는 Environment 클래스로 얻을 수 있다.
- Environment.getExternalStorageState(): 외부 저장 공간 상태
- Environment.getExternalStorageDirectory().getAbsolutePath(): 외부 저장 공간 경로 (애뮬레이터 기준 /sdcard or /storage/emulator/0)
- Environment.getDataDirectory().getAbsolutePath(): 내부 저장 공간 경로 (애뮬레이터 기준 /data/data/패키지명/files)
* 메모리 저장 경로는 기기마다 다를 수 있다.
- Environment.getExternalStoragePublicDirectory(): 기기에 설정된 공용 폴더의 경로를 전달해줌
그 외 (공용 폴더)
- Environment.DIRECTORY_ALARMS: 알람으로 사용할 오디오 파일 저장 폴더
- Environment.DIRECTORY_DCIM: 카메라로 촬영한 사진 저장 폴더
- Environment.DIRECTORY_DOWNLOADS: 다운로드한 파일 저장 폴더
- Environment.DIRECTORY_MUSIC: 음악 파일 저장 폴더
- Environment.DIRECTORY_MOVIES: 영상 파일 저장 폴더
- Environment.DIRECTORY_NOTIFICATIONS: 알림음으로 사용할 오디오 파일 저장 폴더
- Environment.DIRECTORY_PICTURES: 이미지 파일 저장 폴더
내부 저장소에 접근하기 위해서는 특별한 권한이 필요하지 않다. 그러나 외부 저장소는 다른 앱의 외부 저장소도 건드릴 수 있으므로 위험 권한으로 분류되어 사용자에게 권한 허가를 받아야 한다.
먼저 퍼미션을 추가해야한다.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
외부 저장소에 파일을 읽고 쓰겠다는걸 선언해주는 것이다. 그러나 앞서 언급 한대로 위험 권한이므로 사용자에게 권한 허가를 받아야 한다.
API 29 이상부터는 Manifest.xml의 application 내에 추가
android:requestLegacyExternalStorage="true"
checkPermissions 메소드를 호추라여 권한 여부를 알아보고 권한 허가를 받기 위한 창을 띄운다.
public void checkPermissions(String[] permissions){
ArrayList<String> targetList = new ArrayList<>();
for(int i=0;i<permissions.length;i++){
String curPermission = permissions[i]; //확인할 권한을 curPermission에 넣고
int permissionCheck = ContextCompat.checkSelfPermission(this, curPermission); //승인 여부 확인하여 int형 반환
if(permissionCheck == PackageManager.PERMISSION_GRANTED){
Log.i("태그", curPermission+"대한 권한 있음");
}
else{
Log.i("태그", curPermission+"대한 권한 없음");
targetList.add(curPermission);
}
}
String[] targets = new String[targetList.size()];
targetList.toArray(targets);
if(targets.length > 0) {
ActivityCompat.requestPermissions(this, targets, 1);
}
}
잘 이해가 가지 않는다면 참고
[안드로이드] 일반 권한, 위험 권한
권한은 앱 내에서 단말에 대한 기능들을 사용할 수 있도록 허가를 해주는 것으로 일반 권한과 위험 권한으로 나뉜다. 이름에서와 같이 일반보다 위험이 더 뭔가 중요한 정보를 다루는 느낌이 든
edit0.tistory.com
외부저장소 전체 코드
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/button1"
android:text="폴더 만들기"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/edittext1"
android:hint="제목"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/edittext2"
android:hint="저장할 내용 입력"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/button2"
android:text="파일 저장"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/button3"
android:text="파일 불러오기"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="파일 리스트 불러오기"
android:id="@+id/button4"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textview1"
android:textSize="20dp"
android:background="#FFEB3B"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textview2"
android:textSize="20dp"
android:background="#CDDC39"/>
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
String[] permissions = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}; //퍼미션 받을 목록
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/my_folder"; //경로 지정
File file;
Button b1, b2, b3, b4;
EditText et1, et2;
TextView tv1, tv2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkPermissions(permissions); //권한 확인 및 허가 받기
b1 = findViewById(R.id.button1);
b2 = findViewById(R.id.button2);
b3 = findViewById(R.id.button3);
b4 = findViewById(R.id.button4);
et1 = findViewById(R.id.edittext1);
et2 = findViewById(R.id.edittext2);
tv1 = findViewById(R.id.textview1);
tv2 = findViewById(R.id.textview2);
//폴더 만들기
b1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
File mk_folder = new File(path);
if (!mk_folder.exists()) { //같은 파일이 없다면
mk_folder.mkdir(); //파일 생성
tv1.append("폴더 생성됨 \n");
}
}
});
//파일 만들고 입력된 내용 저장하기
b2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try{
file = new File(path + "/" + et1.getText().toString());
FileOutputStream fileOutputStream= new FileOutputStream(file, true);
if(file.length() != 0){ //파일이 있다면 제거하고 다시 만들기
file.delete();
fileOutputStream= new FileOutputStream(file, true);
}
byte[] text = et2.getText().toString().getBytes(); //바이트 데이터로
fileOutputStream.write(text); //쓰기
fileOutputStream.close();
tv1.append("파일 생성됨 \n");
}catch (Exception e){}
}
});
//저장한 파일 내용 읽어오기
b3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
FileInputStream fileInputStream = new FileInputStream(file);
byte[] text = new byte[fileInputStream.available()]; //바이트로 받기
fileInputStream.read(text);
tv1.append(new String(text) + "\n");
fileInputStream.close();
} catch (Exception e) { }
}
});
//폴더 내에 파일 리스트 보기
b4.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
File[] filelist = new File(path).listFiles();
for(int i=0;i<filelist.length;i++){
if(filelist[i].isFile() == true){
tv2.append("파일 - "+ filelist[i] + "\n");
}
else if(filelist[i].isDirectory() == true){
tv2.append("폴더 - " + filelist[i] + "\n");
}
}
}
});
}
public void checkPermissions(String[] permissions){
ArrayList<String> targetList = new ArrayList<>();
for(int i=0;i<permissions.length;i++){
String curPermission = permissions[i]; //확인할 권한을 curPermission에 넣고
int permissionCheck = ContextCompat.checkSelfPermission(this, curPermission); //승인 여부 확인하여 int형 반환
if(permissionCheck == PackageManager.PERMISSION_GRANTED){
Log.i("태그", curPermission+"대한 권한 있음");
}
else{
Log.i("태그", curPermission+"대한 권한 없음");
targetList.add(curPermission);
}
}
String[] targets = new String[targetList.size()];
targetList.toArray(targets);
if(targets.length > 0) {
ActivityCompat.requestPermissions(this, targets, 1);
}
}
}
결과
Device File Explorer 에서 확인할 수 있다.
다음은 내부 저장소 코드
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/edittext1"
android:hint="제목"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/edittext2"
android:hint="저장할 내용 입력"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/button1"
android:text="파일 저장"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/button2"
android:text="파일 불러오기"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textview1"
android:textSize="20dp"
android:background="#FFEB3B"/>
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
Button b1, b2;
EditText et1, et2;
TextView tv1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
b1 = findViewById(R.id.button1);
b2 = findViewById(R.id.button2);
et1 = findViewById(R.id.edittext1);
et2 = findViewById(R.id.edittext2);
tv1 = findViewById(R.id.textview1);
//파일 쓰기
b1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try{
FileOutputStream fileOutputStream = openFileOutput(et1.getText().toString(), MODE_PRIVATE);
String str = et2.getText().toString();
byte[] text = str.getBytes();
fileOutputStream.write(text);
fileOutputStream.close();
tv1.append("파일 생성됨" + "\n");
}catch (Exception e){}
}
});
//파일 읽기
b2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
FileInputStream fileInputStream = openFileInput(et1.getText().toString());
byte[] text = new byte[fileInputStream.available()];
fileInputStream.read(text);
tv1.append(new String(text) + "\n");
fileInputStream.close();
} catch (Exception e) { }
}
});
}
}
결과
/data/data/패키지명/files 내부로 들어가서 확인할 수 있다.
감사합니다.
'안드로이드' 카테고리의 다른 글
[안드로이드] ActionBar(액션바) 알아보기 (0) | 2021.01.21 |
---|---|
[안드로이드] 리소스(Resource) (0) | 2021.01.20 |
[안드로이드] Retrofit2(레트로핏2) 통신 / 오픈 API, 데이터베이스와 데이터 주고 받기 (0) | 2021.01.19 |
[안드로이드] 소리(시스템 효과음), 진동 울리기 (0) | 2021.01.18 |
[안드로이드] GridLayout(그리드 레이아웃) (0) | 2021.01.18 |