Chapter 08 파일처리

Ruinak·2021년 8월 25일
0

Android

목록 보기
9/15
post-thumbnail

1. 파일 처리의 기본

  • 안드로이드에서 파일을 읽고 쓰는 방법에는 Java에서 제공하는 파일 관련 클래스를 사용하는 방법과 안드로이드에서 제공하는 파일 관련 클래스를 사용하는 방법이 있습니다.
  • 아무 곳에서나 파일을 읽고 쓸 수는 없고 제한된 폴더나 SD 카드 등에서만 가능합니다.
  • PC용 운영체제에서 파일을 처리하는 것과 다르게 느껴질 수 있으나 모바일 기기의 특성을 고려한 적절한 방식이므로 보안에 도움이 됩니다.

1-1 내장 메모리 파일 처리

  • 앱을 종료했다가 다음에 다시 실행할 때 사용했던 곳부터 이어서 작업하고 싶은 경우가 있습니다.
  • 이럴 때 내장 메모리에 파일을 저장하고 읽어오는 방식을 활용합니다.
  • 내장 메모리의 저장 위치는 /data/data/패키지명/files 폴더입니다.
  • 일반적으로 응용 프로그램마다 다른 패키지명을 사용하므로 응용 프로그램별로 고유의 저장 공간이 있다고 생각하면 됩니다.
  • 파일을 읽기 위해 먼저 안드로이드 Context 클래스의 openFileInput( ) 메서드를 사용하는데, 이 메서드는 FileInputStream을 반환합니다.
  • 파일을 쓰기 위해 openFileOutput( ) 메서드를 사용하면 FileOutputStream을 반환합니다.
  • Java에서 제공하는 파일을 읽거나 쓰는 java.io.FileInputStream 클래스와 java.io.FileOutputStream의 read( ), write( ) 메서드를 사용하여 파일을 처리합니다.
  • 아래는 내장 메모리에서 파일을 읽거나 쓰는 일반적인 절차를 간단하게 정리했습니다.

예제 8-1 파일 처리의 기본 activity_main.xml

<androidx.appcompat.widget.LinearLayoutCompat
   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"
   android:layout_margin="20dp"
   tools:context=".MainActivity">

   <androidx.appcompat.widget.AppCompatButton
       android:id="@+id/btnWrite"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="내장 메모리에 파일 쓰기" />
   <androidx.appcompat.widget.AppCompatButton
       android:id="@+id/btnRead"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="내장 메모리에서 파일 읽기" />

</androidx.appcompat.widget.LinearLayoutCompat>

예제 8-2 파일 처리의 기본 Java 코드

public class MainActivity extends AppCompatActivity{

    private Button btnWrite, btnRead;

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

