2026/02/22

오늘의 이야기

배경화면 이미지



이렇게 눈이 온걸 본적은 모니터 화면에 나오는 설경이 아름답다.   이 겨울도 이렇게 눈 구경 한번 제대로 못하고 지나갈까? 이 겨울엔 한 번쯤 꼭 눈 구경하러 가야겠다.


 


낼모레가 크리스마스라는데... 나이를 먹어서 인가? 그다지 흥이 나지 않는다. 그저 또 한 해가 가버리는구나 하는 생각만 들뿐...


 





오늘의 이야기



#스치니1000프로젝트 #재미 #행운기원 #Compose #Firebase

🎯 야 너 토요일마다 로또 확인하냐?
나도 맨날 "혹시나~" 하면서 봤거든 ㅋㅋ

근데 이제는 그냥 안 해
AI한테 맡겼어 🤖✨

그것도 구글 Gemini로다가!

그래서 앱 하나 만들었지
👉 "로또 예상번호 by Gemini" 🎱

AI가 분석해서 번호 딱! 뽑아줌
그냥 보고 참고만 하면 됨

재미로 해도 좋고…
혹시 모르는 거잖아? 😏


https://play.google.com/store/apps/details?id=com.billcorea.gptlotto1127




오늘의 이야기




동짓날 먹어 보는 팥죽...

네이버에게 물었더니 알려준  죽 집
진국이기는 하다.

줄이 좀 있네요.  날이 날인지라...






오늘의 이야기



바탕화면에 갑자기 나타난 사슴 한마리...
눈길에서 집으로 잘 찾아가길...

하얀눈길이 잘 보일까?

오늘도 좋은 밤 되길...





오늘의 이야기


#스하리1000명프로젝트

스치니들!
내가 만든 이 앱은, 내 폰에 오는 알림 중에서 중요한 키워드가 있는 경우
등록해둔 친구에게 자동으로 전달해주는 앱이야 📲

예를 들어, 카드 결제 알림을 와이프나 자녀에게 보내주거나
이번 달 지출을 달력처럼 확인할 수도 있어!

앱을 함께 쓰려면 친구도 설치 & 로그인해줘야 해.
그래야 친구 목록에서 서로 선택할 수 있으니까~
서로 써보고 불편한 점 있으면 알려줘 🙏

👉 https://play.google.com/store/apps/details?id=com.nari.notify2kakao





오늘의 이야기

이메일



난 고작   $7.75을 유지 하고 있을 뿐인데...

애드센스 연동



언제면 처음으로 외화벌이(?)를 할 수 있을까 ??? 나도 앵벌이 인가 ??? ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

$100가 되려면 아직 갈길이 멀다...





오늘의 이야기

예전에 어떤 앱을 만들기 할 때 사용했던 음력 변환 하는 소스를 어딘가에서 찾았던 거 같은데... 다시 한번 기억해 보기 위해서 적어 보기로 했다. 


 


https://billcorea.tistory.com/4



 


구글로간 음력 설명서


새로운 버젼으로 업데이트 해 드립니다. Google Calendar 에서는 아직 음력 관리를 원할하게 해 주지 않는 현상이 있었습니다. 그래서 과거에 만들었던 앱을 다시 만들어 보았습니다. 2020.06.20 구글에


billcorea.tistory.com




 


이건 어딘가에서 퍼왔던 소스인데, 기억이 가물 가물 하다.  


import java.text.SimpleDateFormat;
import java.util.Date;

