2022-05-16 Android_Studio_7

2022. 5. 16. 23:10학부 강의/Android_Studio

데이터를 저장하는 방법

  1. 파일
    1. 내부 저장
    2. 외부 저장
  2. 데이터베이스
  3. 웹서버


내부 저장소

장치의 내부 저장공간에 파일을 저장할 수 있다.

 

해당 애플리케이션만 접근 가능하고 애플리케이션을 제거하면 이들 파일들도 제거된다.

 

  • 파일 읽기 : openFileInput() 메소드 사용 → FileInputStream을 반환 → read()메소드사용
  • 파일 쓰기 : openFileOutput() 메소드 사용 → FileOutputStream을 반환 → write()메소드사용
  • 파일 닫기 : close()
//파일 입출력 스트림 반환
FileInputStream [stream_name] = openFileInput( [file_name] );

 

(Input - read) (output - write) 혼동하지 말기!

 

출처 : https://sowon-dev.github.io/2020/09/23/200924and/


내부 저장소에서 유용한 메소드들

 

 


FileTest01

 

 

 

EditText에 입력한 값을 내부저장소의 파일에 저장.


activity_main.xml

//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">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="vertical"
        android:gravity="center">

        <EditText
            android:id="@+id/EditText01"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:singleLine="false"
            android:hint="입력"
            android:ems="20" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/read"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="읽기"/>
        <Button
            android:id="@+id/write"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="쓰기"/>
    </LinearLayout>

</LinearLayout>

android:singleLine : 개행하지 않고 줄 하나만 사용.

 

//LinearLayout 구성 방법

<LinearLayout
    ...>
    <LinearLayout
    ...
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1">
    ...
    </LinearLayout>

    <LinearLayout
    ...
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    ...
    </LinearLayout>
</LinearLayout>

팀 프로젝트를 진행하면서 이와 같은 레어아웃 구성을 만들지 못했다. 참고하자.

 


MainActivity.java

//MainActivity.java
package kr.co.company.filetest01;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class MainActivity extends AppCompatActivity {
    EditText edit;
    String FILENAME = "test.txt";

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

        edit = (EditText) findViewById(R.id.EditText01);

        Button readButton = (Button) findViewById(R.id.read);
        readButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    FileInputStream fis = openFileInput(FILENAME);
                    byte[] buffer = new byte[fis.available()];
                    fis.read(buffer);
                    edit.setText(new String(buffer));
                    fis.close();
                } catch (IOException e) {}
            }
        });

        Button writeButton = (Button) findViewById(R.id.write);
        writeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    FileOutputStream fos = openFileOutput(FILENAME, Context.MODE_PRIVATE);
                    fos.write(edit.getText().toString().getBytes());
                    fos.close();
                } catch (IOException e) {}
            }
        });
    }
}

file을 열고 닫을 때는 오류가 발생하는 경우가 종종 있으니 try&catch 문을 많이 사용한다.

 

[View] → [Tool Windows] → [Device File Explorer]를 클릭하여 Device File Explorer를 연다.

 

내부 저장소에 실제로 파일이 생성되고 저장된 것을 볼 수 있다.

 


자바 입출력 스트림

 

2022.05.15 - [학부 강의/Android_Studio] - 2022-05-15 자바_입출력_스트림

 

2022-05-15 자바_입출력_스트림

자바 입출력 스트림 출처 : 혼자 공부하는 자바 자바에서 데이터는 스트림을 통해서 입출력된다. 스트림은 단일 방향으로 연속적으로 흘러가는 것을 의미한다. java.io 패키지에는 크게 두 종류의

ramen4598.tistory.com

 

 

openFileInput(), openFileOutput() : 보안상의 제약으로 인해 별도로 제공하는 Context 클래스에서 보안이 적용된 파일 관리 메서드를 이용하여 파일을 Open한답니다.

 

package kr.co.company.filetest01;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class MainActivity extends AppCompatActivity {
    EditText edit;
    String FILENAME = "test.txt";

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

        edit = (EditText) findViewById(R.id.EditText01);
        String filePath = getFilesDir().getPath()+"/test2.txt";
        Toast.makeText(getApplicationContext(), filePath, Toast.LENGTH_LONG).show();

        Button readButton = (Button) findViewById(R.id.read);
        readButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
//                    FileInputStream fis = openFileInput(FILENAME);
                    FileInputStream fis = new FileInputStream(filePath);
                    byte[] buffer = new byte[fis.available()];
                    fis.read(buffer);
                    edit.setText(new String(buffer));
                    fis.close();
                } catch (IOException e) {
                }
            }
        });

        Button writeButton = (Button) findViewById(R.id.write);
        writeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
//                    FileOutputStream fos = openFileOutput(FILENAME, Context.MODE_PRIVATE);
                    FileOutputStream fos = new FileOutputStream(filePath);
                    fos.write(edit.getText().toString().getBytes());
                    fos.close();
                } catch (IOException e) {
                }
            }
        });
    }
}
String filePath = getFilesDir().getPath()+"/test2.txt";
...
FileInputStream fis = new FileInputStream(filePath);
...
FileOutputStream fos = new FileOutputStream(filePath);

