안녕, 세상!

8. 파일 처리 본문

It공부/안드로이드

8. 파일 처리

dev_Lumin 2020. 8. 24. 15:28

(1) 파일 처리의 기본

안드로이드에서 파일을 읽고 쓰는 방법에는 Java에서 제공되는 파일 관련 클래스를 사용하는 방법과 안드로이드에서 제공되는 파일 관련 클래스를 사용하는 방법이 있습니다.

 

① 내장 메모리 파일 처리

앱을 종료했다가 다음에 다시 실행할 때 사용했던 곳부터 이어서 작업하고 싶은 경우 내장 메모리에 파일을 저장하고 읽어오는 방식이 사용됩니다.

 

내장 메모리의 저장 위치는 /data/data/패키지명/files 폴더입니다.

일반적으로 응용 프로그램마다 다른 패키지명을 사용하므로 응용 프로그램별로 고유의 저장 공간이 있다고 생각하면 됩니다.

 

파일을 읽기 위해 먼저 안드로이드 Context클래스의 openFileInput() 메소드를 사용합니다.

이 메소드는 FileInputStream을 반환합니다.

 

파일을 쓰기 위해 openFileOutput() 메소드를 사용하면 FileOutputStream을 반환합니다.

그리고 java에서 제공하는 파일을 읽고 쓰는 java.io.FileInputStream 클래스와 java.io.FileOutputStream의 read(), write() 메소드를 사용하여 파일을 처리합니다.

 

 

AVD 에뮬레이터의 /data/data/패키지명/files 폴더에 접근 방법은 다음과 같습니다.

 

1. 에뮬레이터를 우선 [run] 시킵니다.

2. [Shift]를 두 번 연속 클릭 후 "Device file explorer"라고 검색합니다.

3. 왼쪽 창에 AVD의 파일 시스템을 확인할 수 있습니다.

4. /data/data/패키지명/files에 접근할 수 있습니다.

 

 

다음은 내장 메모리에 데이터를 저장해서 특정 날짜에 작성한 글이 저장되게 하는 예제입니다.

 

activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="utf-8"?>
<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:gravity="center"
    android:orientation="vertical">
 
    <DatePicker
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:datePickerMode="spinner"
        android:calendarViewShown="false"
        android:id="@+id/datePicker1" />
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/edtDiary"
        android:background="#6600ff00"
        android:lines="8" />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/btnWrite"
        android:enabled="false"
        android:text="Button" />
</LinearLayout>
cs

DatePicker의 id는 datePicker1

EditText의 id는 edtDiary

Button의 id는 btnWrite

 

 

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Calendar;
 
 
public class MainActivity extends AppCompatActivity {
    DatePicker dp;
    EditText edtdiary;
    Button btnwrite;
    String filename;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setTitle("일기장");
 
        dp= (DatePicker) findViewById(R.id.datePicker1);
        edtdiary = (EditText) findViewById(R.id.edtDiary);
        btnwrite = (Button) 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);
 
        dp.init(cYear, cMonth, cDay, new DatePicker.OnDateChangedListener() {
            @Override
            public void onDateChanged(DatePicker view, int year, int monthofyear, int dayofmonth) {
                filename=Integer.toString(year)+"-"+Integer.toString(monthofyear+1)+"-"
                        +Integer.toString(dayofmonth)+".txt";
                String str = readDiary(filename);
                edtdiary.setText(str);
                btnwrite.setEnabled(true);
            }
        });
 
        btnwrite.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                try{
                    FileOutputStream outfs = openFileOutput(filename, Context.MODE_PRIVATE);
                    String str = edtdiary.getText().toString();
                    outfs.write(str.getBytes());
                    outfs.close();
                    Toast.makeText(getApplicationContext(), filename+"이 저장됨", Toast.LENGTH_SHORT).show();
                }catch (IOException e){
 
                }
            }
        });
    }
 
    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){
            edtdiary.setHint("일기 없음");
            btnwrite.setText("새로 저장");
        }
        return diarystr;
 
    }
}
cs

 java코드는 크게 3 부분으로 나뉩니다.

36~45, 47~60, 62~78로 나뉩니다.

 

36~45 : DatePicker이 변동될 시 동작하는 메소드입니다.

38~40 : DatePicker이 변동되면 변동된 연월일의 정보를 filename이라는 변수에 .txt라는 문자열로 저장을 합니다.

41 : readDiary라는 메소드를 실행