public class LunarTranser {
private static final int kk[] = {
1, 2, 1, 2, 1, 2, 2, 3, 2, 2,
1, 2, 1, 1, 2, 1, 2, 1, 2, 1,
2, 2, 1, 2, 2, 0, 1, 1, 2, 1,
1, 2, 1, 2, 2, 2, 1, 2, 0, 2,
1, 1, 2, 1, 3, 2, 1, 2, 2, 1,
2, 2, 2, 1, 1, 2, 1, 1, 2, 1,
2, 1, 2, 2, 0, 2, 1, 2, 1, 2,
1, 1, 2, 1, 2, 1, 2, 0, 2, 2,
1, 2, 3, 2, 1, 1, 2, 1, 2, 1,
2, 2, 1, 2, 2, 1, 2, 1, 1, 2,
1, 2, 1, 0, 2, 1, 2, 2, 1, 2,
1, 2, 1, 2, 1, 2, 0, 1, 2, 3,
2, 1, 2, 2, 1, 2, 1, 2, 1, 2,
1, 2, 1, 2, 1, 2, 1, 2, 2, 1,
2, 2, 0, 1, 1, 2, 1, 1, 2, 3,
2, 2, 1, 2, 2, 2, 1, 1, 2, 1,
1, 2, 1, 2, 1, 2, 2, 2, 0, 1,
2, 1, 2, 1, 1, 2, 1, 2, 1, 2,
2, 0, 2, 1, 2, 1, 2, 3, 1, 2,
1, 2, 1, 2, 1, 2, 2, 2, 1, 2,
1, 1, 2, 1, 2, 1, 2, 0, 1, 2,
2, 1, 2, 1, 2, 1, 2, 1, 2, 1,
0, 2, 1, 2, 3, 2, 2, 1, 2, 1,
2, 1, 2, 1, 2, 1, 2, 1, 2, 1,
2, 2, 1, 2, 1, 2, 0, 1, 2, 1,
1, 2, 1, 2, 2, 3, 2, 2, 1, 2,
1, 2, 1, 1, 2, 1, 2, 1, 2, 2,
2, 1, 0, 2, 1, 2, 1, 1, 2, 1,
2, 1, 2, 2, 2, 0, 1, 2, 1, 2,
1, 3, 2, 1, 1, 2, 2, 1, 2, 2,
2, 1, 2, 1, 1, 2, 1, 1, 2, 2,
1, 0, 2, 2, 1, 2, 2, 1, 1, 2,
1, 2, 1, 2, 0, 1, 2, 2, 1, 4,
1, 2, 1, 2, 1, 2, 1, 2, 1, 2,
1, 2, 1, 2, 2, 1, 2, 1, 2, 1,
0, 2, 1, 1, 2, 2, 1, 2, 1, 2,
2, 1, 2, 0, 1, 2, 3, 1, 2, 1,
2, 1, 2, 2, 2, 1, 2, 1, 2, 1,
1, 2, 1, 2, 1, 2, 2, 2, 1, 0,
2, 1, 2, 1, 1, 2, 3, 1, 2, 2,
1, 2, 2, 2, 1, 2, 1, 1, 2, 1,
1, 2, 2, 1, 2, 0, 2, 2, 1, 2,
1, 1, 2, 1, 1, 2, 1, 2, 0, 2,
2, 1, 2, 2, 3, 1, 2, 1, 2, 1,
1, 2, 2, 1, 2, 2, 1, 2, 1, 2,
1, 2, 1, 2, 0, 1, 2, 1, 2, 1,
2, 2, 1, 2, 1, 2, 1, 0, 2, 1,
3, 2, 1, 2, 2, 1, 2, 2, 1, 2,
1, 2, 1, 1, 2, 1, 2, 1, 2, 2,
2, 1, 2, 0, 1, 2, 1, 1, 2, 1,
2, 3, 2, 2, 1, 2, 2, 1, 2, 1,
1, 2, 1, 1, 2, 2, 1, 2, 2, 0,
2, 1, 2, 1, 1, 2, 1, 1, 2, 1,
2, 2, 0, 2, 1, 2, 2, 1, 3, 2,
1, 1, 2, 1, 2, 2, 1, 2, 2, 1,
2, 1, 2, 1, 2, 1, 1, 2, 0, 2,
1, 2, 1, 2, 2, 1, 2, 1, 2, 1,
1, 0, 2, 1, 2, 2, 3, 2, 1, 2,
2, 1, 2, 1, 2, 1, 1, 2, 1, 2,
1, 2, 2, 1, 2, 2, 1, 0, 2, 1,
1, 2, 1, 2, 1, 2, 2, 1, 2, 2,
0, 1, 2, 3, 1, 2, 1, 1, 2, 2,
1, 2, 2, 2, 1, 2, 1, 1, 2, 1,
1, 2, 1, 2, 2, 2, 0, 1, 2, 2,
1, 1, 2, 3, 1, 2, 1, 2, 2, 1,
2, 2, 2, 1, 1, 2, 1, 1, 2, 1,
2, 1, 0, 2, 2, 2, 1, 2, 1, 2,
1, 1, 2, 1, 2, 0, 1, 2, 2, 1,
2, 4, 1, 2, 1, 2, 1, 1, 2, 1,
2, 1, 2, 2, 1, 2, 2, 1, 2, 1,
2, 0, 1, 1, 2, 1, 2, 1, 2, 2,
1, 2, 2, 1, 0, 2, 1, 1, 4, 1,
2, 1, 2, 1, 2, 2, 2, 1, 2, 1,
1, 2, 1, 1, 2, 1, 2, 2, 2, 1,
0, 2, 2, 1, 1, 2, 1, 1, 4, 1,
2, 2, 1, 2, 2, 2, 1, 1, 2, 1,
1, 2, 1, 2, 1, 2, 0, 2, 2, 1,
2, 1, 2, 1, 1, 2, 1, 2, 1, 0,
2, 2, 1, 2, 2, 1, 4, 1, 1, 2,
1, 2, 1, 2, 1, 2, 2, 1, 2, 2,
1, 2, 1, 1, 2, 0, 1, 2, 1, 2,
1, 2, 2, 1, 2, 2, 1, 2, 0, 1,
1, 2, 1, 4, 1, 2, 1, 2, 2, 1,
2, 2, 1, 1, 2, 1, 1, 2, 1, 2,
2, 2, 1, 2, 0, 2, 1, 1, 2, 1,
1, 2, 1, 2, 2, 1, 2, 0, 2, 2,
3, 1, 2, 1, 1, 2, 1, 2, 1, 2,
2, 2, 1, 2, 1, 2, 1, 1, 2, 1,
2, 1, 2, 0, 2, 2, 1, 2, 1, 2,
1, 3, 2, 1, 2, 1, 2, 2, 1, 2,
2, 1, 2, 1, 1, 2, 1, 2, 1, 0,
2, 1, 2, 2, 1, 2, 1, 2, 1, 2,
1, 2, 0, 1, 2, 1, 2, 1, 4, 2,
1, 2, 1, 2, 1, 2, 1, 2, 1, 1,
2, 2, 1, 2, 2, 1, 2, 2, 0, 1,
1, 2, 1, 1, 2, 1, 2, 2, 1, 2,
2, 0, 2, 1, 1, 4, 1, 1, 2, 1,
2, 1, 2, 2, 2, 1, 2, 1, 2, 1,
1, 2, 1, 2, 1, 2, 2, 0, 2, 1,
2, 1, 2, 1, 1, 2, 3, 2, 1, 2,
2, 1, 2, 2, 1, 2, 1, 1, 2, 1,
2, 1, 2, 0, 1, 2, 2, 1, 2, 1,
2, 1, 2, 1, 2, 1, 0, 2, 1, 2,
1, 2, 2, 3, 2, 1, 2, 1, 2, 1,
2, 1, 2, 1, 2, 1, 2, 2, 1, 2,
1, 2, 0, 1, 2, 1, 1, 2, 1, 2,
2, 1, 2, 2, 1, 0, 2, 1, 2, 1,
3, 2, 1, 2, 1, 2, 2, 2, 1, 2,
1, 2, 1, 1, 2, 1, 2, 1, 2, 2,
2, 0, 1, 2, 1, 2, 1, 1, 2, 1,
1, 2, 2, 1, 0, 2, 2, 2, 3, 2,
1, 1, 2, 1, 1, 2, 2, 1, 2, 2,
1, 2, 2, 1, 1, 2, 1, 2, 1, 2,
0, 1, 2, 2, 1, 2, 1, 2, 3, 2,
1, 2, 1, 2, 1, 2, 1, 2, 1, 2,
2, 1, 2, 1, 2, 1, 0, 2, 1, 1,
2, 2, 1, 2, 1, 2, 2, 1, 2, 0,
1, 2, 1, 1, 2, 3, 2, 1, 2, 2,
2, 1, 2, 1, 2, 1, 1, 2, 1, 2,
1, 2, 2, 2, 1, 0, 2, 1, 2, 1,
1, 2, 1, 1, 2, 2, 2, 1, 0, 2,
2, 1, 2, 3, 1, 2, 1, 1, 2, 2,
1, 2, 2, 2, 1, 2, 1, 1, 2, 1,
1, 2, 1, 2, 0, 2, 2, 1, 2, 1,
2, 1, 2, 3, 2, 1, 1, 2, 2, 1,
2, 2, 1, 2, 1, 2, 1, 2, 1, 1,
0, 2, 2, 1, 2, 1, 2, 2, 1, 2,
1, 2, 1, 0, 2, 1, 1, 2, 1, 2,
4, 1, 2, 2, 1, 2, 1, 2, 1, 1,
2, 1, 2, 1, 2, 2, 1, 2, 2, 0,
1, 2, 1, 1, 2, 1, 1, 2, 2, 1,
2, 2, 0, 2, 1, 2, 1, 3, 2, 1,
1, 2, 2, 1, 2, 2, 2, 1, 2, 1,
1, 2, 1, 1, 2, 1, 2, 2, 0, 2,
1, 2, 2, 1, 1, 2, 1, 1, 2, 3,
2, 2, 1, 2, 2, 1, 2, 1, 2, 1,
1, 2, 1, 2, 0, 1, 2, 2, 1, 2,
2, 1, 2, 1, 2, 1, 1, 0, 2, 1,
2, 2, 1, 2, 3, 2, 2, 1, 2, 1,
2, 1, 1, 2, 1, 2, 1, 2, 2, 1,
2, 2, 1, 0, 2, 1, 1, 2, 1, 2,
1, 2, 2, 1, 2, 2, 0, 1, 2, 1,
1, 2, 3, 1, 2, 1, 2, 2, 2, 2,
1, 2, 1, 1, 2, 1, 1, 2, 1, 2,
2, 2, 0, 1, 2, 2, 1, 1, 2, 1,
1, 2, 1, 2, 2, 0, 1, 2, 2, 3,
2, 1, 2, 1, 1, 2, 1, 2, 1, 2,
2, 2, 1, 2, 1, 2, 1, 1, 2, 1,
2, 0, 1, 2, 2, 1, 2, 2, 1, 2,
3, 2, 1, 1, 2, 1, 2, 1, 2, 2,
1, 2, 1, 2, 2, 1, 2, 0, 1, 1,
2, 1, 2, 1, 2, 2, 1, 2, 2, 1,
0, 2, 1, 1, 2, 1, 3, 2, 2, 1,
2, 2, 2, 1, 2, 1, 1, 2, 1, 1,
2, 1, 2, 2, 2, 1, 0, 2, 2, 1,
1, 2, 1, 1, 2, 1, 2, 2, 1, 0,
2, 2, 2, 1, 3, 2, 1, 1, 2, 1,
2, 1, 2, 2, 2, 1, 2, 1, 2, 1,
1, 2, 1, 2, 1, 0, 2, 2, 1, 2,
2, 1, 2, 1, 1, 2, 1, 2, 0, 1,
2, 3, 2, 2, 1, 2, 1, 2, 2, 1,
1, 2, 1, 2, 1, 2, 1, 2, 2, 1,
2, 2, 1, 2, 0, 1, 1, 2, 1, 2,
1, 2, 3, 2, 2, 1, 2, 2, 1, 1,
2, 1, 1, 2, 1, 2, 2, 2, 1, 2,
0, 2, 1, 1, 2, 1, 1, 2, 1, 2,
2, 1, 2, 0, 2, 2, 1, 1, 2, 3,
1, 2, 1, 2, 1, 2, 2, 2, 1, 2,
1, 2, 1, 1, 2, 1, 2, 1, 2, 0,
2, 1, 2, 2, 1, 2, 1, 1, 2, 1,
2, 1, 0, 2, 1, 2, 4, 2, 1, 2,
1, 1, 2, 1, 2, 1, 2, 1, 2, 2,
1, 2, 1, 2, 1, 2, 1, 2, 0, 1,
2, 1, 2, 1, 2, 1, 2, 2, 3, 2,
1, 2, 1, 2, 1, 1, 2, 1, 2, 2,
2, 1, 2, 2, 0, 1, 1, 2, 1, 1,
2, 1, 2, 2, 1, 2, 2, 0, 2, 1,
1, 2, 1, 3, 2, 1, 2, 1, 2, 2,
2, 1, 2, 1, 2, 1, 1, 2, 1, 2,
1, 2, 2, 0, 2, 1, 2, 1, 2, 1,
1, 2, 1, 2, 1, 2, 0, 2, 1, 2,
2, 3, 2, 1, 1, 2, 1, 2, 1, 2,
1, 2, 2, 1, 2, 1, 2, 1, 2, 1,
2, 1, 0, 2, 1, 2, 1, 2, 2, 1,
2, 1, 2, 1, 2, 0, 1, 2, 3, 2,
1, 2, 1, 2, 2, 1, 2, 1, 2, 1,
2, 1, 1, 2, 1, 2, 2, 1, 2, 2,
1, 0, 2, 1, 2, 1, 1, 2, 3, 2,
1, 2, 2, 2, 1, 2, 1, 2, 1, 1,
2, 1, 2, 1, 2, 2, 2, 0, 1, 2,
1, 2, 1, 1, 2, 1, 1, 2, 2, 2,
0, 1, 2, 2, 1, 2, 3, 1, 2, 1,
1, 2, 2, 1, 2, 2, 1, 2, 2, 1,
1, 2, 1, 1, 2, 2, 0, 1, 2, 1,
2, 2, 1, 2, 1, 2, 1, 2, 1, 0,
2, 1, 2, 3, 2, 1, 2, 2, 1, 2,
1, 2, 1, 2, 1, 1, 2, 1, 2, 2,
1, 2, 2, 1, 2, 0, 1, 2, 1, 1,
2, 1, 2, 3, 2, 2, 2, 1, 2, 1,
2, 1, 1, 2, 1, 2, 1, 2, 2, 2,
1, 0, 2, 1, 2, 1, 1, 2, 1, 1,
2, 2, 1, 2, 0, 2, 2, 1, 2, 1,
1, 4, 1, 1, 2, 1, 2, 2, 2, 2,
1, 2, 1, 1, 2, 1, 1, 2, 1, 2,
0, 2, 2, 1, 2, 1, 2, 1, 2, 1,
1, 2, 1, 0, 2, 2, 1, 2, 2, 3,
2, 1, 2, 1, 2, 1, 1, 2, 1, 2,
2, 1, 2, 2, 1, 2, 1, 2, 1, 0,
2, 1, 1, 2, 1, 2, 2, 1, 2, 2,
1, 2, 0, 1, 2, 3, 1, 2, 1, 2,
1, 2, 2, 2, 1, 2, 1, 2, 1, 1,
2, 1, 1, 2, 2, 1, 2, 2, 0
};
private final static String yuk[] = {"갑", "을", "병", "정", "무", "기", "경", "신", "임", "계"};
private final static String gap[] = {"자", "축", "인", "묘", "진", "사", "오", "미", "신", "유", "술", "해"};
private final static String ddi[] = {"쥐띠", "소띠", "범띠", "토끼띠", "용띠", "뱀띠", "말띠", "양띠", "원숭이띠", "닭띠", "개띠", "돼지띠"};
private final static String week[] = {"일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일"};

private static final int m[] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31,
30, 31
};