        init();
        initLr();
    }
    public void init(){
        btnWrite = findViewById(R.id.btnWrite);
        btnRead = findViewById(R.id.btnRead);
    }
    public void initLr(){
        btnWrite.setOnClickListener(v -> {
            try {
                FileOutputStream outFs = openFileOutput("file.txt", 
                					Context.MODE_PRIVATE);
                String str = "안드로이드";
                outFs.write(str.getBytes());
                outFs.close();
                Toast.makeText(getApplicationContext(), "file.txt가 생성됨",
                		Toast.LENGTH_SHORT).show();
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        btnRead.setOnClickListener(v -> {
            try {
                FileInputStream inFs = openFileInput("file.txt");
                byte[] txt = new byte[30];
                inFs.read(txt);
                String str = new String(txt);
                Toast.makeText(getApplicationContext(), str,
                		Toast.LENGTH_SHORT).show();
                inFs.close();
            } catch (IOException e) {
                Toast.makeText(getApplicationContext(), "파일 없음", 
                		Toast.LENGTH_SHORT).show();
            }
        });
    }
}

실습 8-1 간단 일기장 앱 만들기

예제 8-3 activity_main.xml 코드

<androidx.appcompat.widget.LinearLayoutCompat
    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"
    android:padding="10dp"
    tools:context=".MainActivity">

    <DatePicker
        android:id="@+id/datePicker"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:calendarViewShown="false"
        android:datePickerMode="spinner"/>
    <EditText
        android:id="@+id/etDiary"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_green_light"
        android:lines="8" />
    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/btnWrite"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="작성하기" />

</androidx.appcompat.widget.LinearLayoutCompat>

예제 8-4, 5, 6, 7 MainActivity.java 코드

public class MainActivity extends AppCompatActivity {

    private DatePicker datePicker;
    private EditText etDiary;
    private Button btnWrite;
    private String fileName;

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

        init();
        initLr();
    }
    public void init(){
        datePicker = findViewById(R.id.datePicker);
        etDiary = findViewById(R.id.etDiary);
        btnWrite = findViewById(R.id.btnWrite);

        Calendar cal = Calendar.getInstance();
        int cYear = cal.get(Calendar.YEAR);
        int cMonth = cal.get(Calendar.MONTH);
        int cDay = cal.get(Calendar.DAY_OF_MONTH);

        datePicker.init(cYear, cMonth, cDay, new DatePicker.OnDateChangedListener() {
            @Override
            public void onDateChanged(DatePicker datePicker, int year, 
            				int monthOfYear, int dayOfMonth) {
                fileName = Integer.toString(year) + "-" + Integer.toString(monthOfYear + 1)
                		+ "-" + Integer.toString(dayOfMonth) + ".txt";
                String str = readDiary(fileName);
                etDiary.setText(str);
                btnWrite.setEnabled(true);
            }
        });
    }

    public void initLr(){
        btnWrite.setOnClickListener(v -> {
            try {
                FileOutputStream outFs = openFileOutput(fileName, Context.MODE_PRIVATE);
                String str = etDiary.getText().toString();
                outFs.close();
                Toast.makeText(getApplicationContext(), fileName + "이 저장됨",
                		Toast.LENGTH_SHORT).show();
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }

    String readDiary(String fName) {
        String diaryStr = null;
        FileInputStream inFs;
        try {
            inFs = openFileInput(fName);
            byte[] txt = new byte[500];
            inFs.read(txt);
            inFs.close();
            diaryStr = (new String(txt)).trim();
            btnWrite.setText("수정하기");
        } catch (IOException e) {
            etDiary.setHint("일기 없음");
            btnWrite.setText("새로 저장");
        }
        return diaryStr;
    }
}

  • 실행은 되지만 에러가 발생해서 앱이 종료되거나, 일기 내용이 입력되지 않으므로 해결방안 찾기

1-2 raw 폴더 파일 처리

  • 프로제긑의 /res/raw 폴더에 필요한 파일을 저장하여 사용하는 방법도 있습니다.
  • 기본적으로 /res 아래에 raw 폴더를 생성하고 프로젝트에서 사용할 파일을 넣어두면 됩니다.
  • Java 코드에서 openRawResource( ) 메서드를 사용하여 접근할 수 있으며, FileInputStream 클래스 대신 InputStream 클래스를 사용합니다.
  • /res/raw는 프로젝트에 포함된 폴더이므로 읽기 전용으로만 사용 가능하고 쓰기는 할 수 없습니다.
  • 쓰기에는 내장 메모리나 SD 카드를 사용해야 합니다.

예제 8-8 /res/raw 폴더의 파일 읽기 activity_main.xml 코드

<androidx.appcompat.widget.LinearLayoutCompat
    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"
    android:padding="10dp"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/btnRead"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="/res/raw에서 파일 읽기" />
    <EditText
        android:id="@+id/etRaw"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:lines="10" />

</androidx.appcompat.widget.LinearLayoutCompat>

예제 8-9 /res/raw 폴더의 파일 읽기 Java 코드

public class MainActivity extends AppCompatActivity {

    private Button btnRead;
    private EditText etRaw;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setTitle("간단 일기장");

        init();
        initLr();
    }
    public void init(){
        btnRead = findViewById(R.id.btnRead);
        etRaw = findViewById(R.id.etRaw);
    }
    public void initLr(){
        btnRead.setOnClickListener(v -> {
            try {
                InputStream inputStream = getResources().openRawResource(R.raw.chapter8);
                byte[] txt = new byte[inputStream.available()];
                inputStream.read(txt);
                etRaw.setText(new String(txt));
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }
}

2. 파일 처리의 응용

  • 안드로이드 내부의 파일을 처리하는 것은 앞에서 했던 것과 같이 /data/data/패키지명/files 폴더를 사용합니다.
  • 읽기 전용 파일의 경우 /res/raw 폴더를 사용할 수 있습니다.
  • 음악, 영상, 그림 파일 등은 여러 응용 프로그램에서 사용되는 경우가 많습니다.
  • MP3 파일을 안드로이드에 내장된 앱에서 들을 수도 있고 별도의 음악 플레이어로 들을 수도 있습니다.
    - 이런 경우 SD 카드에 저장하여 활용해야 합니다.
  • 안드로이드에서는 SD 카드에 저장된 데이터에 특별한 인증 절차 없이 접근할 수 있습니다.
  • 제한된 공간의 내장 메모리보다 훨씬 큰 공간을 사용할 수 있으며 확장성도 뛰어납니다.
  • 실제 안드로이드 폰의 경우 주로 마이크로 SD 카드를 장착하여 사용합니다.
  • AVD에서 가상 SD 카드를 장착할 수 있는데, AVD를 생성할 때 512MB의 가상 SD 카드가 장착되었습니다.
  • 메뉴에서 [Tools] - [AVD Manger]를 선택하여 해당 AVD 이름의 오른쪽에 있는 Edit 아이콘을 클릭한 후 <Show Advanced Settings>를 클릭하면 사용하는 AVD에 장착된 SD 카드를 확인할 수 있습니다.
  • 만약 가상 SD 카드가 만들어져 있지 않으면 원하는 크기를 새로 입력하면 됩니다.

2-1 SD 카드에서 파일 읽기

  • 폴더 경로가 다른 것만 제외하면 SD 카드의 기본적인 경로는 내장 메모리에서 파일을 읽을 떄와 별 차이가 없습니다.
  • 먼저 [View] - [Tool Window] - [Device File Explorer]에서 /sdcard 폴더 또는 /storage/emulated/0 폴더에 적당한 텍스트 파일을 하나 넣습니다.(Upload)
  • AVD에서 SD 카드의 경로는 절대적인 것이 아니며 SDK 버전에 따라 달라질 수 있습니다.
  • AndroidManifest.xml 파일에 SD 카드를 사용할 수 있도록 퍼미션을 지정해야 합니다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.cos.프로젝트명">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
    <application
        android:requestLegacyExternalStorage="true"
        ...>
        
    	....
        
    </application>
</manifest>
  • 위와 같이 AndroidManifest.xml에 퍼미션 및 application 관련 속성을 추가합니다.
  • 마시멜로(Android 6.0. API 23)부터는 보안 모델이 강화되어 각 앱마다 파일, 사진, 미디어 등에 액세스할 수 있도록 코딩해야 합니다.
    - ActivityCompat.requestPermissions( ) 메서드를 사용하면 됩니다.
    - 위 메서드는 사용자가 거부 / 허용을 선택할 수 있는 대화상자가 나오게 하는데, <허용>을 클릭해야 파일 등에 접근이 가능합니다.

예제 8-10 SD 카드에서 파일 읽기 activity_main.xml

<androidx.appcompat.widget.LinearLayoutCompat
    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"
    android:padding="10dp"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/btnRead"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="SD 카드에서 파일 읽기" />
    <EditText
        android:id="@+id/etSD"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:lines="10" />

</androidx.appcompat.widget.LinearLayoutCompat>

예제 8-11 SD 카드에서 파일 읽기 Java 코드

public class MainActivity extends AppCompatActivity {

    private Button btnRead;
    private EditText etSD;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setTitle(null);

        init();
        initLr();
    }
    
    public void init(){
        btnRead = findViewById(R.id.btnRead);
        etSD = findViewById(R.id.etSD);

        ActivityCompat.requestPermissions(this, new String[] 
        	{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, MODE_PRIVATE);
    }
    
    public void initLr(){
        btnRead.setOnClickListener(v -> {
            try {
                FileInputStream inFs = new FileInputStream("/sdcard/test.txt");
                byte[] txt = new byte[inFs.available()];
                inFs.read(txt);
                etSD.setText(new String(txt));
                inFs.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }
}

실습 8-2 간단 이미지 뷰어 만들기

예제 8-16, 17 myPictureView 클래스의 Java 코드

public class myPictureView extends View {

    // myPictureView 에 보여줄 이미지 파일의 경로 및 파일 이름을 저장할 변수
    String imagePath = null;

    public myPictureView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // imagePath 에 값이 있으면 화면에 그림 파일을 출력함
        if(imagePath != null) {
            Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
            canvas.drawBitmap(bitmap, 0, 0, null);
            bitmap.recycle();
        }
    }
}

예제 8-18 activity_main.xml 코드

<androidx.appcompat.widget.LinearLayoutCompat
    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=".MainActivity">

    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/btnPrev"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="50"
            android:text="이전 그림" />
        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/btnNext"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="50"
            android:text="다음 그림" />
    </androidx.appcompat.widget.LinearLayoutCompat>

    <com.cos.project8_2.myPictureView
        android:id="@+id/myPictureView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</androidx.appcompat.widget.LinearLayoutCompat>

예제 8-19, 20, 21 MainActivity.java 코드

public class MainActivity extends AppCompatActivity {

    private Button btnPrev, btnNext;
    myPictureView myPictureView;
    int num = 1;
    File[] imageFiles;
    String imageFname;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setTitle("간단 이미지 뷰어");

        ActivityCompat.requestPermissions(this, new String[] 
        		{Manifest.permission.WRITE_EXTERNAL_STORAGE}, MODE_PRIVATE);
        btnPrev = findViewById(R.id.btnPrev);
        btnNext = findViewById(R.id.btnNext);
        myPictureView = findViewById(R.id.myPictureView);

        imageFiles = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
        			+ "/Pictures").listFiles();
        imageFname = imageFiles[1].toString();
        myPictureView.imagePath = imageFname;

        btnPrev.setOnClickListener(v -> {
            if(num <= 1){
                Toast.makeText(getApplicationContext(), "첫번째 그림입니다.", 
                		Toast.LENGTH_SHORT).show();
            } else {
                num--;
                imageFname = imageFiles[num].toString();
                myPictureView.imagePath = imageFname;
                myPictureView.invalidate();
            }
        });

        btnNext.setOnClickListener(v -> {
            if(num >= imageFiles.length - 1){
                Toast.makeText(getApplicationContext(), "마지막 그림입니다.", 
                		Toast.LENGTH_SHORT).show();
            } else {
                num++;
                imageFname = imageFiles[num].toString();
                myPictureView.imagePath = imageFname;
                myPictureView.invalidate();
            }
        });
    }
}

Mainfest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.cos.project8_2">

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

    <application
        android:requestLegacyExternalStorage="true"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Project8_2">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
  • 실행이 되지 않으므로 해결방안 찾기
profile
Nil Desperandum <절대 절망하지 마라>

0개의 댓글