42 : 반환받은 데이터를 editdiary에 넣습니다.

43 : activity_main.xml에서 비활성화시킨 버튼을 다시 활성화시킵니다.

 

62~78 : readDiary메소드

66 : fname에 해당되는 파일을 열어서 입력 파일 스트림에 내용을 저장합니다. 파일이 없으면 catch문이 실행됩니다.

67~68 : byte형 변수 txt를 선언하고 파일 내용을 txt에 읽어 들입니다.

69 : 파일을 닫습니다.

70 : 읽어온 txt를 문자열로 변경한 후 trim() 메소드로 앞뒤의 공백을 제거하고 반환할 diarystr 변수에 대입합니다.

71 : 내용이 한 번 저장되었으니 버튼을 "수정하기"로 바꿉니다.

 

42 : 반환받은 데이터를 editdiary에 넣습니다.

43 : activity_main.xml에서 비활성화시킨 버튼을 다시 활성화시킵니다.

 

50 : filename변수에 있는 파일을 쓰기 모드로 아웃풋 스트림에 저장합니다. .txt파일도 생성됩니다.

51 : editdiary에 있는 내용을 String형태로 가지고 와서 str변수에 저장 후 str변수의 내용을 byte[] 형으로 아웃풋 스트림에 write 합니다.(filename변수에 있는 .txt파일에 write 한다는 의미)

 

 

처음에 DatePicker을 한 번 굴려주면 위와 같이 나옵니다.

 

일기에 내용을 입력하고 '새로저장' 버튼을 클릭하면 다음과 같이 Toast가 출력됩니다.

 

DatePicker을 다른 날짜로 돌렸다가 다시 돌아오면 다음과 같이 버튼이 '수정하기'로 바뀌고 입력한 데이터가 보존된 것을 확인할 수 있습니다.

 

생성된 .txt파일은 Device File Explorer에서도 확인할  수 있습니다.

 

 

 

 

 

② raw 폴더 파일 처리

Project Tree의 [app] - [res] 폴더에 raw 파일을 생성해서 필요한 파일을 저장해서 사용하는 방법도 있습니다.

Java 소스에서 openRawResource() 메소드를 사용하여 접근할 수 있으며 FileInputStream 클래스 대신 InputStream 클래스를 사용합니다.

/res/raw는 프로젝트에 포함된 폴더이므로 읽기 전용으로만 사용 가능하고 쓰기는 할 수 없습니다.

 

주요 동작 코드는 다음과 같습니다.

 

try {

    InputStream inputS = getResources().openRawResource(R.raw.raw_test);

    byte[] txt = new byte[inputS.available()];

    inputS.read(txt);

    edtRaw.setText(new String(txt));    // edtRaw는 EditText 변수

    inputS.close();

    }catch (IOException e) { }

 

 

 

 

 

(2) 파일 처리의 응용

특정 파일들이 여러 응용 프로그램에서 사용되는 경우에는 SD 카드에 저장하여 활용해야 됩니다.

안드로이드에서는 SD카드에 저장된 데이터에 특별한 인증 절차 없이 접근할 수 있습니다.

제한된 공간의 내장 메모리보다 훨씬 큰 공간을 사용할 수 있으며 확장성도 뛰어납니다.

 

AVD에도 SD카드를 장착할 수 있는데 방법은 다음과 같습니다.

 

메뉴의 [Tools] -> [AVD Manager] 의 에뮬레이터의 오른쪽에 Edit 아이콘을 클릭 후 [Show Advanced Settings]를 클릭하면 사용하는 AVD에 장착된 SD카드를 확인할 수 있습니다.

원하는 크기를 설정하고 [Finish] 버튼을 누르면 됩니다.

 

 

 

 

① SD 카드에서 파일 읽기, 폴더 및 파일 생성하기 예제

아무 .txt파일(내용이 담긴)을 호스트 컴퓨터에 만들어 놓고 Device File Explorer에서 /sdcard 폴더 또는 /storage/emulator/0 폴더에 호스트 컴퓨터에 만든 텍스트 파일을 올려놓습니다.

오른쪽 마우스 클릭 후 [upload]를 눌러서 파일을 올려놓으면 됩니다.

 

 

AndroidManifest.xml

AndroidManifest.xml 파일에 SD 카드를 사용할 수 있도록 퍼미션을 지정해야 합니다.

다음 코드를 AndroidManifest.xml에 넣습니다. (<application 부분은 아래 코드 부분만 넣으면 됨)

 

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