private static Date init_date;

private static final String errMsg = "입력값 에러";

/**
* 음력을 양력으로
*
* @param TranseDay 음력일('yyyyMMdd')
* @param leapyes 윤달 여부
* @return String 처리결과 양력일 엔티티
* @throws java.lang.Exception
*/
public static String LunarTranse(String TranseDay, boolean leapyes) throws Exception {
@SuppressWarnings("unused")
int dt[] = new int[163];
int lyear = Integer.parseInt(TranseDay.substring(0,4));
int lmonth = Integer.parseInt(TranseDay.substring(4,6));
int lday = Integer.parseInt(TranseDay.substring(6,8));

if(!leapyes && !verifyDate(lyear, lmonth, lday, "solar-"))
{
return "";
}
if(leapyes && !verifyDate(lyear, lmonth, lday, "solar+"))
{
return "";

}
int m1 = -1;
long td = 0L;
if(lyear != 1881)
{
m1 = lyear - 1882;
for(int i = 0; i <= m1; i++)
{
for(int j = 0; j < 13; j++)
td = td + (long)kk[i * 13 + j];

if(kk[i * 13 + 12] == 0)
td = td + 336L;
else
td = td + 362L;
}

}
m1++;
int n2 = lmonth - 1;
int m2 = -1;
do
{
m2++;
if(kk[m1 * 13 + m2] > 2)
{
td = td + 26L + (long)kk[m1 * 13 + m2];
n2++;
continue;
}
if(m2 == n2)
break;
td = td + 28L + (long)kk[m1 * 13 + m2];
} while(true);
if(leapyes)
td = td + 28L + (long)kk[m1 * 13 + m2];
td = td + (long)lday + 29L;
m1 = 1880;
do
{
m1++;
boolean leap = m1 % 400 == 0 || m1 % 100 != 0 && m1 % 4 == 0;
if(leap)
m2 = 366;
else
m2 = 365;
if(td < (long)m2)
break;
td = td - (long)m2;
} while(true);
int syear = m1;
m[1] = m2 - 337;
m1 = 0;
do
{
m1++;
if(td <= (long)m[m1 - 1])
break;
td = td - (long)m[m1 - 1];
} while(true);
int smonth = m1;
int sday = (int)td;
long y = (long)syear - 1L;
td = ((y * 365L + y / 4L) - y / 100L) + y / 400L;
boolean leap = syear % 400 == 0 || syear % 100 != 0 && syear % 4 == 0;
if(leap)
m[1] = 29;
else
m[1] = 28;
for(int i = 0; i < smonth - 1; i++)
td = td + (long)m[i];

td = td + (long)sday;
@SuppressWarnings("unused")
int w = (int)(td % 7L);
int i = (int)(td % 10L);
i = (i + 4) % 10;
int j = (int)(td % 12L);
j = (j + 2) % 12;
@SuppressWarnings("unused")
int k1 = (lyear + 6) % 10;
@SuppressWarnings("unused")
int k2 = (lyear + 8) % 12;

String sValue= String.valueOf(syear);

if(smonth<10)
sValue+="0";
sValue+= String.valueOf(smonth);
if(sday<10)
sValue+="0";
sValue+= String.valueOf(sday);

String return_value = sValue ;

return return_value;
}
/**
* 양력을 음력으로
*
* @param TranseDay 양력일('yyyyMMdd')
* @return String 처리결과 음력일
* @throws java.lang.Exception
*/
public static String solarTranse(String TranseDay) throws Exception {

// 2020.04.15 : 음력의 날자도 당해년도 음력 날자를 찾아서 주는 것으로.
long time = System.currentTimeMillis();
SimpleDateFormat sdf = new SimpleDateFormat("yyyymmdd");
java.sql.Date dd = new java.sql.Date(time);
TranseDay = sdf.format(dd).substring(0, 4) + TranseDay.substring(4, 8) ;

int dt[] = new int[163];
for(int i = 0; i < 163; i++)
{
dt[i] = 0;
for(int j = 0; j < 12; j++)
switch(kk[i * 13 + j])
{
case 1: // '\001'
case 3: // '\003'
dt[i] = dt[i] + 29;
break;

case 2: // '\002'
case 4: // '\004'
dt[i] = dt[i] + 30;
break;
}

switch(kk[i * 13 + 12])

{
case 1: // '\001'
case 3: // '\003'
dt[i] = dt[i] + 29;
break;

case 2: // '\002'
case 4: // '\004'
dt[i] = dt[i] + 30;
break;
}
}
int syear = Integer.parseInt(TranseDay.substring(0,4));
int smonth = Integer.parseInt(TranseDay.substring(4,6));
int sday = Integer.parseInt(TranseDay.substring(6,8));

long k11 = syear - 1;
long td2 = ((k11 * 365L + k11 / 4L) - k11 / 100L) + k11 / 400L;
boolean ll = syear % 400 == 0 || syear % 100 != 0 && syear % 4 == 0;
if(ll)
m[1] = 29;
else
m[1] = 28;

if(!verifyDate(syear, smonth, sday, "lunar"))
{
throw new Exception("Date Error [" + syear + smonth + sday + "]");

}

for(int i = 0; i < smonth - 1; i++)
td2 = td2 + (long)m[i];

td2 = td2 + (long)sday;


long td = (td2 - 0xa7a5eL) + 1L;
long td0 = dt[0];
int i=0;
for(i = 0; i < 163; i++)
{
if(td <= td0)
break;
td0 = td0 + (long)dt[i + 1];
}

int lyear = i + 1881;
td0 = td0 - (long)dt[i];
td = td - td0;
int jcount=0;
if(kk[i * 13 + 12] != 0)
jcount = 13;
else
jcount = 12;
int m2 = 0;
int j=0;
int m1;
for(j = 0; j < jcount; j++)
{
if(kk[i * 13 + j] <= 2)
m2++;
if(kk[i * 13 + j] <= 2)
m1 = kk[i * 13 + j] + 28;
else
m1 = kk[i * 13 + j] + 26;
if(td <= (long)m1)
break;
td = td - (long)m1;
}

@SuppressWarnings("unused")
int m0 = j;
long lmonth = m2;
int lday = (int)td;
@SuppressWarnings("unused")
int w = (int)(td2 % 7L);
i = (int)((td2 + 4L) % 10L);
j = (int)((td2 + 2L) % 12L);
@SuppressWarnings("unused")
int i1 = (lyear + 6) % 10;
@SuppressWarnings("unused")
int j1 = (lyear + 8) % 12;
String sValue= String.valueOf(lyear);

if(lmonth<10)
sValue+="0";
sValue+= String.valueOf(lmonth);
if(lday<10)
sValue+="0";
sValue+= String.valueOf(lday);

String return_value = sValue ;

return return_value;
}

