국비교육(22-23)

97일차(2)/Android App(62) : Internal, External Storage 저장 (write)

서리/Seori 2023. 2. 25. 23:24

97일차(2)/Android App(62) : Internal, External Storage 저장 (write)

 

 

- 새 모듈 생성 step24fileio

- 안드로이드에서의 파일 입출력 관련 학습

 

 

- 안드로이드의 파일 시스템에 앱이 이렇게 설치되어 있다고 하면,

 앱마다 각각 내부 저장소(Internal Storage) 가 있다. 각각의 앱의 고유한 저장소이다.

- SharedPreference 는 어떤 xml문서를 만드는데, 이 xml문서가 만들어지는 공간이다.

 

 

- 이 내부 저장소 말고 다른 외부 저장소도 있다.

- External Storage(sdcard) 는 내부적으로 들어있다. 확장할 수도 있다.

- 각각의 내부저장소에서 외부저장소로 파일을 보내서 생성할 수도 있다.

 

- 이런 외부저장소는 저장소를 외부에 공개할 수도 있다(특정 권한을 걸어두는 것도 가능)

- 이 정보를 Content Provider 를 통해서 다른 앱에 서비스할 수도 있다.

- 안드로이드 앱에서 어떤 공간에 파일을 생성해서 저장할 일이 많다. 저장할 곳을 internal, external 중에 선택해서 생성하면 된다.

 

 

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:hint="문자열 입력..."
        android:id="@+id/inputMsg"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="파일에 저장"
        android:id="@+id/saveBtn"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="파일에 저장(External)"
        android:id="@+id/saveBtn2"/>
</LinearLayout>

 

MainActivity

package com.example.step24fileio;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.PrintWriter;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    EditText inputMsg;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //필요한 UI 의 참조값 얻어오기
        inputMsg=findViewById(R.id.inputMsg);
        Button saveBtn=findViewById(R.id.saveBtn);
        //버튼에 리스너 등록
        saveBtn.setOnClickListener(this);

        Button saveBtn2=findViewById(R.id.saveBtn2);
        saveBtn2.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.saveBtn:
                saveToInternal();
                //saveToInternal2();
                break;
            case R.id.saveBtn2:

                saveToExternal();
                break;
        }
        new AlertDialog.Builder(this)
                .setTitle("알림")
                .setMessage("저장했습니다.")
                .setNeutralButton("확인", null)
                .create()
                .show();
    }


    //외부 저장 장치에 저장하기
    public void saveToExternal(){
        //입력한 문자열을 읽어온다.
        String msg=inputMsg.getText().toString();
        //외부 저장 장치의 폴더를 가리키는 File 객체
        File externalDir=getExternalFilesDir(null);
        //해당 폴더의 절대경로를 얻어낸다.
        String absolutePath=externalDir.getAbsolutePath();
        Log.d("absolutePath", absolutePath);
        //텍스트 파일을 만들기 위한 파일 객체 생성
        File file=new File(absolutePath+"/memo.txt");
        try{
            FileWriter fw=new FileWriter(file, true);
            fw.append(msg);
            fw.flush();
            fw.close();
        }catch (Exception e){
            Log.e("saveToExternal()", e.getMessage());
        }
    }

    //내부 저장 장치에 저장하기
    public void saveToInternal(){
        //입력한 문자열을 읽어온다.
        String msg=inputMsg.getText().toString();
        try {
            //파일을 저장하기 위한 디렉토리 만들기
            File dir=new File(getFilesDir(), "myDir");
            if(!dir.exists()){
                dir.mkdir();
            }
            //해당 디렉토리에 파일을 만들기 위한 File 객체
            File file=new File(dir, "memo.txt");
            FileWriter fw=new FileWriter(file, true);
            fw.append(msg+"\n");
            fw.flush();
            fw.close();
        }catch(Exception e){
            Log.e("saveToInternal()", e.getMessage());
        }
    }
    //내부 저장 장치에 저장하기
    public void saveToInternal2(){
        //입력한 문자열을 읽어온다.
        String msg=inputMsg.getText().toString();
        try {
            FileOutputStream fos=openFileOutput("memo2.txt", MODE_APPEND);
            PrintWriter pw=new PrintWriter(fos);
            pw.println(msg+"\n");
            pw.flush();
            pw.close();
        }catch(Exception e){
            Log.e("saveToInternal()", e.getMessage());
        }
    }
}

 

- 버튼 onClickListener 를 Activity에 구현하고 메소드 오버라이드

 

**저장장치와 관련된 안드로이드 공식문서 : 링크

- 앱별 저장소는 내부 저장소를, 공유 저장소는 sdcard(외부 저장소)를 말한다.

 

- sdcard 에 파일을 저장하려면 어떻게 해야하는지

 

- context는 액티비티or서비스를 가리킨다.

 

 

- openFileInput,Output 메소드로 FileInputStream/FileOutputStream 타입 객체를 얻어낼 수 있다.

 

 

- 부모로부터 물려받은 상수값(부모의 필드값) 을 사용하면 된다.

 

 

- FileNotFoundException이 발생하므로 try~catch문으로 묶어준다.

 

- inputMsg 에 들어오는 값을 읽어와서 파일에 write 한다.

 

- FileOutStream에는 write 기능이 없기 때문에

  File 객체를 생성하고, 이 File 타입을 받는 FileWriter 객체의 append() 메소드를 사용하기

- append() : 문자열을 쭉 붙여 나열해주는 메소드

 

- device file explorer 안의 data-data 폴더 안으로 들어가면 모듈명으로 생성된 폴더가 있다.

 

 

- 모듈명 폴더-files 안에 생성된 폴더와 파일! 여기가 앱의 고유한 저장장치, internal storage이다.