위와 같은 방식으로도 입출력 스트림을 생성할 수 있었다.

 

이렇게 작성하면 경로를 설정하는데 getFilesDir().getPath()를 사용해야 하고 아마 보안 설정이 조금 달라질 순 있다.

 

그것을 제외하고선 작동하는 것에 있어 두 방법 모두 입출력 스트림을 생성하는 기능에 별다른 차이점은 없었다.

 


read()

FileInputStream fis = openFileInput(FILENAME);
byte[] buffer = new byte[fis.available()];
fis.read(buffer);
edit.setText(new String(buffer));
fis.close();

 

fis라는 파일 입력 스트림을 통해서 파일의 데이터를 읽어온다.

 

buffer라는 byte의 배열을 만들고 fis에서 읽어올 수 있는 바이트 수만큼 크기를 설정한다.

 

배열 buffer에 fis 스트림에서 온 바이트를 저장한다.

 

그리고 edit(=EditText)에 텍스트로 buffer 배열에 든 바이트 값을 사용하려 한다.

 

문제는 EditText.setText()String을 입력값으로 받으니 buffer에 든 바이트를 String으로 변환해야 한다.

 

 

Java에서는 Stringbyte[]로 변환하여 전달하거나 파일에 저장할 수 있다.

 

그렇다면 데이터를 읽어오는 입장에서는 byte[]를 다시 String으로 변환하여 사용해야 한다.

  • String.getBytes()는 문자열을 byte 배열로 변환합니다. (String to byte[])
  • String 객체를 생성할 때 인자로 byte 배열을 전달하면 문자열로 변환됩니다. (byte[] to String)

 

byte[] bytes = str.getBytes();

String coverted = new String(bytes);

 

물론 String에서 byte[]로 변환할 때와 byte[]를 String 변환할 때 사용하는 인코딩 방식은 동일해야 한다.

 

문자열을 변환할 때, getBytes()String()의 인자로 특정 CharacterSet를 전달하면 지정한 인코딩 방식으로 데이터를 변환할 수 있다.

 

//예시
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);

String coverted = new String(bytes, StandardCharsets.UTF_8);

출처 : https://codechacha.com/ko/java-convert-bytes-to-string/


write()

FileOutputStream fos = openFileOutput(FILENAME, Context.MODE_PRIVATE);
fos.write(edit.getText().toString().getBytes());
fos.close();

FileOutputStreamFileInputStream과는 달리 Context.mode_???가 필요하다.

 

openFileOutput으로 파일 생성 시 아래의 4가지 모드 중 하나를 지정해야 한다.

  • MODE_PRIVATE : 혼자만 사용하는 배타적 모드로 파일 생성(default, 일반적으로 가장 많이 사용)
  • MODE_APPEND : 파일이 이미 존재할 경우 덮어쓰기 모드가 아닌 이어쓰기로 OPEN.
  • MODE_WORLD_READABLE : 다른 응용프로그램이 파일을 읽을 수 있도록 허용
  • MODE_WORLD_WRITABLE : 다른 응용프로그램이 파일을 기록할 수 있도록 허용

 

fos.write(edit.getText().toString().getBytes());

getText() 함수가 리턴하는 타입은 String 클래스 타입을 리턴하지 않고, Editable 인터페이스 타입을 리턴.

 

Editable 인터페이스의 .toString()로 String 타입으로 변환.

 

String.getBytes()는 문자열을 byte 배열로 변환합니다. (String to byte[])

 

출처 : https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=javaking75&logNo=140179569292


Context

 

Context는 어떤 것에 영향을 주는 외부적인 것들을 총칭한다.

 

이는 어떤 루틴이 실행될 때 변수의 값들, 이전에 실행된 함수들의 내부 상태가 될 수도 있고, cpu의 레지스터 값들을 말할 수도 있다.

 

출처 : https://kldp.org/node/51223


외부 저장소

 

외부 저장소 : sd카드 등 이동식 볼륨

 

안드로이드에서 외부 저장소를 사용하려면 외부 저장소에 관한 읽기 및 쓰기 액세스 권한을 정의하여야 한다.

  • READ_EXTERNAL_STORAGE
  • WRITE_EXTERNAL_STORAGE

 

외부 미디어 장착 여부 검사

String state = Environment.getExternalStorageState(); 

if(state.equals(Environment.MEDIA_MOUNTED))
// 미디어에 쓰고 읽을 수 있다

else if(state.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) 
// 미디어를 읽을 수 있다

else
// 쓰거나 읽을 수 없다

FileTest02

 

activity_main.xml는 Filetest02와 동일

 

MainActivity.java

public class MainActivity extends AppCompatActivity {
    EditText edit;
    String FILENAME = "test.txt";

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