private static boolean verifyDate(int k, int l, int l1, String s)
{
if(k < 1881 || k > 2043 || l < 1 || l > 12)
return false;
if(s.equals("lunar") && l1 > m[l - 1])
return false;
if(s.equals("solar+"))
{
if(kk[(k - 1881) * 13 + 12] < 1)
return false;
if(kk[(k - 1881) * 13 + l] < 3)
return false;
if(kk[(k - 1881) * 13 + l] + 26 < l1)
return false;
}
if(s.equals("solar-"))
{
int j = l - 1;
for(int i = 1; i <= 12; i++)
if(kk[((k - 1881) * 13 + i) - 1] > 2)
j++;

if(l1 > kk[(k - 1881) * 13 + j] + 28)
return false;
}
return true;
}
public static String[] getYuk() {
return yuk;
}
public static String[] getGap() {
return gap;
}
public static String[] getDdi() {
return ddi;
}
public static String[] getWeek() {
return week;
}
public static void setInit_date(Date init_date) {
LunarTranser.init_date = init_date;
}
public static Date getInit_date() {
return init_date;
}
public static String getErrmsg() {
return errMsg;
}
}

그 예전 부터 지금까지 잘 사용하고 있으니, 소스의 변환 능력은 검증이 되었다고 볼 수 있을 것 같다.


 