- 앱의 고유한 공간 안에 myDir 이라는 폴더가 만들어졌다. 내부 저장장치에 폴더,파일을 만들어 기록한 것이다.

 

 

- 내가 입력한 메세지를 memo.txt 안에서 이렇게 확인해볼 수 있다.

 

 

- getFilesDir() 로 디렉토리 생성 가능

- 만들어둔 myDir 폴더(디렉토리)안에 memo.txt라는 파일객체를 만들고

 안에 텍스트를 넣어주었다.

 

- 이런 내부저장소에 저장하는 작업은 가상기기에서만 확인할 수 있고, 실 기기에서는 확인할 수 없다.(보안 문제)

 

 

- 폴더와 파일을 생성하고, FileWriter를 사용해서 기록해주었다.

 

FileWriter fw=new FileWriter(file, true);
fw.append(msg+"\n");
fw.flush();
fw.close();

 

- 저장시 개행기호를 추가해주면 msg와 개행기호가 함께 전달되어 파일에 append된다.

 


 

 

- 복사해서 saveToInternal2 메소드 추가. 다른 방법으로 파일 저장 테스트하기!

- PrintWriter는 생성자의 인자로 OutputStream을 받아준다. println() 기능을 사용해서 저장해주기

 

 

- 열어놓았던 device file explorer를 최신 버전으로 보기 위해서는

 폴더 우클릭- Synchronize 해주면 된다.

 

 

- openFileOutput() 메소드를 사용하면 파일이 하위 폴더 없이도 바로 만들어진다.

- 이 메소드를 통해서 얻어내는 FileOutputStream은 그 Files 폴더 안쪽에 뭔가를 만들어낼 수 있는 OutputStream이다.

- 그러나 이 FileOutputStream을 통해서 문자열을 만들기는 불편하므로, PrintWriter 객체를 사용해서 문자열을 저장한 것.

 

 

- MODE_PRIVATE 으로 하면 기존의 내용은 지워지고 새로 입력한 내용만 들어가있으며,

  MODE_APPEND 은 위와 같이 입력한 내용이 누적된다.

 


 

- 레이아웃에서 saveBtn2 버튼을 추가

 

- manifest에 permission 필요하고, permission 켜기 등 작업이 필요

- 내부에는 저장이 많이 안된다. 외부에 저장할 일이 많다.

 

 

- storage-emulated

- 이 폴더가 기본 외부 저장장치의 경로이다.

 

- 이전에는 AndroidManifest.xml에 아래 permission 표기가 필요했지만, 버전업 되면서 permission이 필요없게 되었다.

(AndroidManifest.xml 을 따로 수정하지 않아도 된다)

 

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

 

- WRITE를 하면 READ는 자동으로 승인된다.

 

 

- permission을 명시해야 한다. 사용자가 직접 승인을 켜야한다는 코드!

 

 

- 권한이 없으면 여기서 리턴하고, 권한이 있으면 아래 메소드를 수행하도록 구조를 짠다.

 

- 어떤 권한 요청을 하게되면 결과를 받을 메소드가 필요하다.

- onRequestPermissionsResult 메소드 오버라이드

 

 

- 코드가 0번인 경우 이 결과 메소드 안쪽 코드가 실행되도록 분기하기.

 

 

- 외부 저장장치의 폴더를 가리키는 메소드. 인자는 그냥 null을 넣어주면 된다.

 

//외부 저장장치에 저장하기
public void saveToExternal(){
    //입력한 문자열을 읽어온다.
    String msg=inputMsg.getText().toString();
    //외부 저장장치의 폴더를 가리키는 File 객체
    File externalDir=getExternalFilesDir(null);
    //해당 폴더의 절대경로를 얻어낸다.
    String absolutePath=externalDir.getAbsolutePath();
    //텍스트 파일을 만들기 위한 File 객체 생성
    File file=new File(absolutePath+"/memo.txt");
    try {
        FileWriter fw=new FileWriter(file, true);
        fw.append(msg);
        fw.flush();
        fw.close();
    } catch (IOException e) {
        Log.e("saveToExternal", e.getMessage());
    }
}

 

- 외부 저장장치에 파일을 저장하기 위한 코드!

 

File externalDir=getExternalFilesDir(null);
String absolutePath=externalDir.getAbsolutePath();

 

- File 객체를 사용해 외부 저장장치 폴더를 찾아 해당 폴더의 절대경로를 요청한 것

 

 

- 로그를 찍어보면 내부 저장소에 저장할 때와 경로가 다른 것을 확인할 수 있다.

 

 

- emulated 폴더 안의 0-Android-data-(패키지명 폴더)-files 안에 파일이 생성되었다.

 

 

- 해당 위치를 찾아 들어가보면 저장한 내용이 들어가있는 것을 확인할 수 있다.

 


 

* 앱을 제거(uninstall) 하면 이 저장된 파일, 폴더는 어떻게 될까?

 

- 앱을 삭제하면 외부 저장장치이지만 같이 삭제된다.

 

- 외부 저장장치가 내부 저장장치보다 사용 용량은 크지만,

  다른 앱에서 이 폴더에 접근할 수는 없다. 보안이 유지된다.

 

 

- 내부저장장치를 보아도 data-data 안에 할당받은 공간이 같이 삭제되었다고 나온다.

 


 

- 버튼 클릭시 저장되었다는 알림이 뜨도록 하기

 

 

- onClick 안에 저장되었다는 알림창 추가!

 

 

- 버튼 클릭시 저장되었다는 알림이 출력된다.

 

 

- 이처럼 앱에서 Android File System 안에 파일을 만들 수 있다면,

서버에서 어떤 정보를 내려받아서 저장해두면 인터넷이 되지 않는 상황에서도 파일을 사용할 수 있다.