        String state = Environment.getExternalStorageState();
        if(state.equals(Environment.MEDIA_MOUNTED)==false){
            Toast.makeText(this, "외부 스토리지 실패", Toast.LENGTH_SHORT).show();
        }

        edit = (EditText) findViewById(R.id.EditText01);

        Button readButton = (Button) findViewById(R.id.read);
        readButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                File file = new File(getExternalFilesDir(null), FILENAME);
                try {
                    InputStream is = new FileInputStream(file);
                    byte[] buffer = new byte[is.available()];
                    is.read(buffer);
                    edit.setText(new String(buffer));
                    is.close();
                } catch (IOException e) {
                    //TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });

        Button writeButton = (Button) findViewById(R.id.write);
        writeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                File file = new File(getExternalFilesDir(null), FILENAME);
                try {
                    OutputStream os = new FileOutputStream(file);
                    os.write(edit.getText().toString().getBytes());
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}
String state = Environment.getExternalStorageState();
if(state.equals(Environment.MEDIA_MOUNTED)==false){
    Toast.makeText(this, "외부 스토리지 실패", Toast.LENGTH_SHORT).show();
}

외부 스토리지에 읽고 쓸 수 있는지 확인한다.

 

.equal()==은 차이가 있다.

 

.equal() : value를 비교 (call by value)

 

==: reference를 비교 (call by reference)

 

stateEnvironment.MEDIA_MOUNTED는 가지고 있는 값은 같지만 주소 값은 다르다.

 

그래서 equal()로 비교하는 것이 옳다.

 

File file = new File(getExternalFilesDir(null), FILENAME);
try {
    InputStream is = new FileInputStream(file);
    byte[] buffer = new byte[is.available()];
    is.read(buffer);
    edit.setText(new String(buffer));
    is.close();
} catch (IOException e) {
    //TODO Auto-generated catch block
    e.printStackTrace();
}
File file = new File(getExternalFilesDir(null), FILENAME);
try {
    OutputStream os = new FileOutputStream(file);
  os.write(edit.getText().toString().getBytes());
  os.close();
} catch (IOException e) {
    e.printStackTrace();
}
  • File file = new File(getExternalFilesDir(null), FILENAME);

 

File class의 생성자 설명
File(File parent, String child) parent 폴더 아래 child라는 파일에 대한 File 객체를 생성
File(String path) path에 해당하는 파일에 대한 File 객체를 생성
File(String parent, String child) parent 폴더 아래 child라는 파일에 대한 File 객체를 생성
File(URI uri) url 경로에 파일을 File 객체로 생성

첫 번째 생성자에 속한다.

 

  • getExternalFilesDir(null)
public abstract File getExternalFilesDir (String type)
String type :The type of files directory to return. May be null for the root of the files directory or one of the following constants for a subdirectory: Environment.DIRECTORY_MUSIC, Environment.DIRECTORY_PODCASTS, Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS, Environment.DIRECTORY_PICTURES, or Environment.DIRECTORY_MOVIES
출처 : https://developer.android.com/reference/android/content/Context.html?hl=ko#getExternalFilesDir(java.lang.String)

 

File 객체를 반환한다.

 

인자의 값에 따라서 반환하는 파일 디렉터리의 종류가 바뀐다.

 

음악, 팟캐스트, 알람, 알림, 사진... 등이 있는 디렉터리를 선택할 수 있다.

 

null을 선택하면 앱의 files의 주소를 반환한다.

 

  • FileInputStream is = new FileInputStream(file); 를 사용하지 않았을까?

자바의 다형성과 동적 바인딩 때문에 실행 결과는 동일하다.

 

  • e.printStackTrace();
JAVA Exception 에러 출력 설명
e.getMessage() 간략하게 에러의 원인을 출력
e.toString() 에러의 Exception 내용과 원인을 출력
e.printStackTrace() 에러 발생 원인의 위치를 찾아 단계별로 출력

 


AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="kr.co.company.filetest02">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

    <application
        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.FileTest02">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>
//추가된 사항

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

외부 저장소를 이용하기 위해서는 매니페스트 파일에 필요한 권한을 추가해야 한다.

 

매니페스트 파일은 (manifest file)은 컴퓨팅에서 집합의 일부 또는 논리 정연한 단위인 파일들의 그룹을 위한 메타데이터를 포함하는 파일이다.

 

예를 들어, 컴퓨터 프로그램의 파일들은 이름, 버전 번호, 라이선스, 프로그램의 구성 파일들을 가질 수 있다.

 

출처 : https://ko.wikipedia.org/wiki/매니페스트_파일#애플리케이션어셈블리_매니페스트


힘들어 ㅜㅜ

 

'학부 강의 > Android_Studio' 카테고리의 다른 글

2022-05-23 Intent_이해  (0) 2022.05.24
2022-05-17 project_github_upload  (0) 2022.05.17
2022-05-15 자바_입출력_스트림  (0) 2022.05.15
2022-04-15 Android_Studio_6  (0) 2022.04.16
2022-04-03 Android_Studio_5  (0) 2022.04.04