사용하는 부분은 다음과 같다.   먼저 음력을 양력으로 변환 하기


LunarTranse(String TranseDay, boolean leapyes)

기준일자(yyyyMMdd : 8자리 숫자) 와 윤달 여부만 전달 하면  양력 으로 변환된 날자가 돌아온다.


 


다음은 양력을 음력으로 변환하기


solarTranse(String TranseDay)

기준일자(yyyyMMdd : 8자리 숫자)을 전달하면 음력으로 변환된 날자가 돌아온다.


 


이런 기능의 사용예시는 앞에서 기술한 바와 같이 앱으로 활용하고 있다.


 


https://play.google.com/store/apps/details?id=com.nari.lunar3google 



 


음력달력, 일정관리 - Google Play 앱


음력을 양력으로 변환하여 관리 합니다. 양력은 음력으로 관리 합니다.


play.google.com




 





오늘의 이야기


#스하리1000명프로젝트,
في بعض الأحيان يكون من الصعب التحدث مع العمال الأجانب، أليس كذلك؟
لقد صنعت تطبيقًا بسيطًا يساعد! أنت تكتب بلغتك، والآخرون يرون ذلك بلغتهم.
يترجم تلقائيًا بناءً على الإعدادات.
مفيد للغاية لإجراء محادثات سهلة. ألق نظرة عندما تحصل على فرصة!
https://play.google.com/store/apps/details?id=com.billcoreatech.multichat416




오늘의 이야기

이전 포스팅에서 제주버스의 정보를 수집했다. 


https://billcorea.tistory.com/111


 


이제 그 정보를 나의 앱에 넣는 작업을 해 봐야겠다.  일단은 데이터를 저장할 table 을 구성해 보았다.  뭐 말그대로 앞전 포스팅에서 작성한 class 중에서 item 이 들어 있는 class 구조를 그대로 적용해 보면 될 것 같다.


 


import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

public class DBHelper extends SQLiteOpenHelper {

private static final String DB_NAME = "opdGangDB";
private static final int DB_Ver = 4;
private static final String TAG = "DBHelper";

public DBHelper(Context context) {
super(context, DB_NAME, null, DB_Ver);
}

/**
* aClassCode, bClassCode, cClassCode : 코드 분류 1,2,3 차
* cntData : 위치별-상호별 중복 건수
* infoData : 상세 정보 y,x,place_name 으로 cntData 와 연계
* @param db
*/
@Override
public void onCreate(SQLiteDatabase db) {

StringBuffer sb = new StringBuffer();
sb.append("create table ClassCode");
sb.append("( _id integer primary key autoincrement, ");
sb.append(" classNameA text, ");
sb.append(" classNameB text, ");
sb.append(" classNameC text ");
sb.append(" ) ");
db.execSQL(sb.toString());

sb = new StringBuffer();
sb.append("create table cntData");
sb.append("( _id integer primary key autoincrement, ");
sb.append(" y text, ");
sb.append(" x text, ");
sb.append(" place_name text, ");
sb.append(" atCnt number ");
sb.append(" ) ");
db.execSQL(sb.toString());

sb = new StringBuffer();
sb.append("create table infoData");
sb.append("( _id integer primary key autoincrement, ");
sb.append(" y text, ");
sb.append(" x text, ");
sb.append(" address_name text, ");
sb.append(" category_group_code text, ");
sb.append(" category_group_name text, ");
sb.append(" category_name text, ");
sb.append(" distance text, ");
sb.append(" id text, ");
sb.append(" phone text, ");
sb.append(" place_name text, ");
sb.append(" place_url text, ");
sb.append(" road_address_name text, ");
sb.append(" url text, ");
sb.append(" regTime text, ");
sb.append(" likeCnt text, ");
sb.append(" unlikeCnt text ");
sb.append(" ) ");
db.execSQL(sb.toString());

/**
* 제주 버스 정류소 정보 수집 2021.12.20 ~
*/
sb = new StringBuffer();
sb.append("create table stationItem ");
sb.append("( _id integer primary key autoincrement, ");
sb.append(" dirTp text, ");
sb.append(" govNm text, ");
sb.append(" localX real, ");
sb.append(" localY real, ");
sb.append(" mobiNum text, ");
sb.append(" stationId text, ");
sb.append(" stationNm text, ");
sb.append(" upd text, ");
sb.append(" useYn text ");
sb.append(" ) ");
db.execSQL(sb.toString());
}

/**
* @param db
* @param oldVersion
* @param newVersion
*/

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

StringBuffer sb = new StringBuffer();

Log.e(TAG, "newVersion=" + newVersion) ;
switch (newVersion) {
case 4:

sb = new StringBuffer();
sb.append("create table stationItem ");
sb.append("( _id integer primary key autoincrement, ");
sb.append(" dirTp text, ");
sb.append(" govNm text, ");
sb.append(" localX real, ");
sb.append(" localY real, ");
sb.append(" mobiNum text, ");
sb.append(" stationId text, ");
sb.append(" stationNm text, ");
sb.append(" upd text, ");
sb.append(" useYn text ");
sb.append(" ) ");
db.execSQL(sb.toString());

break;
case 3:
db.execSQL("alter table infoData add column likeCnt text ");
db.execSQL("alter table infoData add column unlikeCnt text ");
break;
}

}
}

