코틀린의 파일 입출력은 매우간단하다.
우선 데이터를 파일로 쓸때는 JSON을 이용하면 간편하니 gson 라이브러리를 쓰자.
//userInfo라는 객체를 json 형태로 바꾼다.
val fileContents = Gson().toJson(userInfo)
//휴대폰의 내부저장소 중 캐시저장소에 파일을 만든다.
val file = File(context.cacheDir, "account_" + userInfo.email)
//파일에 준비한 json을 쓴다.
file.writeText(fileContents)
파일을 쓰는건 이렇게만 해도 된다.
writeText는 내부적으로 FileOutputStream을 연다음, 인자로 넘긴 String을 파일에 쓴다. 그런 다음 닫는다.
(사실 .use를 쓰기때문에 수동으로 열고 닫지는 않는다)
public fun File.writeBytes(array: ByteArray): Unit = FileOutputStream(this).use { it.write(array) }
그냥 요래 되있을 뿐이다.
아무튼 이렇게 쓴 파일을 읽을때는
private fun getUserInfoFromFile(context: Context, email: String): UserInfo? {
val file = File(context.cacheDir, "account_" + email)
if (file.exists())
return Gson().fromJson(file.readText(), UserInfo::class.java)
return null
}
이런식으로 해주면 된다.
설명할 것도 없지만, 간단하게 설명하면 경로를 토대로 파일객체를 만들고 파일이 있으면 파일을 읽어서 그 내용을 UserInfo형으로 변환하는 코드이다.
자 그럼 하나씩 살펴보자.
Gson().toJson(userInfo)
이 코드에서 의문점이 들지 않았는가?
gson은 그냥 json을 만들고 파싱하는 라이브러리일 뿐이다. userInfo라는 객체를 어떤 식으로 json 으로 만들고 또 그 반대로 json을 userInfo로 만들어야하는지 모른다.
따라서 우리가 그 방법을 정해줘야한다.
data class UserInfo(
@SerializedName("email")
val email : String = "",
@SerializedName("nickname")
val nickname : String = "",
@SerializedName("sex")
val sex : Int = 0,
@SerializedName("age")
val age : Int = 10,
@SerializedName("categorys")
val categorys : HashSet<Int> = hashSetOf()) : Serializable
@SerializedName 라는 gson에 있는 어노테이션을 이용해서 클래스를 구성하는 멤버변수의 값이 어떤 key에 바인딩되어 json화 될지 정해줄 수있다. 예시를 보자
public class MyClass {
@SerializedName("name") String a;
@SerializedName(value="name1", alternate={"name2", "name3"}) String b;
String c;
public MyClass(String a, String b, String c) {
this.a = a;
this.b = b;
this.c = c;
}
}
MyClass target = new MyClass("v1", "v2", "v3");
Gson gson = new Gson();
String json = gson.toJson(target);
System.out.println(json);
===== OUTPUT =====
{"name":"v1","name1":"v2","c":"v3"}
위 예시처럼 @SerializedName을 선언하지 않은 필드는 변수이름이 그대로 key가 됬음을 알 수 있다.
+
사족을 좀 달자면, C++을 하던 사람은 이렇게 생각할 수도 있다.
'그냥 클래스를 바이트어레이로 변환해서 저장한다음에 그걸 다시 읽어들여서 클래스로 만들면 되지 않나?'
C++은 memcpy라는 메모리카피 함수가 있기때문에 가능해보인다.
하지만 한계점이 있다.
자바나 코틀린에서 원시타입을 제외한 객체들은 모두 사용자가 접근 불가능한 포인터로 선언된다. (정확하진 않다.)
그런데 클래스의 바이너리데이터를 그대로 저장해버리면 포인터의 값이 저장되버린다.
즉, 실제 객체의 값이 아니라 주소가 저장되는 것이다.
따라서 다시 파일을 불러와도 주소가 불러와지고, 그 주소는 이상한 메모리를 가리키게 될것이다.
그렇다면 원시타입으로만 이루어진 클래스는 그냥 저장해도 될까?
그것도 안된다.
코틀린에서는 잘 모르겠지만, 클래스에는 숨겨진 필드가 존재한다.
C++의 경우에는 가상함수테이블을 가리키는 포인터가 있다. 따라서 그대로 저장할 경우 똑같은 이슈가 발생한다.
이런 문제들때문에 클래스를 저장할때는 memcpy 같은걸로 바로 저장하지 않고 json 형식을 이용하는 것이다.
'안드로이드 앱개발' 카테고리의 다른 글
카메라로 사진찍기 (0) | 2020.10.27 |
---|---|
파일입출력2 : 저장소 (Android Studio Internal/External Storage) (0) | 2020.10.27 |
AdMob nativeAd 를 RecyclerView를 활용해 표시하기 (nativeAd with RecyclerView) (0) | 2020.10.27 |
volley로 이미지 업로드 (volley multipart request) (0) | 2020.10.27 |
라이브러리는 이해하고 사용하자. (0) | 2020.10.27 |