<application

    android:requestLegacyExternalStorage="true"

 

 

이 AndroidManifest.xml 설정은 ① ② ③ 예제 모두 적용해야 합니다.

 

 

다음은 SD카드에 저장된 파일의 내용을 불러오는 기능과 SD카드에 파일 또는 디렉터리(폴더)를 생성할 수 있는 기능을 가진 코드의 예제입니다.

저는 sdcard 디렉터리에 sdtest.txt라는 텍스트 파일에 '가나다라마바사'라고 저장해 놓았습니다.

 

 

acvitity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="utf-8"?>
<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:gravity="center"
    android:orientation="vertical">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btnread"
        android:text="SD카드에서 파일 읽기" />
    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/edtSD"
        android:lines="10" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btnmkdir"
        android:text="SD카드에 디렉터리 생성" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btnrmdir"
        android:text="SD카드에 디렉터리 삭제" />
 
</LinearLayout>
cs

 

 

AcitivyMain.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import android.Manifest;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
 
public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btnRead;
        final EditText edtSD;
        btnRead = (Button) findViewById(R.id.btnread);
        edtSD = (EditText) findViewById(R.id.edtSD);
        ActivityCompat.requestPermissions(thisnew String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, MODE_PRIVATE);
 
        btnRead.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                try {
                    FileInputStream infs = new FileInputStream("/sdcard/sdtext.txt");
                    byte[] txt = new byte[infs.available()];
                    infs.read(txt);
                    edtSD.setText(new String(txt));
                    infs.close();
                } catch (IOException e) {
 
                }
            }
        });
 
        Button btnmkdir, btnrmdir;
        btnmkdir = (Button) findViewById(R.id.btnmkdir);
        btnrmdir = (Button) findViewById(R.id.btnrmdir);
        final String strSDpath = Environment.getExternalStorageDirectory().getAbsolutePath();
        final File myDir = new File(strSDpath+"/mydir");
 
        btnmkdir.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                myDir.mkdir();
            }
        });
 
        btnrmdir.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                myDir.delete();
            }
        });
 
    }
}
cs

19~37이 sdtest.txt의 데이터를 불러오는 기능의 코드입니다.

39~55가 sdcard디렉터리에 mydir라는 파일을 생성하고 제거할 수 있는 기능의 코드입니다.

 

23 : 이 앱에게 파일 액세스 작업을 허용할지 묻는 창이 나옵니다.

27~35 : InputStream에 /sdcard/sdtest.txt 파일의 데이터를 불러서 EditText인 edtSD에 넣고 출력해줍니다.

 

42 : SD카드의 절대 경로를 돌려주는데, 대개 /storage/emulated/0 또는 /sdcard 경로가 됩니다.

저는 /sdcard로 설정되어 있습니다.

 

 

 

 

 

 

② 특정 폴더의 하위 폴더 및 파일 목록 출력 예제

 

activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<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:gravity="center"
    android:orientation="vertical">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btnfilelist"
        android:text="시스템 폴더의 폴더/파일 목록" />
    <EditText
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/edtfilelist" />
 
</LinearLayout>
cs

 

 

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import androidx.appcompat.app.AppCompatActivity;
import java.io.File;
 
public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        Button btnFilelist;
        final EditText edtFilelist;
        btnFilelist = (Button) findViewById(R.id.btnfilelist);
        edtFilelist = (EditText) findViewById(R.id.edtfilelist);
 
        btnFilelist.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                String sysDir = Environment.getRootDirectory().getAbsolutePath();
                File[] sysFiles = (new File(sysDir).listFiles());
 
                String strfname;
                for(int i=0; i<sysFiles.length; i++){
                    if (sysFiles[i].isDirectory()==true){
                        strfname="<폴더> "+sysFiles[i].toString();
                    }
                    else{
                        strfname="<파일> "+sysFiles[i].toString();
                    }
                    edtFilelist.setText(edtFilelist.getText()+"\n"+strfname);
                }
            }
        });
    }
}
cs

23 : 안드로이드의 시스템 폴더 경로를 돌려줍니다. 대개 /system이 됩니다.

24 : 시스템 폴더의 폴더/파일 개수만큼 반복합니다.

27~33 : 현재 파일이 폴더인지 확인해서 폴더이면 앞에 <폴더> 글자를 파일이면 <파일> 글자를 붙입니다.

34 : 파일/폴더 목록을 에디트 텍스트에 이어서 출력합니다.

 

 

 

 

 

 