sqlite 에서 데이터를 구성해 앱을 만들다 보면 나중에 추가 하거나 구조를 변경 하게 되는 경우가 있는데,  그 때 마다 data을 다 지우고 다시 만들면 사용자도 힘들고, 개발자도 힘들고 ... 그래서 db을 생성할 때 버전에 따라서 변하게 하는 것이 좋을 듯 하다.  그러면 이전 앱을 설치한 사용자도 upgrade 되면 새로 데이터를 넣지 않아도 되기 때문이다. 


 


이 앱은 벌써 데이터 구조를 3, 4 버전까지 늘렸다. 2 버전은 어디 갔는 지 모르겠지만... (사실은 한번 잘못 빌드를 했다가.. 실폐를 해서 ) 아무튼 새로 버전을 받더라도 사용자는 아무런 의심 없이 사용하게 될 것이다.


 


이번에는 이 데이터를 불러서 사용할 것들을 정리해 봐야겠다.


import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import com.billcoreatech.opdgang1127.JejuBusInfo.StationItemBean;
import com.billcoreatech.opdgang1127.JejuFD6.JejuFD6infoBean;
import com.billcoreatech.opdgang1127.Utils.StringUtils;

public class DBHandler extends RuntimeException {

DBHelper helper ;
SQLiteDatabase db ;
String TAG = "DBHandler";

public DBHandler(Context context) {
helper = new DBHelper(context) ;
db = helper.getWritableDatabase() ;
}

public static DBHandler open (Context ctx) throws SQLException {
DBHandler handler = new DBHandler(ctx) ;
return handler ;
}

public void close() {
helper.close();
}

...

/**
* 제주 버스 정류소 정보 2021.12.20 ~
* @param station
* @return
*/
public long appendBusStation(StationItemBean station) {
long result = 0 ;
Cursor rs = selectBusStation(station.getStationId());
if (rs.moveToNext()) {
result = updateBusStation(station);
} else {
result = insertBusStation(station);
}
rs.close();
return result ;
}

/**
* 제주 버스 정류소 정보 2021.12.20 ~
* @param busStation
* @return
*/
public Cursor selectBusStation(String busStation) {
StringBuffer sb = new StringBuffer();
sb.append("select * from stationItem ");
if (!"".equals(busStation)) {
sb.append(" where stationId = '" + busStation.trim() + "' ");
}
return db.rawQuery(sb.toString(), null);
}

/**
* 주변 정류소 찾기
* @param x : 경도
* @param y : 위도
* @param roundValue : 주변범위 m
* @return
*/
public Cursor selectBusStationArea(double x, double y, int roundValue) {
double x1 = x - StringUtils.LongitudeInDifference(y, roundValue);
double x2 = x + StringUtils.LongitudeInDifference(y, roundValue);
double y1 = y - StringUtils.LatitudeInDifference(roundValue) ;
double y2 = y + StringUtils.LatitudeInDifference(roundValue) ;
StringBuffer sb = new StringBuffer();
sb.append("select * from stationItem ");
sb.append(" where (localX >= " + x1 + " and localX <= " + x2 + ") ");
sb.append(" and (localY >= " + y1 + " and localY <= " + y2 + ") ");
sb.append(" and useYn = 'Y' ");
Log.e(TAG, sb.toString());
return db.rawQuery(sb.toString(), null);
}

/**
* 제주 버스 정류소 정보 2021.12.20 ~
* @param station
* @return
*/
public long insertBusStation(StationItemBean station) {
ContentValues values = new ContentValues();
values.put("dirTp", station.getDirTp());
values.put("govNm", station.getGovNm());
values.put("localX", station.getLocalX());
values.put("localY", station.getLocalY());
values.put("mobiNum", station.getMobiNum());
values.put("stationId", station.getStationId());
values.put("stationNM", station.getStationNm());
values.put("upd", station.getUpd()) ;
values.put("useYn", station.getUseYn()) ;

return db.insert("stationItem", null, values) ;
}

/**
* 제주 버스 정류소 정보 2021.12.20 ~
* @param station
* @return
*/
public long updateBusStation(StationItemBean station) {
ContentValues values = new ContentValues();
values.put("dirTp", station.getDirTp());
values.put("govNm", station.getGovNm());
values.put("localX", station.getLocalX());
values.put("localY", station.getLocalY());
values.put("mobiNum", station.getMobiNum());
values.put("stationNM", station.getStationNm());
values.put("upd", station.getUpd()) ;
values.put("useYn", station.getUseYn()) ;

return db.update("stationItem", values, "stationId = '" + station.getStationId() + "' ", null) ;
}

}

이렇게 insert, update, select 을 구성했고, append 도 추가 했다. append 는 새로 데이터를 받아온 다음 그것을 저장하기 위해서, 


 


이제 데이터를 받아와서 저장하는 구현을 해 봐야지.


 


   @Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {

case R.id.actionGetBusStation:

fd6Binding = Updatejejufd6infoViewBinding.inflate(getLayoutInflater());
fd6Binding.adView.loadAd(adRequest);
AlertDialog.Builder builder1 = new AlertDialog.Builder(MainActivity.this, R.style.ThemeDialog)
.setTitle(getString(R.string.titleUpdateBusStation) + "(last:" + sp.getString("lastUpdateBusStation", "1970-01-01") + ")")
.setView(fd6Binding.getRoot())
.setPositiveButton(getString(R.string.OK), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
doGetBusStation();
}
})
.setNegativeButton(getString(R.string.CLOSE), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {

}
});
AlertDialog dialog1 = builder1.create();
dialog1.show();

break;


}
return super.onOptionsItemSelected(item);
}

