안드로이드 스튜디오를 이용해 개발을 진행할 때 Log.d()를 이용해 값을 확인할 때 Logcat창을 확인하는 것이 일반적인데 run app을 하게 되면 Run창이 디폴트로 설정되어 있어 필요한 Logcat창이 아닌 Run창이 자동적으로 보여 불편함을 겪었다. 그래서 안드로이드 스튜디오 설정을 통해 이 디폴트 값을 Logcat창으로 바꾸는 과정을 알아보았다.
설정 방법
일단 공식 문서에도 설정 방법이 나와있고 방법은 굉장히 간단하다. 필자는 이 문서를 참고했다.
KAKAO API를 사용해 간단한 앱을 만들던 와중 Git에 올렸을 때의 API 키가 공개되는 것에 대한 문제를 직면하게 되었다. 사실 로컬에서 테스트를 하고 개발을 할 때에는 큰 상관은 없지만 Git에 코드 업로드, 베포 시에는 문제가 되기 때문에 이 API 키를 숨기는 것에 대해 알아보았다.
키를 숨기는 이유
키를 숨기는 이유는 여러가지 이유가 있다.
1. 보안
API 키를 외부에 노출시키면 누구나 해당 API를 사용할 수 있다.
API 키가 외부에 노출되면 악의적인 공격이나 남용의 발생할 수 있다. 그렇기 때문에 API 키를 안전하게 보호하여 API를 사용하는 앱 또는 서비스의 보안을 유지해야 된다.
2. 개인 정보 보호
API 중 사용자 데이터에 접근하는 권한이 있을 수 있다. 이러한 경우 키가 노출되면 사용자의 개인 정보가 유출될 수 있다.
이것은 개인 정보 보호 법규에 위배될 수 있다.
3. 과금 유발
API를 사용하는 것에 있어서 일정량 사용량 이상을 사용하게 되면 과금이 발생할 수 있는데 API 키가 공개되면 이 점을 이용해 무단으로 API 서비스를 이용하고 과금은 API 키를 공개한 사람이 지불하게 되는 불상사가 발생할 수 있다.
이러한 이유들로 인해 API 키는 외부에 공개되지 않는 것이 원칙이다.
방법
안드로이드 스튜디오에서 API 키를 숨기는 방법은 여러가지가 있는데 그 중 2가지 방법을 소개해보려고 한다.
1. gitignore를 이용한 방법
먼저 API 키를 저장할 클래스 생성
class Constants {
companion object{
const val BASE_URL = "https://dapi.kakao.com"
// 개인 API 사용
const val AUTH_HEADER = "KakaoAK $REST_API_KEY 입력" <- 에 개인이 부여받은 API 키입력
}
}
.gitignore
프로젝트의 루트 디렉토리에서 맥 기준 cmd + shift + . 키를 입력해 숨김 파일을 확인한 후
.gitignore 파일을 편집
.gitignore 파일
맨 밑줄에 해당 파일 추가 시 git에 push 시 해당 파일 추적을 멈춘다.
2. local.properties와 BuildConfig 사용
필자는 이 방법을 사용해 진행하였다.
먼저 앞서 올린 gitignore를 자세히 보면 local.properties라는 파일이 존재한다.
local.properties은 기본적으로 git이 추적하지 않기때문에 따로 gitignore 파일을 설정할 필요가 없다.
이 파일을 이용해 키 값을 숨길 수 있다.
먼저 안드로이드 스튜디오에서 local.properties 파일을 찾는다. 파일은 다음과 같은 경로에 위치한다.
local.properties 파일 위치
이 파일을 들어가서 API 키를 추가한다.
local.properties 파일
여기서 API 키를 설정한다.
그 후 build.gradle에 들어가 몇가지 수정을 해준다.
먼저 local.properties의 값을 불러오기 위한 설정을 추가해준다.
// local.properties의 값을 가져오기 위한 설정
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
그리고 buildConfig를 사용하기 위한 설정
// BuildConfig 사용을 위한 설정
defaultConfig {
...
// BuildConfig에 값 추가
buildConfigField "String", "KAKAO_REST_API_KEY", properties["api_key"]
}
buildFeatures {
// BuildConfig 사용 설정
buildConfig = true
}
...
}
숫자나라 기사단의 각 기사에게는 1번부터 number까지 번호가 지정되어 있습니다. 기사들은 무기점에서 무기를 구매하려고 합니다.
각 기사는 자신의 기사 번호의 약수 개수에 해당하는 공격력을 가진 무기를 구매하려 합니다. 단, 이웃나라와의 협약에 의해 공격력의 제한수치를 정하고, 제한수치보다 큰 공격력을 가진 무기를 구매해야 하는 기사는 협약기관에서 정한 공격력을 가지는 무기를 구매해야 합니다.
예를 들어, 15번으로 지정된 기사단원은 15의 약수가 1, 3, 5, 15로 4개 이므로, 공격력이 4인 무기를 구매합니다. 만약, 이웃나라와의 협약으로 정해진 공격력의 제한수치가 3이고 제한수치를 초과한 기사가 사용할 무기의 공격력이 2라면, 15번으로 지정된 기사단원은 무기점에서 공격력이 2인 무기를 구매합니다. 무기를 만들 때, 무기의 공격력 1당 1kg의 철이 필요합니다. 그래서 무기점에서 무기를 모두 만들기 위해 필요한 철의 무게를 미리 계산하려 합니다.
기사단원의 수를 나타내는 정수 number와 이웃나라와 협약으로 정해진 공격력의 제한수치를 나타내는 정수 limit와 제한수치를 초과한 기사가 사용할 무기의 공격력을 나타내는 정수 power가 주어졌을 때, 무기점의 주인이 무기를 모두 만들기 위해 필요한 철의 무게를 return 하는 solution 함수를 완성하시오.
제한 사항
1 ≤ number ≤ 100,000
2 ≤ limit ≤ 100
1 ≤ power ≤ limit
해답
class Solution_기사단원의_무기 {
fun solution(number: Int, limit: Int, power: Int): Int {
var answer = 0
// 약수 개수를 담은 배열...
val cnt = ArrayList<Int>()
for (i in 1..number) {
var divisorCount = 0
var j = 1
// 약수 개수 구하기...
while (j * j <= i) {
if (j * j == i) divisorCount++
else if (i % j == 0) divisorCount += 2
j++
}
cnt.add(divisorCount)
}
for (i in cnt.indices) {
if (cnt[i] > limit) {
cnt[i] = power
answer += cnt[i]
} else {
answer += cnt[i]
}
}
return answer
}
}
처음에는 약수 개수를 구하는 방법을 단순히 i까지 반복하는 방식으로 구현했다. 하지만 이 방법을 사용했을 시 시간 초과가 발생했다.
// O(N) 시간 초과 코드...
while (j <= i) {
if (i % j == 0) divisorCount++
j++
}
위 코드를 사용 시 해당 구문의 시간 복잡도는 O(N)이 나온다... 구글링을 통해 더효율 좋은코드를검색해 본결과 O(N^1/2)에 효율이 나오는 방법을 찾을 수 있었다.
// O(N^1/2)
while (j * j <= i) {
if (j * j == i) divisorCount++
else if (i % j == 0) divisorCount += 2
j++
}
O(N^1/2)에 시간 복잡도를 가지는 위의 코드는 약수의 개수는 제곱근을 제외하곤 짝을 이룬다는 성질을 이용해 구현한 코드이다. 따라서 기존의 코드의 절반의 반복을 통해 같은결괏값을얻을 수 있었다.
총평
알고리즘을 구현하는 것에 대해 시간 복잡도의 개념을 알고 있었지만 시간 복잡도가 실제 코드의 성능과 직접적인 연관이 있다는 사실을 체감할 수 있는 문제였다. 다음 문제를 풀 때는 코드의 효율성에 대해 고려를 하면서진행해야겠다.
Shazam의 기본 페이지를 따라 만드는 도중 GridView 자식 위젯의 bottom overflow 발생...
overflow 발생
자식 위젯인 Container에서 height 설정, GridView의 shrinkWrap 설정 등 최대한 아는 다양한 방법을 시도해봤지만 실패...
2. 해결 방안
Expanded(
child: GridView.count(
// crossAxisCount is the number of columns
crossAxisCount: 2,
// <<해결 방안>>
childAspectRatio: 0.75, // 자식의 비율 설정...
// This creates two columns with two items in each column
children: List.generate(songs.length, (index) {
그러다 찾은 해결 방안은 GridView.count의 설정인 childAspectRatio의 값을 설정해주는 것
childAspectRatio 옵션은 말 그대로 GridView의 자식 위젯의 비율을 정하는 옵션입니다.
저의 경우 자식 위젯이 bottom overflow가 발생했음으로 세로방향의 길이를 늘려야했습니다. 따라서 해당 옵션의 값을 1보다 작게 줘 길이를 세로로 늘렸습니다.
// 프레임 생성
JFrame frame = new JFrame("Pokemon");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 프레임 크기 설정
frame.setSize(400, 400);
frame.setLocationRelativeTo(null); // 프레임을 화면 가운데에 배치
frame.setResizable(false); // 프레임 창 크기 고정(false)
// 프레임 표시
frame.setVisible(true);
프로그램의 기본적인 프레임을 생성해 줍니다. 추후에 이 프레임 안에 버튼, 메시지 창 등을 추가하게 됩니다.
프로그램 실행 시 화면 가운데 위치하는 것을 원했기 때문에 setLocationRelativeTo() 함수를 이용해 실행 시 바로 화면 중앙에서 실행되게 설정했습니다.
또한 프로그램의 창크기를 고정하기 위해 setResizable() 함수를 이용해 창크기의 변경을 막아놨습니다.
2. 패널 생성 및 설정
// 이미지 라벨 패널 생성
JPanel imgLabelPanel = new JPanel();
imgLabelPanel.setLayout(new FlowLayout());
imgLabelPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
// 버튼 패널 생성
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new FlowLayout());
// 대화창 패널 생성
JPanel labelPanel = new JPanel();
labelPanel.setLayout(new BorderLayout());
labelPanel.setBackground(Color.WHITE);
labelPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY));
labelPanel.setBorder(BorderFactory.createStrokeBorder(new BasicStroke(2.0f)));
프레임 안에서 각각의 종류가 다른 컴포넌트를 넣기 위한 패널을 생성합니다. 각각의 패널에 대한 정보도 설정해 줍니다.
대화창의 패널은 레이아웃을 BorderLayout으로 설정했습니다. 이는 대화창에서의 메시지를 가운데의 띄우기 위한 선행작업입니다.
3.버튼, 라벨, 이미지 라벨 생성 및 설정
먼저 사용할 이미지를 찾아 해당 프로젝트 내에 이미지 파일을 넣습니다. 저는 따로 images라는 폴더를 만들어 이미지를 추가해 줬습니다.
images 폴더 내 이미지 추가
그러고 나서 이미지를 불러오는 코드를 작성해 줍니다.
// 이미지 로드
ImageIcon image = null;
try {
image = new ImageIcon(ImageIO.read(main.class.getResource("images/pichu.png")));
} catch (IOException e) {
e.printStackTrace();
}
해당 코드로 이미지를 불러오게 됩니다.
이후 이미지가 들어갈 라벨, 대화창에 쓰일 라벨 그리고 상호작용 가능한 버튼들을 생성합니다.
// 이미지를 표시할 라벨 생성
JLabel imageLabel = new JLabel(image);
imageLabel.setPreferredSize(new Dimension(200, 200));
// 흰색 배경의 라벨 생성
JLabel backgroundLabel = new JLabel();
backgroundLabel.setOpaque(true);
backgroundLabel.setBackground(Color.WHITE);
// 라벨 생성
JLabel label = new JLabel("<html><body style='text-align:center;'>오잉??<br> 피츄의 상태가...?</html>");
label.setHorizontalAlignment(JLabel.CENTER);
// 버튼 생성
JButton OKBtn = new JButton("진화");
Dimension OKBtn_buttonSize = new Dimension(50, 30);
OKBtn.setPreferredSize(OKBtn_buttonSize);
JButton NoBtn = new JButton("취소");
Dimension NoBtn_buttonSize = new Dimension(50, 30);
NoBtn.setPreferredSize(NoBtn_buttonSize);
JButton resetBtn = new JButton("초기화");
Dimension resetBtn_buttonSize = new Dimension(70, 30);
resetBtn.setPreferredSize(resetBtn_buttonSize);
resetBtn.setEnabled(false);
버튼과 라벨을 만들고 버튼의 텍스트, 크기 배경 색깔 등의 세부 설정을 해줍니다. 중간에 대화창에 쓰일 lable은 텍스트의 중앙 정렬, 줄 넘김 등을 사용하기 위해 html 문법을 사용했습니다.
4. 패널에 컴포넌트 추가
// 버튼 패널에 버튼 추가
buttonPanel.add(OKBtn);
buttonPanel.add(NoBtn);
buttonPanel.add(resetBtn);
// 이미지 패널에 이미지 추가
imgLabelPanel.add(imageLabel);
// 대화 패널에 라벨 추가
labelPanel.add(label, BorderLayout.CENTER);
// 프레임에 추가
frame.setLayout(new BorderLayout());
frame.add(imgLabelPanel, BorderLayout.NORTH); // 이미지 패널 추가
frame.add(labelPanel, BorderLayout.CENTER); // 대화 패널 추가
frame.add(buttonPanel, BorderLayout.SOUTH); // 버튼 패널 추가
앞서 만든 패널에 해당하는 컴포넌트들을 추가해 줍니다.
BorderLayout의 특징을 이용해 각각의 패널이 프레임 어느 곳에 위치할지 특정해 줍니다.