이미지 뷰어 앱 

저장된 이미지를 넘기면서 볼 수 있는 앱의 예제입니다.

우선 새로운 클래스가 필요하므로 Project Tree의 [java] -> [패키지명]에서 오른쪽 버튼을 클릭한 후 [New] -> [Java Class]를 선택해서 'myPictureview'라는 이름으로 클래스를 생성하겠습니다.

 

 

그다음 미리 사용할 사진들을 [Device File Explorer]에서 /sdcard/pictures 폴더에 적당한 이미지 파일을 upload 해둡니다.

 

 

 

myPictureview.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
 
public class myPictureview extends View {
    String imagePath=null;
    public myPictureview(Context context, @Nullable AttributeSet attrs){
        super(context, attrs);
 
    }
 
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(imagePath !=null) {
            Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
            canvas.drawBitmap(bitmap, 00null);
            bitmap.recycle();
        }
    }
}
cs

19~22 : imagePath에 값이 있으면 화면에 그림 파일을 출력한다는 의미입니다. (Bitmap 클래스는 다음 글에서 설명)

 

 

activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="utf-8"?>
<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">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/btnPrev"
            android:layout_weight="1"
            android:text="이전 그림"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/btnNext"
            android:layout_weight="1"
            android:text="다음 그림" />
 
    </LinearLayout>
    <com.example.myapplication7.myPictureview
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/myPictureview1" />
</LinearLayout>
cs

 

 

AndroidManifest.xml

에서 말한 대로 적용

 

 

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.Manifest;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
 
import java.io.File;
 
public class MainActivity extends AppCompatActivity {
    Button btnPrev, btnNext;
    myPictureview myPicture;  // myPictureview는 제가 따로 만든 클래스, 즉 제가 만든 이미지용 위젯
    int num;
    File[] imageFiles;
    String imageFname;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setTitle("이미지 뷰어");
        ActivityCompat.requestPermissions(thisnew String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},MODE_PRIVATE);
        btnPrev = (Button) findViewById(R.id.btnPrev);
        btnNext = (Button) findViewById(R.id.btnNext);
        myPicture = (myPictureview) findViewById(R.id.myPictureview1);
 
        imageFiles=new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/Pictures").listFiles();
        imageFname=imageFiles[0].toString();
        myPicture.imagePath = imageFname;
 
        btnPrev.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                if(num <=0) {
                    Toast.makeText(getApplicationContext(), "첫번째 그림입니다.", Toast.LENGTH_SHORT).show();
                }
                else{
                    num --;
                    imageFname = imageFiles[num].toString();
                    myPicture.imagePath=imageFname;
                    myPicture.invalidate();
                }
            }
        });
 
        btnNext.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                if(num>=imageFiles.length-1){
                    Toast.makeText(getApplicationContext(), "마지막 그림입니다.",Toast.LENGTH_SHORT).show();
                }
                else{
                    num++;
                    imageFname = imageFiles[num].toString();
                    myPicture.imagePath=imageFname;
                    myPicture.invalidate();
                }
            }
        });
    }
}
cs

14 : myPicture변수는 제가 앞서 만든 클래스를 이용한 위젯입니다.

29 : SD카드에 파일을 읽어서(/sdcard/pictures 안의 파일들) listFiles() 메소드로 배열을 만듭니다.

30 : String형 변수 imageFname에 첫 번째 사진 파일명을 저장합니다.

31 : 저장한 파일명을 myPicture 위젯 안에 있는 imagePath에 저장합니다.

33~45 : 이전 버튼을 클릭할 때 처음 사진이라면 Toast를 나타나게 하고 아니면 이전 사진 나오게 합니다.

40~41 : 현재 번호의 이미지 파일 이름을 myPicture 클래스에 전달합니다.

42 : invalidate() 메소드를 호출하면 myPicture 클래스의 onDraw()가 호출됩니다. (다음 글에서 설명)

47~59 : 다음 버튼을 클릭할 때 마지막 사진이면 Toast를 나타나게 하고 아니면 다음 사진 나오게 합니다.

 

 

'It공부 > 안드로이드' 카테고리의 다른 글

10. 액티비티와 인텐트  (0) 2020.08.26
9. 그래픽  (0) 2020.08.25
7.메뉴와 대화상자  (0) 2020.08.23
6. 고급 위젯  (0) 2020.08.22
5. 레이아웃  (0) 2020.08.21
Comments