option menu 를 선택 하면 아래 처럼 데이터를 수신 받고 그것을 이용해서 저장하는 for 문을 구성 하였다.


 


   /**
* 2021.12.20 제주 버스 정류소 정보 수집 기능 추가
*/
private void doGetBusStation() {

binding.baseProgressBar.setVisibility(View.VISIBLE);
retrofit = new Retrofit.Builder()
.baseUrl(RetrofitApi.baseURL)
.client(new OkHttpClient())
.addConverterFactory(SimpleXmlConverterFactory.create())
.build();
service = retrofit.create(RetrofitApi.class);
String lastUpdateDate = sp.getString("lastUpdateBusStation", "1970-01-01");
service.getStationInfo(lastUpdateDate).enqueue(new Callback<StationBean>() {
@Override
public void onResponse(Call<StationBean> call, Response<StationBean> response) {
Log.e(TAG, "response=" + response.code() + " Total=" + response.body().getBodyClass().getTotalCount()
+ "\n" + response.message());
if (!"0".equals(response.body().getBodyClass().getTotalCount())) {
dbHandler = DBHandler.open(getApplicationContext());
for (StationItemBean item : response.body().getBodyClass().getItems()) {
Log.e(TAG, item.getStationNm());
long cnt = dbHandler.appendBusStation(item);
Log.e(TAG, cnt + " " + item.getStationNm());
}
dbHandler.close();
editor = sp.edit();
editor.putString("lastUpdateBusStation", StringUtils.getToday());
editor.commit();
}
binding.baseProgressBar.setVisibility(View.GONE);
}

@Override
public void onFailure(Call<StationBean> call, Throwable t) {
Log.e(TAG, "ERROR=" + t.toString()) ;
}
});

}

 저번과 달라진 부분은 service 호출할 때 최종 변경일자를 구해서 전달하도록 수정한 부분이다. 그래서 retrofit api 에서도 다음과 같이 날자를 받아서 파라미터로 전달 하도록 구성을 변경 하였다. 


import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;

public interface RetrofitApi {
String baseURL = "http://busopen.jeju.go.kr";
String getStation = "/OpenAPI/service/bis/Station";

@GET(getStation)
Call<StationBean> getStationInfo(@Query(value="last") String lastUpdate);
}

이렇게 하면 저번 포스팅에서 보았던 제주버스api 에 최종 변경된 정보만 수집하는 모양으로 작업을 할 수 있다. 


이제 가져온 데이터가 앱에서 어떻게 활용이 되는 지 잠시 보도록 하겠다. 


 


버스 정류소가 표시된 지도



이렇게 앱에 버스 정류소를 표시할 수 있는 자료가 만들어 졌다.  다음은 뭐 해야하지 ?  내가 있는 위치에서 저 버스 정류소까지 가는 방법을 연구해 보아야 겠다.


 


그럼 오늘도 즐~ 코딩...





오늘의 이야기

앱에서 지원하고 싶은 것중 우선 나의 주변에 버스 정류소를 찾는다. 어떻게 찾을까 ?   모든 데이터는 data.go.kr 공공데이터 포털을 중심으로 ... 찾아보니 제주도에서 제공하는 버스 정보가 있다.


 


http://www.jeju.go.kr/help/open.htm?page=3&act=view&seq=967654 


다만, 가이드의 정보를 기준으로 보면 http:// 으로 시작하는 기본url 과 xml 형식으로 자료를 전송한다는 것이 조금 예전 방식인 것 같은 생각이 들었다.  


 


이제 앱에 retrofit 통신을 하기 위한 준비를 해 보자.  gradle 파일에 수정을 


    // 데이터 주고 받기
implementation 'com.squareup.retrofit2:retrofit:2.7.2'
implementation 'com.squareup.retrofit2:converter-simplexml:2.1.0'

retrofit 과 xml 파싱을 위해서 추가 했다.


 


다음은 manifest 에 internet 접속을 위한 권한 등록


 


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

이제 준비는 되었고.  그럼 데이터를 가져오기 위해서 일단 open api 가 제공하는 데이터 구조를 확인해 보아야겠다.


 


xml 샘플


xml 마지막 부분



데이터는 이렇게 조회가 되고 있고, open api 가이드의 내용도 이와 같다.  그래서 일단은 데이터를 받아올 class 을 만들어 보았다. 


 


맨 먼저 제일 바깥쪽에 구성되는 class 부터


import org.simpleframework.xml.Element;
import org.simpleframework.xml.Root;


@Root(name="response", strict = false)
public class StationBean {
@Element(name="header")
HeaderClass headerClass;
@Element(name="body")
BodyClass bodyClass ;

public HeaderClass getHeaderClass() {
return headerClass;
}

public BodyClass getBodyClass() {
return bodyClass;
}

public void setHeaderClass(HeaderClass headerClass) {
this.headerClass = headerClass;
}

public void setBodyClass(BodyClass bodyClass) {
this.bodyClass = bodyClass;
}
}

Root 는 response 로 구성 되며, 그 안에는 header 와 body 가 들어간다. 


다음은 header class


import org.simpleframework.xml.Element;
import org.simpleframework.xml.Root;

@Root(name="header", strict = false)
public class HeaderClass {
@Element(name="resultCode")
String resultCode;
@Element(name="resultMsg")
String resultMsg ;

public String getResultCode() {
return resultCode;
}

public String getResultMsg() {
return resultMsg;
}

public void setResultCode(String resultCode) {
this.resultCode = resultCode;
}

public void setResultMsg(String resultMsg) {
this.resultMsg = resultMsg;
}
}

header 에는 resultCode, 와 resultMsg 만 들어 있고, 실제 데이터는 body 에 들어간다.  다음은 body class


import org.simpleframework.xml.Element;
import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.Root;

import java.util.ArrayList;

@Root(name="body", strict = false)
public class BodyClass {
@ElementList(entry = "items")
ArrayList<ItemClass> items ;
@Element(name="numOfRows")
String numOfRows ;
@Element(name="pageNo")
String pageNo ;
@Element(name="totalCount")
String totalCount ;

public ArrayList<ItemClass> getItems() {
return items;
}

public String getNumOfRows() {
return numOfRows;
}

public String getPageNo() {
return pageNo;
}

public String getTotalCount() {
return totalCount;
}

public void setItems(ArrayList<ItemClass> items) {
this.items = items;
}

public void setNumOfRows(String numOfRows) {
this.numOfRows = numOfRows;
}

public void setPageNo(String pageNo) {
this.pageNo = pageNo;
}

public void setTotalCount(String totalCount) {
this.totalCount = totalCount;
}
}

body 에는 데이터가 들어가는 items 와 건수등을 표시하는 데이터 들이 들어 있는데, 구현하면서 조금 방황(?)한 부분은 ArrayList 로 표시하는 items 에 대한 구현을 어떻게 해야 하는 가에 대한 부분 이었다.  gson 으로 파싱을 할 때는 그런거 없이 수월했는데, xml 형식에서는 EntryList 가 표현이 되도록 구현을 해야 되는 것이었다.


 


다음은 item이 들어가는 item class 


import org.simpleframework.xml.Element;
import org.simpleframework.xml.Root;

@Root(name="item", strict = false)
public class ItemClass {
@Element(name="dirTp")
String dirTp ;
@Element(name="govNm")
String govNm;
@Element(name="localX")
double localX ;
@Element(name="localY")
double localY ;
@Element(name="mobiNum")
String mobiNum ;
@Element(name="stationId")
String stationId ;
@Element(name="stationNm")
String stationNm ;
@Element(name="upd")
String upd ;
@Element(name="useYn")
String useYn ;

public double getLocalX() {
return localX;
}

public double getLocalY() {
return localY;
}

public String getMobiNum() {
return mobiNum;
}

public String getDirTp() {
return dirTp;
}

public String getGovNm() {
return govNm;
}

public String getStationId() {
return stationId;
}

public String getStationNm() {
return stationNm;
}

public String getUpd() {
return upd;
}

public String getUseYn() {
return useYn;
}

public void setDirTp(String dirTp) {
this.dirTp = dirTp;
}

public void setGovNm(String govNm) {
this.govNm = govNm;
}

public void setLocalX(double localX) {
this.localX = localX;
}

public void setLocalY(double localY) {
this.localY = localY;
}

public void setMobiNum(String mobiNum) {
this.mobiNum = mobiNum;
}

public void setStationId(String stationId) {
this.stationId = stationId;
}

public void setStationNm(String stationNm) {
this.stationNm = stationNm;
}

public void setUpd(String upd) {
this.upd = upd;
}

public void setUseYn(String useYn) {
this.useYn = useYn;
}
}

이렇게 구현 하면 데이터를 받아올 준비는 되었다.  이제 interface 을 구현해 볼 차례 인데,    openapi 에는 호출시 파라미터를 last 와 serviceKey 를 받는다고 되어 있지만, 현재( 2021.12.19 )에는 아무것도 전달하지 않아도 되고, last 뒤에 날자만 전달해도 된다. last 을 넣어보내면 최근 업데이트 된 정보만 받아오는 것으로 확인이 되었다.  다만, 나의 앱에서는 정보가 처음이라, 전체 데이터를 받기 위해서 아무것도 파라미터로 사용하지 않다. 


 


import retrofit2.Call;
import retrofit2.http.GET;

public interface RetrofitApi {
String baseURL = "http://busopen.jeju.go.kr";
String getStation = "/OpenAPI/service/bis/Station";

@GET(getStation)
Call<StationBean> getStationInfo();
}

 interface class 는 이렇게 구성을 했다. query 가 없어서 아무것도 전달하지는 않았다.


이번에는 activity 에서 호출을 해 보도록 하겠다.


 


public class MainActivity extends AppCompatActivity {

...

Retrofit retrofit ;
RetrofitApi service ;

...


private void doGetBusStation() {

retrofit = new Retrofit.Builder()
.baseUrl(RetrofitApi.baseURL)
.client(new OkHttpClient())
.addConverterFactory(SimpleXmlConverterFactory.create())
.build();
service = retrofit.create(RetrofitApi.class);

service.getStationInfo().enqueue(new Callback<StationBean>() {
@Override
public void onResponse(Call<StationBean> call, Response<StationBean> response) {
Log.e(TAG, "response=" + response.code() + " Total=" + response.body().getBodyClass().getTotalCount());
for(ItemClass item : response.body().getBodyClass().getItems()) {
Log.e(TAG, item.getStationNm());
}
}

@Override
public void onFailure(Call<StationBean> call, Throwable t) {
Log.e(TAG, "ERROR=" + t.toString()) ;
}
});

}


...


}

아직 데이터를 받아와서 어떻게 하고자 하는 지 결정을 하지 못했다. 일단, 데이터가 들어 오는 것은 확인을 하였다.  이 구현을 하는데,  CLEARTEXT communication to XXXX not permitted by network security policy 을 만나게 되면, 어떻게 할 것인가를 고민하지 말고 다음을 따라해 보면 된다. 이유는 http:// 으로 된 주소에 통신을 시도하기 때문에 발생하는 보안 문제인데,  나 앱이 모든 호출에서 http:// 로 할 것이 아니기 때문에 특정 url 만 http 로 호출할 것이라고 등록을 해 주면 된다. 


 


먼저 xml 파일을 하나 만든다.  res/xml/network_config.xml 이라고 .. 그안에는 다음과 같이 넣어 준다. 


<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">busopen.jeju.go.kr</domain>
</domain-config>
</network-security-config>

특정 주소는 http 로 통신을 하겠다는 것이다.  보안정책 때문에 누군가 싫어할지 모르지만.


그리고 다음은 manifest 에 다음을 추가해 준다. 


 


    <application
android:allowBackup="true"
android:icon="@mipmap/ic_info_logo"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_info_logo_round"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_config"

networkSecurityConfig 을 추가해 주면 끝.


 


응답 로그



제주도에는 4358개이 정류소 정보가 있나 보다.  다음엔 이것을 저장해서 데이터로 활용해 보도록 하겠다.


 





오늘의 이야기


#billcorea #운동동아리관리앱
🏸 شنيدل، تطبيق ضروري لأندية كرة الريشة!
👉 مباراة اللعب - سجل النتائج وابحث عن المعارضين 🎉
مثالي لأي مكان، بمفردك، مع الأصدقاء، أو في النادي! 🤝
إذا كنت تحب كرة الريشة، جربها بالتأكيد

اذهب إلى التطبيق 👉 https://play.google.com/store/apps/details?id=com.billcorea.matchplay




오늘의 이야기

배경화면 이미지 이렇게 눈이 온걸 본적은 모니터 화면에 나오는 설경이 아름답다.   이 겨울도 이렇게 눈 구경 한번 제대로 못하고 지나갈까? 이 겨울엔 한 번쯤 꼭 눈 구경하러 가야겠다.   낼모레가 크리스마스라는데... 나이를 먹어서 인가? 그다지 ...