일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 오블완
- 아이패드다이어리
- 42seoul
- DBMS
- 프로그래밍언어론
- IOS
- 다이어리
- CD
- 스프링
- 리눅스
- CI
- 인공지능
- 소켓
- springboot
- JPA
- 티스토리챌린지
- Spring
- jenkins
- 오라클
- javascript
- swift
- Xcode
- sql
- AI
- libasm
- MySQL
- 데이터베이스
- 스프링부트
- 네트워크
- 스프링부트 웹 소켓
- Today
- Total
Hi yoahn 개발블로그
[모바일SW] 기말 정리1 본문
** java code에 MainActivity가 ListActivity를 상속받는 경우 xml 에서 ListView의 id ="@android:id/list" 로 해야한다
1. 인텐트
애플리케이션
- 대부분 하나 이상의 애플리케이션 컴포넌트로 구성
- 하나의 컴포넌트에서 다른 컴포넌트를 호출하려면 "다른 컴포넌트를 호출하고 싶다"는 의사표현 필요
- 의사표현의 수단 = 인텐트 객체
1.1 인텐트 (=의도)
- 2개 이상의 애플리케이션 컴포넌트를 연결시키는 메신저
(컴포넌트를 상호 연결하기 위한 메시지) - 경계 없는 애플리케이션 컴포넌트를 상호 연결해주고, (액티비티, 서비스, 방송수신자)3가지 애플리케이션 컴포넌트를 활성화시키는 메시지
- 인텐트를 사용하려면 자바 파일이나 액티비티를 프로젝트에 추가
- 인텐트는 다른 애플리케이션의 컴포넌트 호출 가능.
일부 애플리케이션은 권한이 부여되어야 컴포넌트 실행 가능 -> 안드로이드 매니페스트 파일에 퍼미션 추가 필요 - 메시지 = 안드로이드의 애플리케이션 컴포넌트 사이에 통신 수단으로 동작
- 메시지 자체가 작업을 할 수 있는 것이 아니라 단지 수단이다.
- 메시지를 읽고 작업을 수행하는 것은 애플리케이션 컴포넌트
애플리케이션 컴포넌트
- 액티비티, 서비스, 컨텐트 공급자, 방송수신자
- 컴포넌트들은 애플리케이션에서 느슨하게 결합 (약한 결합)
- 결합된 컴포넌트의 내역은 안드로이드 매니페스트 파일에 포함
- 느슨한 결합 => 다른 시스템과 차별화할 수 있는 안드로이드 시스템의 중요한 특징
- 비록 애플리케이션끼리 철저하게 격리되지만 권한만 있다면 안드로이드는 하나의 애플리케이션에 포함된 컴포넌트를 다른 애플리케이션의 컴포넌트로 결합 허용
인텐트 생성
- public Intent(Context, Class<?> cls)
new Intent(this, SubActivity.class)
Context 에서 SubActivity를 부르기 위한 Intent 생성 (명시적인 인텐트) - Intent()
-> 메소드의 매개변수로 필요한 걸 전달해야 함 - Intent(Intent o)
- Intent(String action)
- Intent(String action, Uri uri)
- Intent(Context context, Class<?> cls)
- Intent(String action, Uri uri, Context context, Class<?> cls)
ex)
Intent intent = new Intent(Caller.this, Callee.class);
intent.putExtra("arg", "test");
Caller 액티비티가 Callee 액티비티를 호출하는 메시지를 가진 인텐트 객체를 생성한다.
그리고 "arg"라는 키에 "test"라는 값을 가진 엑스트라를 인텐트에 추가한다.
Extra:
- 서브루틴을 호출할 때 루틴 내에 매개변수를 사용할 수 있음 -> 매개변수에 대응하는 인수를 보내야 함
- 인수가 필요할 때 사용하는 것
- 추가적인 데이터를 인텐트에 함께 보내고자 할 때 사용
- Extras: 엑스트라를 묶어놓은 것
전달 방법
startActivity(intent), startActivityForResult(), startService()등을 호출
Intent1Demo
MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn = findViewById(R.id.btn);
btn.setOnClickListener(v -> {
Intent intent = new Intent(this, SubActivity.class);
startActivity(intent);
});
}
}
MainActivity에서 특정 버튼 리스너에 Intent 객체를 생성한 후 startActivity를 담아
SubActivity
public class SubActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sub);
findViewById(R.id.btn).setOnClickListener(view -> {
finish();
});
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn"
android:text="INVOKE CALLEE"/>
</LinearLayout>
activity_sub.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="callee: exit from here?"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="EXIT"
android:id="@+id/btn"/>
</LinearLayout>
1.2 인텐트 필터
- 인텐트는 다양한 형태로 존재
- 메시지에 인텐트를 찾아야 할 대상 컴포넌트가 지정되어있지 않다면 안드로이드는 적절한 대상 컴포넌트를 찾아 활성화
- 모든 애플리케이션은 안드로이드 매니페스트 파일의 인텐트 필터에 잠재적인 대상을 표시
- 명시적 인텐트만 있다면 필요 없는 기능이지만, 암시적 인텐트는 홍보를 해야 인텐트가 들어오면 처리 가능
- 인텐트 필터 = 컴포넌트가 처리할 수 있는 인텐트를 적어놓은 것 (홍보)
- 대상이 여러개인 경우 사용자가 선택할 수 있다.
- 안드로이드 시스템 내부에서는 애플리케이션 컴포넌트에 의하여 다양한 인텐트 발생 가능
- 애플리케이션 컴포넌트는 자신들이 할 수 있는 일을 시스템에게 알리기 위하여 하나 이상의 인텐트 필터를 사용
- 특정 인텐트를 수신하고자 하는 컴포넌트는 인텐트 필터를 사용하여 자신이 받고 싶은 인텐트 메시지를 정의
1.3 인텐트 동작 원리
인텐트 라우팅(intent routing) 혹은 인텐트 확정(intent resolving)
- 특정 인텐트가 주어지면 해당 인텐트의 의도에 가장 잘 부합하는 인텐트 필터를 가진 단 하나의 컴포넌트를 찾아주는 과정
패키지 관리자
- 애플리케이션 컴포넌트의 인텐트 필터를 분석하여 수신하고자 하는 메시지의 내용을 수집하여 관리
- 분석한 필터의 내용을 바탕으로 최적의 액티비티를 찾아 액티비티 관리자에게 통보
액티비티 관리자
- 수신한 인텐트에 가장 잘 부합하는 액티비티를 찾기 위하여 패키지 관리자에게 문의
- 인텐트를 수신해야 할 대상 액티비티를 활성화
액티비티 활성화
- 인텐트 메시지는 컴포넌트를 늦은 실행시간 바인딩을 수행하는 도구
- 컴포넌트의 종류에 따라 인텐트를 전달하는 별도의 매커니즘 사용
- 액티비티를 위한 매커니즘
- public abstract void startActivity(Intent intent)
- public void startActivityForResult(Intent intent, int requestCode)
- 호출된 대상 Activity의 호출한 액티비티 확인
(자신을 호출한 액티비티 확인)
- public Intent getIntent()
- 결과 값이 없는 경우
startActivity(); - 결과 값이 있는 경우
startActivityForResult( ); 로 다른 액티비티 호출 (-> onActivityResult() 로 넘어감)
- setResult(resultCode, Intent);
- finish
1.4 인텐트 객체
인텐트 객체의 의미
- 정보의 묶음
- 인텐트 객체는 인텐트를 수신하는 컴포넌트에게 관심있는 정보의 묶음
- 인텐트 객체 포함 정보
- 수행되어야 할 액션, 처리할 데이터, 컴포넌트의 범주
- 대개 액션과 데이터 정보로 충분하다. 경우에 따라 더 정확하고 상세한 처리를 위하여 추가적인 정보가 필요함
인텐트 객체에 포함되는 것
- 컴포넌트 이름
- 선택사항이다.
이게 포함되면 명시적인 인텐트가 되며 연결할 컴포넌트가 정해져 있는 것이다. - 명시적으로 지정되면, 인텐트 객체가 지정된 클래스의 객체로 전달
- 컴포넌트 이름이 있다면 호출 대상 컴포넌트가 고정됨. 인텐트 객체의 다른 정보는 별 의미 없어짐.
- 동일한 애플리케이션 내의 서브 액티비티를 호출할 때 주로 사용
- 다른 애플리케이션의 액티비티도 권한만 있다면 명시적으로 호출 가능
- 컴포넌트 이름이 명시적으로 지정되지 않았을 경우
- 주로 다른 애플리케이션에 속한 컴포넌트를 호출할 때 사용
- 안드로이드는 인텐트 객체의 다른 정보를 사용하여 가장 적합한 대상 컴포넌트를 탐색.
즉, 안드로이드는 모든 애플리케이션의 매니페스트 파일에 있는 인텐트 필터를 참조
- 선택사항이다.
- 액션
대상 컴포넌트 액션을 위한 상수 의미 액티비티 ACTION_CALL 통화 시작 ACTION_DIAL 전화 걸기 ACTION_EDIT 데이터를 편집하기 위해 표시 ACTION_MAIN 태스크를 위한 초기 액티비티 시작, 입출력을 위한 데이터 없음 ACTION_PICK 데이터로부터 한 항목을 선택하기 ACTION_VIEW 보여주기 ACTION_SYNC 서버와 모바일 단말기의 데이터 동기화 ACTION_SEARCH 검색이 되는 앱들을 띄워줌 방송 수신자 ACTION_BATTERY_LOW 배터리 부족 ACTION_HEADSET_PLUG 헤드셋 장비 접속/해제 ACTION_SCREEN_ON 화면 켜짐 ACTION_TIMEZONE_CHANGED 시간대 변경 - 데이터
- 별도의 데이터가 필요 없는 액션도 있지만 많은 액션이, 대응하는 데이터 명세를 가짐
- 액션이 ACTION_CALL 혹은 ACTION_DIAL 이라면 데이터 필드는 통화하고자 하는 전화번호인 "tel:" 이라는 접두어가 있는 URI
- 액션이 ACTION_EDIT 이라면 데이터 필드는 편집을 위해 보여질 문서에 대한 URI
- 액션이 ACTION_VIEW 이라면 데이터 필드는 http:를 포함한 URI
- 데이터를 처리할 컴포넌트를 인텐트와 매치할 때, URI 외에 데이터의 형식을 알 필요가 있다.
- 별도의 데이터가 필요 없는 액션도 있지만 많은 액션이, 대응하는 데이터 명세를 가짐
- 범주
- CATEGORY_BROWSABLE
이미지 혹은 이메일과 같은 링크에 의하여 참조되는 데이터를 표시하기 위해 브라우저에 의해 대상 액티비티를 안전하게 호출할 수 있다. - CATEGORY_DEFAULT
- CATEGORY_GADGET
액티비티는 가젯을 보유하고 있는 다른 액티비티에 내장될 수 있다. - CATEGORY_HOME
단말기를 켤 때나 HOME 키를 눌렀을 때 액티비티가 홈 화면을 보여준다 - CATEGORY_LAUNCHER
액티비티는 태스크의 시작 액티비티가 될 수 있으며 애플리케이션 런처에 표시된다 - CATEGORY_PREFERENCE
대상 액티비티가 설정 화면이다.
- CATEGORY_BROWSABLE
- 엑스트라
- 인텐트를 처리할 컴포넌트에 전달되어야 할 추가적인 정보로써 키와 값의 쌍으로 구성
- 주로 액티비티 사이에 매개변수와 반환 값을 전달하는 도구로 사용
- 정보의 저장
- public Intent putExtra(String name, int value)
- public Intent putExtras(Bundle extras)
전달할 값이 있으면 putExtra로 저장후 intent 객체를 setResult(RESULT_OK, intent); 로 추가해서 전달
- 저장된 값을 읽기
- public <type> get<Type>Extra ( String name, <type> defaultValue)
defaultValue => key 에 해당하는 name에 대응되는 값이 존재하지 않으면 defaultValue를 읽음 - public String getStringExtra( String name )
- public Bundle getExtras( )
(putExtras(extras)로 넣은 것을 getExtras()로 읽어옴)
- public <type> get<Type>Extra ( String name, <type> defaultValue)
- Bundle: 번들을 위한 메소드
- public <type> get<Type>(String key)
- public <type> get<Type>(String key, <type> defaultValue)
- public void put<Type>(String key, <type> value)
Intent i = new Intent(); i.putExtra("TEST", "Test String"); // 인텐트를 받은 다른 컴포넌트가 Extras 데이터를 받아옴 String str = getIntent().getExtras().getString("TEST");
1.5 인텐트 종류
명시적 인텐트
- Explicit Intent
- 호출 대상 컴포넌트의 이름이 명시된 경우
- 메세지 자체에 해당 메시지를 전달해야 하는 수신처가 명확하게 명시된 경우
암시적 인텐트
- Implicit Intent
- 호출 대상 컴포넌트의 특성만 나열된 경우
- 주어진 단서(Action/Data/Category)를 기반으로 가장 적합한 하나의 컴포넌트를 찾아내야 하는 경우
- 라우팅 규칙이 있음. 예를 들어, 인텐트에 액션 값이 명시되어 있다면 인텐트 필터에 해당 액션 값이 명시되어야 함
Intent2Demo
MainActivity
public class MainActivity extends AppCompatActivity {
LinearLayout ll;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ll = findViewById(R.id.ll);
Button btn = findViewById(R.id.btn);
btn.setOnClickListener(view -> {
Intent i = new Intent(this, SubActivity.class);
startActivityForResult(i, 1); #requestCode 가 RESULT_OK 면 안됨
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1 && resultCode == RESULT_OK) {
ll.setBackgroundColor(data.getIntExtra("color", Color.WHITE));
}
}
// public void onClick(View v) {
// Intent i = new Intent(this, SubActivity.class);
// startActivityForResult(i, 1);
// }
}
- requestCode => RESULT_OK 면 안됨
SubActivity
public class SubActivity extends AppCompatActivity {
int color;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sub);
((RadioGroup)findViewById(R.id.group)).setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup radioGroup, int i) {
switch (i) {
case R.id.red:
color = Color.RED;
break;
case R.id.green:
color = Color.GREEN;
break;
case R.id.blue:
color = Color.BLUE;
}
System.out.println(color);
}
});
Button btn = findViewById(R.id.exit);
btn.setOnClickListener(view -> {
Intent i = new Intent();
i.putExtra("color", color);
setResult(RESULT_OK, i);
finish();
});
}
// public void onClick(View v) {
// Intent i = new Intent();
// i.putExtra("color", color);
// setResult(RESULT_OK, i);
// finish();
// }
}
activity_main
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:id="@+id/ll">
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="배경색 바꾸기"/>
</LinearLayout>
activity_sub
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/exit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="적용하기"
android:onClick="onClick"/>
<RadioGroup
android:id="@+id/group"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RadioButton
android:id="@+id/red"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="빨간색"/>
<RadioButton
android:id="@+id/green"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="초록색"/>
<RadioButton
android:id="@+id/blue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="파란색"/>
</RadioGroup>
</LinearLayout>
Intent3Demo
MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button call = findViewById(R.id.call);
Button search = findViewById(R.id.search);
call.setOnClickListener(view -> {
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:01012341234"));
startActivity(intent);
});
search.setOnClickListener(view -> {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://google.com"));
startActivity(intent);
});
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<Button
android:id="@+id/call"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="전화 걸기" />
<Button
android:id="@+id/search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="구글 검색"/>
</LinearLayout>
2. 데이터베이스
- 안드로이드는 SQLite라는 내장형 데이터베이스를 제공
- 파일과 마찬가지로 DB는 기본적으로 생성된 애플리케이션에 종속
(외부에 공개하려면 contents provider 사용) - 가벼운 파일 기반인 SQLite DB는 내장형 디바이스에 적합
- SQLite DB는 DB의 생성 및 오픈과 버전 관리를 담당하는 SQLiteOpenHelper 클래스와 DB에 대한 데이터의 추가/삭제/수정 및 질의 작업을 담당하는 SQLiteDatabase 클래스를 제공
**
1. SQLiteOpenHelper
2. SQLiteDatabase
3. ContentValues : DB 접근에 사용
4. Cursor : 질의 결과를 Application 으로 반환하는 타입
**
DB adapter의 구성
- 일반적으로 생성자
- SQLiteOpenHelper 클래스를 확장한 내부 클래스
- DB의 열기 및 닫기 메소드
- 레코드 추가 / 삭제 / 수정 및 질의 작업을 수행하는 메소드
SQLiteOpenHelper 클래스의 확장
- SQLiteOpenHelper 클래스를 상속받아 생성자 구현
(생성자에서 DB 생성) - 상속된 2개의 추상 메소드 구현
- public abstract void onCreate(SQLiteDatabase db)
(테이블 생성, 데이터 추가 등 작업) - public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
(버전관리)
- public abstract void onCreate(SQLiteDatabase db)
2.1 레코드 접근
데이터베이스의 목적
- 레코드 추가 / 삭제 수정
- 질의한 결과(레코드 내용)를 화면에 출력
ContentValues & Cursor
1 ) ContentValues
레코드의 추가 및 갱신
- ContentValues는 레코드의 변경할 필드 값을 준비하기 위해 사용하는 클래스
- ContentValues의 빈 객체를 생성한 후, put() 메소드들을 사용하여 레코드를 추가 / 수정
- public void put ( String key, Integer value)
- public void put ( String key, byte[] value)
- public void put ( String key, String value)
- 메소드
- public long insert( String tablename, String nullColumnHack, ContentValues values)
- public int update( String tablename, ContentValues values, String whereClause, String[] whereArgs)
- public int delete(String tablename, String whereClause, String[] whereArgs)
레코드의 질의
- 커서 객체는 질의에 의하여 반환
- 결과의 레코드 집합에 대해 무작위로 접근한다.
- 메소드
- public Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)
- Cursor 인터페이스가 무작위 접근을 위해 제공하는 메소드
- public abstract int getColumnCount( )
- public abstract int getCount( )
- public abstract int getInt(int columnIndex)
- public abstract int getPosition( )
- public abstract String getString( )
- public abstract boolean moveToFirst( )
- public abstract boolean moveToLast( )
- public abstract boolean moveToNext( )
커서와 관련된 adapter
- SimpleCursorAdapter, (ArrayAdapter도 사용 가능)
- 커서를 adapter에 바인딩 한 후 adapter 뷰를 통하여 출력 가능
- 안드로이드는 커서가 지정하는 열 혹은 필드를 텍스트뷰와 같은 위젯에 매칭하는 SimpleCursorAdapter제공
- 생성자
- public SimpleCursorAdapter( Context context, int layout, Cursor c, String[] from, int[] to)
- but, 폐기중 -> ArrayAdapter로 대체
레코드 접근
* 열의 인덱스를 셀 때 RecNo(DB 번호)는 세지 않음 *
- cursor.moveToFirst( ), cursor.getInt( _index)
- DB에 정의된 필드 타입에 따라 getInt(_index), getString(_index ) 등으로 값을 가져옴
(매개변수 = index) 위치에 있는 값을 가져옴 - cursor.moveToFirst( ), cursor.getColumnIndex("_id") => 0, cursor.getColumnIndex("email") => 3
- cursor.getColumnIndex(String name)
-> name 필드에 위치하는 인덱스를 얻음 - cursor.getPosition( )
현재 Cursor가 참조하고 있는 행의 인덱스를 얻어올 수 있음 - cursor.getColumnName(_index)
현재 필드의 이름을 얻어올 수 있음
- cursor.getColumnName(0) -> "_id"
- cursor.getColumnName(3) -> "email"
예제
MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FruitsOpenHelper helper = new FruitsOpenHelper(this);
SQLiteDatabase db = helper.getReadableDatabase();
Cursor c = db.rawQuery("select * from fruits;", null);
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_2, c,
new String[]{"name", "price"},
new int[]{android.R.id.text1, android.R.id.text2}, 0);
ListView list = findViewById(R.id.list);
list.setAdapter(adapter);
}
}
FruitsOpenHelper
public class FruitsOpenHelper extends SQLiteOpenHelper {
public FruitsOpenHelper(@Nullable Context context) {
super(context, "fruits.db", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table fruits ( _id integer primary key autoincrement," +
"name text, price integer);");
db.execSQL("insert into fruits values(null, '사과', 1500);");
db.execSQL("insert into fruits values(null, '바나나', 1000);");
db.execSQL("insert into fruits values(null, '오렌지', 2000);");
}
@Override
public void onUpgrade(SQLiteDatabase db, int i, int i1) {
db.execSQL("drop table if exists fruits;");
onCreate(db);
}
}
activity_main
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/list"/>
</LinearLayout>
DB1Demo
MainActivity
public class MainActivity extends AppCompatActivity {
ArrayAdapter<Memo> adapter;
List<Memo> data;
EditText edit;
ListView list;
MemoOpenHelper helper;
SQLiteDatabase db;
Button del;
View.OnClickListener listener = view -> {
Memo m;
db = helper.getWritableDatabase();
switch (view.getId()) {
case R.id.add:
String str = edit.getText().toString();
if (str.length() != 0) {
// db에 넣을 데이터 형식 = Contentvalues
ContentValues values = new ContentValues();
values.put("memo", str);
// db에 contentvalues를 삽입
long id = db.insert("memos", null, values);
// list에도 같은 내용을 추가해준다
data.add(new Memo(id, str));
}
break;
case R.id.del:
if (adapter.getCount() > 0) {
// adapter에서 첫번째 아이템 가져오기
m = adapter.getItem(0);
// 어댑터에서 가져온 아이템의 id를 가진 데이터를 삭제
db.delete("memos", "_id=" + m.getId(), null);
// List에서는 0번째 인덱스를 삭제한다
data.remove(0);
}
break;
}
// 어댑터에 데이터 셋이 변경되었다고 알린다.
adapter.notifyDataSetChanged();
edit.setText("");
del.setEnabled(data.size() > 0);
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edit = findViewById(R.id.edit);
list = findViewById(R.id.list);
Button add = findViewById(R.id.add);
del = findViewById(R.id.del);
helper = new MemoOpenHelper(this);
data = new ArrayList<>();
// DB 를 읽어오는 부분
db = helper.getReadableDatabase();
// 디비를 읽어서 Cursor를 가져온다.
Cursor c = db.query("memos", new String[]{"_id, memo"},
null, null, null, null, null);
c.moveToFirst(); // Cursor를 첫번째 위치로 옮김
// Cursor 내용 전체를 하나씩 읽어서 List에 추가한다.
while (!c.isAfterLast()) {
data.add(new Memo(c.getLong(0), c.getString(1)));
c.moveToNext();
}
c.close();
helper.close();
// 어댑터를 생성한다. list의 layout과 거기에 출력할 데이터셋을 추가한다.
adapter = new ArrayAdapter(this,
android.R.layout.simple_list_item_1, data);
// 버튼에 리스너 연결
add.setOnClickListener(listener);
del.setOnClickListener(listener);
del.setEnabled(data.size() > 0);
// ListView에 어댑터 연결
list.setAdapter(adapter);
}
}
ArrayAdapter를 이용할 경우, Cursor를 통해 디비 내용을 읽어서 data list에 저장한 후 어댑터로 연결
MemoOpenHelper
public class MemoOpenHelper extends SQLiteOpenHelper {
public MemoOpenHelper(@Nullable Context context) {
super(context, "memos", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table memos (" +
" _id integer primary key autoincrement," +
"memo text not null);");
}
@Override
public void onUpgrade(SQLiteDatabase db, int i, int i1) {
db.execSQL("drop table if exists memos;");
onCreate(db);
}
}
Memo
public class Memo {
private long id;
private String s;
public Memo(Long id, String s) {
this.id = id;
this.s = s;
}
public long getId() {
return id;
}
public String getS() {
return s;
}
@Override
public String toString() {
return s;
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/edit" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/add"
android:text="추가"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/del"
android:text="삭제"/>
</LinearLayout>
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:choiceMode="singleChoice"
android:id="@+id/list"/>
</LinearLayout>
DB2Demo - SimpleCursorAdapter 사용
MainActivity
public class MainActivity extends AppCompatActivity{
EditText edit;
MemosOpenHelper helper;
SQLiteDatabase db;
SimpleCursorAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edit = findViewById(R.id.edit);
helper = new MemosOpenHelper(this);
// helper에서 읽기용 db를 가져온다
db = helper.getReadableDatabase();
// rawQuery를 통해 테이블을 읽어 Cursor를 가져온다.
Cursor c = db.rawQuery("select * from memos;", null);
// SimpleCursorAdapter에 list layout, cursor, 가져올 데이터 필드명, 가져올 데이터의 layout, flag 설정
adapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_1, c,
new String[]{"memo"}, new int[]{android.R.id.text1}, 0);
ListView list = findViewById(R.id.list);
// list에 어댑터 연결
list.setAdapter(adapter);
// 삭제 버튼 어댑터 item이 없으면 비활성화
findViewById(R.id.del).setEnabled(adapter.getCount() > 0);
}
public void onClick(View view){
db = helper.getWritableDatabase();
switch (view.getId()) {
case R.id.add:
String str = edit.getText().toString();
// db에 넣을 contentvalues 생성
ContentValues values = new ContentValues();
values.put("memo", str);
// content values를 DB에 삽입한다.
db.insert("memos", null, values);
break;
case R.id.del:
// adapter에서 첫번째 아이템을 가져온다.
Cursor c = (Cursor) adapter.getItem(0);
// 삭제할 대상의 where 조건 명시
db.delete("memos", "_id=" + c.getLong(0), null);
c.close();
}
// db의 내용이 변경되었기 때문에 다시 조회한다.
Cursor c = db.query("memos", new String[]{"_id", "memo"},
null, null, null, null, null);
// adapter에 커서 내용이 변경되었음을 알려준다.
adapter.changeCursor(c);
// 어댑터에 데이터셋이 변경됨을 알림
adapter.notifyDataSetChanged();
edit.setText("");
findViewById(R.id.del).setEnabled(adapter.getCount() > 0);
}
}
Memo.class, MemosOpenHelper.class, activity_main.xml 는 DB1Demo와 동일
SQLiteDatabase 함수
select | db.query(); | db.query("table", new String[]{"_id", "memo", null, null, null, null, null); Return : Cursor |
rawQuery | rawQuery("select * from fruits;"); Return : Cursor |
|
execSQL | execSQL("create table <table> ( _id integer primary key autoincrement, name text, price integer);"); execSQL("insert into <table> values (null, '사과', 1400); execSQL("drop table if exists fruits"); Return: void |
|
insert | insert | insert("table", null, ContentValues); |
delete | delete | delete("table", "_id=" + memo.getId(), null); |
3. 로그캣
- 안드로이드가 제공하는 디버깅을 위한 범용 로그 패키지
- 수준별 로그 메시지를 출력하려면 툴바에 있는 V, D, I, W, E를 사용한다.
- Verbose: 모든 메시지 보기
- Debug: Debug, Information, Warning, Error 수준의 메시지만 보기
- Information: Information, Warning, Error 수준의 메시지만 보기
- Warning: Warning, Error 수준의 메시지만 보기
- Error: Error 수준의 메시지만 보기
- 의미있는 로그 메시지를 출력하려면 하단부의 Filter 필드를 사용하여 원하는 문자만 포함하는 로그 메시지만 걸러내어 출력할 수 있다.
- 로그 메시지의 내용
시간, 우선순위, 프로세스 식별자, 태그, 로그메시지 - 로그캣에 쓰기
Log.i(String tag, String message, [Throwable exception]) // v/d/i/w/e 가능 Log.i("life", "full.onCreate()");
-
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.i("life", "main.onCreate()"); text = findViewById(R.id.text); if (savedInstanceState != null) { text.setText(savedInstanceState.getString("text")); } } @Override protected void onRestart() { super.onRestart(); Log.i("life", "main.onRestart()"); } @Override protected void onStart() { super.onStart(); Log.i("life", "main.onStart()"); } @Override protected void onResume() { super.onResume(); Log.i("life", "main.onResume()"); } @Override protected void onPause() { super.onPause(); Log.i("life", "main.onPause()"); } @Override protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); Log.i("life", "main.onSaveInstanceState()"); outState.putString("text", text.getText().toString()); } // @Override // protected void onRestoreInstanceState(Bundle savedInstanceState) { // super.onRestoreInstanceState(savedInstanceState); // if (savedInstanceState != null) { // text.setText(savedInstanceState.getString("text")); // } // } @Override protected void onStop() { super.onStop(); Log.i("life", "main.onStop()"); } @Override protected void onDestroy() { super.onDestroy(); Log.i("life", "main.onDestroy()"); }
4. 액티비티와 생명주기
4.1 애플리케이션, 프로세스, 태스크
안드로이드 애플리케이션
- 액티비티, 서비스, 컨텐츠 공급자, 방송수신자가 느슨하게 결합된 컴포넌트로 구성됨
- 결합된 컴포넌트에 대한 내역은 매니페스트 파일의 <application> 엘리먼트에 명시
- 새로운 액티비티가 시작할 때마다 이전 액티비티는 정지되지만 시스템은 이전 액티비티를 액티비티 스택 / 백 스택 이라는 스택에 보존한다.
*액티비티는 사용자와 상호작용 하는 컴포넌트* - windows는 화면을 여러개 띄울 수 있지만 모바일에서는 화면을 1개만 띄울 수 있다.
모바일은 여러개 동시에 띄울 수 없음
프로세스
- 일반적인 의미: 메모리에 적재되어 실행되고 있는 프로그램
- 안드로이드의 프로세스: 애플리케이션 컴포넌트가 실행되는 장소. 가상 머신으로 분리된 물리적인 실행 단위
안드로이드 애플리케이션은 가상머신에서 다음과 같은 환경으로 수행됨
- 기본적으로 하나의 애플리케이션은 하나의 프로세스로 동작
하나의 프로세스 = 하나의 가상 머신 (일반적으로) - 애플리케이션이 시작될 필요가 있으면 안드로이드는 시스템 자원을 확보하여 프로세스를 시작하며, 더 이상 필요가 없거나 시스템 자원이 부족하면 프로세스를 종료 -> 애플리케이션의 명시적인 종료가 불필요함 (사용자가 종료하지 않음)
- 모든 프로세스는 자신의 가상머신을 가지기 때문에 다른 프로세스와는 격리되어 실행
- 파일을 공유하기 위한 여러가지 방법이 있지만 파일을 가진 애플리케이션은 일반적으로 자신만 파일에 접근이 가능하다.
(ContentProvider)
안드로이드 시스템은 메모리가 부족할 경우 중요하지 않은 순서대로 프로세스를 메모리에서 제거한다.
중요도 순서
- 포그라운드 프로세스
(현재 사용중인 프로세스) - 비지블 프로세스
(보이긴 하는데 상호작용 하지 않는 프로세스) - 서비스 프로세스
- 백그라운드 프로세스
- 공백 프로세스
태스크
- 액티비티는 다른 애플리케이션에 존재하는 액티비티까지 호출 가능하다.
-> 실행 측면에서 보면 애플리케이션 컴포넌트는 경계가 없음 - 사용자와 상호작용하는 액티비티는 애플리케이션의 단위보다 태스크 단위로 관리
- 태스크는 동일한 목적을 위한 액티비티의 집합으로서 사용자에게 하나의 애플리케이션처럼 간주되는 논리적 실행 단위
- 애플리케이션과는 달리 태스크는 매니페스트 파일에 명시할 엘리먼트가 없고, 단지 액티비티의 스택에 불과
- [Back] 버튼을 누르면 현재 실행중인 액티비티는 백스택에서 제거되며, 이전 액티비티가 실행 액티비티로 재개됨
액티비티와 태스크에 대한 디폴트 동작 방식
- 액티비티 A가 액티비티 B를 시작하면 액티비티 A는 중지 혹은 정지되고 시스템은 액티비티 A의 상태를 보유한다.
액티비티 B의 실행 도중에 [Back] 버튼을 누르면 액티비티 A의 상태를 복원하고 재시작한다. - [Home] 버튼을 눌러 태스크를 떠나면 실행중인 액티비티가 정지되고 태스크는 백그라운드로 이동.
시스템은 태스크에 포함된 모든 액티비티의 상태를 보유.
태스크를 시작한 런처 아이콘을 선택하여 태스크를 재시작하면 태스크는 포그라운드로 돌아오고 백스택의 최상위에 있는 액티비티가 화면에 나타남 - [Back] 버튼을 누르면 실행중인 액티비티가 백스택에서 제거 및 파기되며, 백스택의 차상위 액티비티가 재개된다. 시스템은 파기된 액티비티의 상태는 보유하지 않음
- 태스크에서 동일 액티비티의 인스턴스를 여러개 생성 가능
- 태스크에 포함된 액티비티는 응집된 단위로 이동
4.2 액티비티 생명주기
생명주기
- 모바일 컴퓨팅의 부족한 자원 -> 메모리와 같은 자원이 부족하더라도 걸려오는 전화 수신은 필수적
-> 안드로이드는 시스템 자원이 부족하면 사용자의 동의 없이 프로세스를 강제로 종료 - 생명주기란 프로세스의 생성 및 소멸을 포함한 일련의 과정
액티비티 상태
- 활성 상태 혹은 실행 상태
- 포그라운드에 존재
- 사용자 - 액티비티가 상호작용 가능한 상태 - 중지 상태
사용자와 인터랙션을 하지 않지만 보일 수 있음 - 정지 상태
생명주기 메서드
생명주기 메서드들은 사용자가 호출할 수 없다. 시스템에 의해 호출되는 콜백 메서드이다.
- onCreate()
- 액티비티가 생성된다.
- 필요한 모든 것들에 대해 초기화가 이루어진다.
- onStart()
- 액티비티가 보이기 시작한다.
- onResume()
- 액티비티가 보이고 사용자와 상호작용을 할 수 있다.
- onPause()
- 다른 액티비티에게 포커스를 빼앗긴다.
- 다른 액티비티를 호출하는 경우 불림
- 현재 액티비티가 안보일 수도 있음
- onStop()
- 액티비티가 더이상 보이지 않는다.
- 액티비티가 사라지는 것은 아님
- 이 경우 액티비티는 중요도가 낮아 우선순위에서 밀리면 바로 제거됨
- onDestroy()
- 액티비티가 파기되기 시작
- 메모리에서 제거된다.
필수적인 생명주기 외의 메서드
- onRestart()
- 기존 액티비티를 다시 실행할 때
- onRestart -> onStart() -> onResume()
editText에 글을 쓰거나 라디오 버튼을 선택한 상태에서 전화가 오면 onPause/onStop 되어있다가 전화가 끝나면 다시 돌아와도 액티비티 상태를 유지해주기 때문에 원래 상태로 복귀된다.
액티비티의 상태 복원
- 생명주기 메소드에 의한 상태 복원
- 생명주기 메소드를 사용하면 액티비티가 중지 혹은 정지되더라도 액티비티의 상태는 유지.
따라서 중지 혹은 정지된 액티비티가 복귀 혹은 다시 실행되어 포그라운드 상태가 되면 원래 상태로 복귀된다. - 액티비티의 상태를 Bundle 형태로 저장해두면 다시 시작할 때 액티비티를 원래대로 화면에 표시.
액티비티가 처음 시작하는 경우라면 onCreate() 메소드의 매개변수인 Bundle 객체는 null 을 포함한다. - 사용자가 삭제한 경우는 복원할 필요 없음
- 생명주기 메소드를 사용하면 액티비티가 중지 혹은 정지되더라도 액티비티의 상태는 유지.
- 생명주기 메소드에 의한 상태 복원의 문제점
- 시스템이 메모리 부족 등의 이유로 액티비티를 파기 가능
-> 액티비티 복원 불가. 따라서 액티비티를 다시 시작하면 시스템은 액티비티 인스턴스를 재생성한다 - 사용자는 시스템에 의한 액티비티 파기 여부를 알 수 없음
- 시스템이 메모리 부족 등의 이유로 액티비티를 파기 가능
액티비티의 강제 종료와 상태 복원
- 강제 종료의 경우에 상태를 저장하기 위하여 안드로이드는 개발자에게 다음 메소드를 제공
- onSaveInstanceState()
: 액티비티가 보이는 마지막 시점에 호출되어 상태를 저장한다.
onPause() -> onSaveInstanceState()
비정상 종료를 위해서 반드시 구현 - onRestoreInstanceState()
: 액티비티가 파기된 후 다시 시작되는 시점에 호출되어 상태를 복원한다.
* 이 메소드에서 이전 상태를 가져오거나, onCreate() 메소드에서 이전 상태를 가져올 수도 있다.
onCreate() -> onStart() 다음에 호출됨
- onSaveInstanceState()
5. 퍼미션
- 특정 기능을 사용하겠다는 선언
- 사용자의 개인정보를 보호하기 위한 목적
- 퍼미션이 없을 경우 기능이 무시되거나 예외가 발생한다.
- 특정 기능을 몰래 수행하는 것을 방지하며 설치할 때 혹은 실행 중에 체크한다.
- 특정 기능이 동작하지 않을 때 퍼미션을 우선적으로 점검해 본다.
안드로이드 기본 보안 아키텍처
- 기본적으로 어떠한 앱도 다른 앱, 운영체제 또는 사용자에게 부정적인 영향을 미칠 수 있는 작업을 실행할 권한이 없다는 것을 바탕으로 설계
- 민감한 사용자 데이터 및 특정 시스템 기능에 액세스할 수 있는 퍼미션 요청 필요
- 기능에 따라 시스템에서 자동으로 권한을 부여하거나 사용자에게 요청을 승인하라는 메시지 표시 가능
기존 퍼미션 모델의 문제점
- 허가제가 아닌 신고제 -> 누구나 권한 획득 가능
- 최종 사용자가 앱 설치 시 일일이 퍼미션 확인하지 않음
- 특정 기능, 신고한 모든 기능을 사용. 무엇을 하는지는 알 수 없음
- 일단 설치되면 신고한 모든 기능 사용 가능
5.1 퍼미션 모델
- 6.0에서 퍼미션 모델을 수정
- 별로 위험하지 않은 일반 퍼미션과 보안상 민감한 퍼미션으로 분류
- 일반 퍼미션
- 설치할 때 허가 받으면 계속 사용 가능
- 진동 모터, 플래시 사용
- 위험 퍼미션
- 실행 중에 일일이 허가 필요
- 주소록, 통화 목록과 같은 개인정보
- 개별 퍼미션을 관리하기는 어려우므로 그룹별로 분류
퍼미션 그룹 | 퍼미션 |
CONTACTS | READ_CONTACTS WRITE_CONTACTS GET_ACCOUTS |
PHONE | READ_PHONE_STATE CALL_PHONE READ_CALL_LOG WRITE_CALL_LOG ADD_VOICEMAIL USE_SIP PROCESS_OUTGOING_CALLS |
SMS | SEND_SMS RECEIVE_SMS READ_SMS RECEIVE_WAP_PUSH RECEIVE_MMS |
STORAGE | READ_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE |
LOCATION | ACCESS_FINE_LOCATION ACCESS_COARSE_LOCATION |
CALENDAR | READ_CALENDAR WRITE_CALENDAR |
CAMERA | CAMERA |
MICROPHONE | RECORD_AUDIO |
SENSORS | BODY_SENSORS |
어떤 앱이 <permission>을 부여했다면, 그 앱을 사용하는 앱은 <uses-permission>을 선언해야 한다.
<permission
android:name="com.test.permission.SOME_PERMISSION"
android:label="SOME permission"
android:description="@string/permission"
android:protectionLevel="normal"/>
- name: 퍼미션의 이름
- label, description: 퍼미션에 대한 설명
- protectionLevel: 보호 수준
- normal: 낮은 수준의 보호. 사용자에게 권한 부여 요청이 필요 없는 경우
- dangerous: 높은 수준의 보호. 사용자에게 권한 부여 요청이 필요한 경우
- Android 6.0 (API Level 23) 이전까지는 개발자 신고제. 6.0부터는 퍼미션을 사용자가 거부 가능
퍼미션 확인
public int checkSelfPermission(String permission)
return
- PackageManager.PERMISSION_GRANTED: 퍼미션 부여된 상태
- PackageManager.PERMISSION_DENIED: 퍼미션 부여되지 않은 상태
퍼미션 요청
public final void requestPermissions(String[] permissions, int requestCode)
requestPermissions(new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE}, 200);
퍼미션 허용 여부 확인
public abstract void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
public class MainActivity extends AppCompatActivity {
TextView text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = findViewById(R.id.result);
}
public void onClick(View view) {
switch (view.getId()) {
case R.id.read:
tryOutContacts();
break;
case R.id.reset:
text.setText("주소록 초기화");
break;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
outContact();
}
}
}
void tryOutContacts() {
if (checkSelfPermission(Manifest.permission.READ_CONTACTS)
== PackageManager.PERMISSION_GRANTED) {
outContact();
} else
requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, 1);
}
void outContact() {
ContentResolver cr = getContentResolver();
Cursor c = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null);
int nameidx = c.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
if (c.moveToNext()) {
text.setText(c.getString(nameidx));
} else
text.setText("주소록이 비어 있습니다.");
c.close();
}
}
연락처 다 가져오기 - StringBuilder
public class MainActivity extends AppCompatActivity {
TextView text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = findViewById(R.id.result);
}
public void onClick(View view) {
switch (view.getId()) {
case R.id.read:
tryOutContacts();
break;
case R.id.reset:
text.setText("주소록 초기화");
break;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
outContact();
}
}
}
void tryOutContacts() {
if (checkSelfPermission(Manifest.permission.READ_CONTACTS)
== PackageManager.PERMISSION_GRANTED) {
outContact();
} else
requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, 1);
}
void outContact() {
ContentResolver cr = getContentResolver();
Cursor c = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null);
int nameidx = c.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
if (!c.isAfterLast()) {
StringBuilder sb = new StringBuilder();
c.moveToFirst();
while (!c.isAfterLast()) {
sb.append(c.getString(nameidx) + "\n");
c.moveToNext();
}
text.setText(sb.toString());
}else
text.setText("주소록이 비어 있습니다.");
c.close();
}
}
ListView를 이용해서 연락처 가져오기
public class MainActivity extends AppCompatActivity {
ListView listView;
List data;
ArrayAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = findViewById(R.id.result);
data = new ArrayList();
adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, data);
listView.setAdapter(adapter);
}
public void onClick(View view) {
switch (view.getId()) {
case R.id.read:
tryOutContacts();
break;
case R.id.reset:
data.clear();
break;
}
adapter.notifyDataSetChanged();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
outContact();
}
}
}
void tryOutContacts() {
if (checkSelfPermission(Manifest.permission.READ_CONTACTS)
== PackageManager.PERMISSION_GRANTED) {
outContact();
} else
requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, 1);
}
void outContact() {
ContentResolver cr = getContentResolver();
Cursor c = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null);
int nameidx = c.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
if (!c.isAfterLast()) {
c.moveToFirst();
while (!c.isAfterLast()) {
data.add(c.getString(nameidx));
c.moveToNext();
}
}
c.close();
}
}
권한 체크하는 방법
- checkSelfPermission( Manifest.permission.{READ_CONTACTS} == PackageManager.PERMISSION_GRANTED )
퍼미션 체크하는 메소드를 통해 권한이 부여되었는지 확인한다. - 권한이 부여되지 않은 경우
requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, (requestCode) 1);
메서드를 사용해서 퍼미션을 요청한다. - onRequestPermissionResult() 메서드를 오버라이드 한다.
- requestPermission에서 요청할때 보낸 요청 코드인지 확인하고
- grantResults.length > 0
허가된 권한이 있는지 체크 - grantResults[0] == PackageManager.PERMISSION_GRANTED
부여된 권한 목록 중 첫번째 항목이 Permission Granted 가 맞는지 확인한다. - 권한이 부여된게 맞으면 하려던 작업을 한다.
- 권한이 이미 부여된 경우 권한 요청할 필요 없이 필요한 작업을 수행한다.
6. 리소스 2
6.1 리소스 접근
선택적 리소스
- 애플리케이션의 현지화 가능
- 선택적 리소스를 위한 수식어
수식어 내용 언어 kr, en, fr 화면 크기 small, normal, large 화면 방향 port, land 화면 픽셀 밀도 ldpi(120), mdpi(160), hdpi(240) - 리소스와 참조
리소스: res/drawable-mdpi/myImage.png
코드: R.drawable.myImage
XML: @drawable/myImage- 동일 이름의 다른 확장자 사용 불가
-> 참조 시 확장자는 포함 안됨
- 동일 이름의 다른 확장자 사용 불가
다양한 언어 지원
- 프로젝트에 있는 리소스 폴더를 이용하면 쉽게 해결 가능
- 언어에 따라 각종 로케일 폴더 - values/, values-ko/, values-en/ ..
- 사용자 디바이스에 설정된 지역에 따라 적절한 문자열 사용
- values 폴더는 설정된 언어에 관계없이 사용
values-en은 영어인 경우에 사용
다양한 화면 지원
- 다양한 크기의 화면을 지원하려면 화면 크기에 적합한 레이아웃 XML 파일을 모두 생성. res/ 폴더 아래에 layout-large/, layout-small-land 등
- 다양한 밀도의 이미지 리소스를 지원하려면 drawable-<밀도>/ <hdpi, xhdpi ,,>
7. 출력
그래픽
화면을 디자인할 때 용도에 맞는 위젯을 레이아웃에 배치
모든 그림을 안드로이드가 제공하지 않음 -> 복잡한 그림은 직접 그림
구성 요소
- 안드로이드 시스템, 사용자
- 캔버스, 페인트, 패스
7.1 Canvas
캔버스
- 그래픽이 그려지는 실제 화면의 인터페이스
- 화면 영역에 어떤 것을 그리는 수단을 제공
캔버스에 직접 출력하려면
- View를 상속받아 onDraw()를 재정의
- 생성자도 정의해야 한다.
- onDraw(Canvas canvas)는 그리기를 할 때 호출되며 인수로 Canvas 객체 전달됨
도형 출력 메서드 (canvas. 으로 시작
- drawPoint(float x, float y, Paint paint)
- drawLine(float startX, float startY, float stopX, float stopY, Paint p)
- drawCircle(float cx, float cy, float radius, Paint paint)
- drawRect(float left, float top, float right, float bottom, Paint p)
drawRect(RectF r, Paint p).. - drawText(String text, float x, float y, Paint p)
- drawRoundRect(RectF r, float rx, float ry, Paint paint) : 둥근 사각형
- drawOval(,,) : 타원
- drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint p)
시작 각도 - 오른쪽 기준, 각도는 시계방향, center는 가운데 위치 연결 여부 - drawLines()
- drawPoints()
캔버스에 색상 채우기
- drawRGB(int r, int g, int b)
- drawARGB(int a, int r, int g, int b)
- drawColor(int color)
- drawPaint(Paint p)
7.2 Paint
붓의 두께, 색상 등 그리기에 대한 속성 정보
메소드
- setARGB(int a, int r, int g, int b)
- setAntiAlias(boolean a)
- setColor(int color)
- setDither(boolean d)
- setStyle(Paint.Style....)
Paint.Style.FILL, ,, 곡선의 외곽선 및 채움 방식을 지정 - setTextSize(float size)
- setTypeface(Typeface tf)
선의 속성 지정
한번 설정한 속성은 변경하지 않는 한 유지된다.
7.3 Path
- 직선 조각, 2차 곡선, 3차 곡선을 구성하는 도형의 궤적 정보를 가짐
- 미리 도형의 경로를 구성할 수 있음
- 도형의 궤적 정보만 가짐. 화면에 직접적으로 보이지 않음
- canvas.drawPath(path, paint) 를 호출하여 그림
메소드
- addCircle(float x, float y, float radius, Path.Direction dir)
- addOval(RectF o, Path.Direction dir)
- addRect(
- addRoundRect(
- lineTo(x, y)
그리기 (현재위치에서 x,y 까지) - moveTo(x, y)
이동 (절대좌표) - rLineTo(x, y)
현재위치에서 +x, +y 만큼 이동 - rMoveTo(x, y)
- reset()
- set(Path)
7.4 위젯 수정
커스텀 위젯 생성 방법
- 완전히 새로 생성
- 기존 위젯 / 레이아웃을 확장하여 관련 메소드를 재정의
- 다수의 위젯 조합
public class MainActivity extends AppCompatActivity {
static final String[] codes =
new String[]{"Apple", "Banana", "Cupcake", "Donut", "Eclair", "Froyo"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView lv = findViewById(R.id.list1);
lv.setOnItemClickListener((adapterView, view, i, l) -> {
String s= "Select item = " + codes[i];
Toast.makeText(MainActivity.this, s, Toast.LENGTH_SHORT).show();
});
lv.setAdapter(new ArrayAdapter<String>(this, R.layout.row, codes));
}
}
public class SmartEditText extends AppCompatEditText {
public SmartEditText(Context context) {
super(context);
}
public SmartEditText(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public SmartEditText(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
boolean b = super.onKeyDown(keyCode, event);
setBackgroundColor(0xffffff00);
return b;
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.wiget1demo.SmartEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hi Android!"
android:padding="10dp"/>
<ListView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/list1"/>
</LinearLayout>
row.xml
<?xml version="1.0" encoding="utf-8"?>
<com.example.wiget1demo.LabelView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:padding="10dp"
android:textColor="#ff0000ff">
</com.example.wiget1demo.LabelView>
public class LabelView extends AppCompatTextView {
public LabelView(@NonNull Context context) {
super(context);
}
public LabelView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public LabelView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.YELLOW);
super.onDraw(canvas);
}
}
8. 데이터 관리
(다른 프로젝트에서 DB를 접근할 때)
8.1 컨텐트 공급자
- 안드로이드에서는 기본적으로 모든 애플리케이션은 다른 애플리케이션과 격리되어 실행
- 따라서 하나의 애플리케이션에 포함된 데이터베이스 혹은 파일 시스템과 같은 컨텐트 모델을 다른 애플리케이션에서 접근 불가
- 컨텐트 공급자는 애플리케이션 사이에 컨텐트 모델을 공유하기 위한 매커니즘
- 애플리케이션이 외부 컨텐트 모델을 접근하려면 컨텐트 리졸버가 필요
(컨텐트 리졸버: 공개된 Content 접근) - 컨텐트 공급자는 애플리케이션에 속한 컨텐트 모델을 외부에 노출시키는 역할을 수행
- DB를 외부에 노출시키려면 Content Provider를 통해서 접근해야 한다.
- Content Resolver를 통해 외부 DB에 접근한다.
- Content Provider로부터 결과를 돌려받는다.
- 직접 외부 DB에 접근은 불가능하다.
8.2 내장 컨텐트 공급자
- 데이터 집합의 식별
- 하나의 공적인 URI를 이용
- URI 형식
<schema>://<authority>/<data_path>/<id> - URI 예
content://browser/bookmarks
content://contacts/people
content://contacts/people/3
- android.provider 패키지
- 대표적인 내장 컨텐트 공급자의 URI
컨텐트 URI 통화 로그 CallLog.Calls.CONTENT_URI 연락처 리스트 ContactsContract.Contacts.CONTENT_URI 브라우저 북마크 Browser.BOOKMARKS_URI 브라우저 검색 이력 Browser.SEARCHES_URI 전화 번호 ContactsContract.CommonDataKinds.Phone.CONTENT_URI 시스템 설정 값 Settings.System.CONTENT_URI 내장 미디어 이미지 MediaStore.Image.Media.INTERNAL_CONTENT_URI 외부 미디어 이미지 MediaStore.Image.Media.EXTERNAL_CONTENT_URI 내장 미디어 동영상 MediaStore.Video.Media.INTERNAL_CONTENT_URI 외부 미디어 동영상 MediaStore.Video.Media.EXTERNAL_CONTENT_URI 내장 미디어 오디오 MediaStore.Audio.Media.INTERNAL_CONTENT_URI 외부 미디어 오디오 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
cp1Demo
MainActivity.java
import static android.provider.ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
import static android.provider.ContactsContract.CommonDataKinds.Phone.NUMBER;
public class MainActivity extends AppCompatActivity {
CursorAdapter cursorAdapter;
Cursor c;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView list = findViewById(R.id.list);
// String[] from = {ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME}
String[] from = {ContactsContract.Contacts.DISPLAY_NAME,
NUMBER};
int[] to = {android.R.id.text1, android.R.id.text2};
cursorAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_2,
c, from, to, 0);
list.setAdapter(cursorAdapter);
if (checkSelfPermission(Manifest.permission.READ_CONTACTS)
== PackageManager.PERMISSION_DENIED) {
requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, 1);
return ;
}
showData();
}
private void showData() {
c = getContentResolver().query(CONTENT_URI, null, null, null);
cursorAdapter.changeCursor(c);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1) {
if (permissions.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
showData();
}
}
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.cp1demo">
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
-> uses-permission 태그 추가
cp2Demo
MainActivity.java
public class MainActivity extends AppCompatActivity {
TextView view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
view = findViewById(R.id.text);
if (checkSelfPermission(Manifest.permission.READ_CALL_LOG)
== PackageManager.PERMISSION_DENIED) {
requestPermissions(new String[]{Manifest.permission.READ_CALL_LOG}, 1);
return ;
}
makeDisplay();
}
void makeDisplay() {
Cursor c = getContentResolver().query(CallLog.Calls.CONTENT_URI, null, null, null);
int i1 = c.getColumnIndex(CallLog.Calls.DATE);
int i2 = c.getColumnIndex(CallLog.Calls.NUMBER);
int i3 = c.getColumnIndex(CallLog.Calls.DURATION);
while (c.moveToNext()) {
view.append(c.getString(i1) + "\n");
view.append(c.getString(i2) + "\n");
view.append(c.getString(i3) + "\n");
}
c.close();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
makeDisplay();
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.cp2demo">
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<application
android:allowBackup="true"
- <uses-permission android:name="android.permission.READ_CALL_LOG" /> 추가
9. 다이어로그
- 문자 그대로 시스템과 대화하기 위한 윈도우
- 사용자에게 시스템의 현재 상태 및 오류 사항 등을 알려주기 위하여 경고 메시지를 띄우거나 질문사항을 보내어 사용자의 선택을 받아들이는 기본적인 통신 수단
- 대표적인 Dialog 클래스
- AlertDialog
아이콘, 메시지, 버튼 3개를 가지며 가장 많이 사용됨 - DatePickerDialog
달력에서 날짜를 제공하여 날짜 설정을 가능하게 함 - ProgressDialog
실행 상태를 통지하는 진행 바를 포함 - TimePickerDialog
시간을 제공하여 시간 설정을 가능하게 함 - ZoomDialog
줌 레벨을 선택함. 주로 지도에서 사용
- AlertDialog
9.1 경고 다이어로그
- 윈도우즈의 모달 다이어로그 방식과 유사
- 경고 다이어로그에서 버튼을 누르지 않으면 진행하던 작업으로 돌아가거나 다른 작업으로 진행할 수 없음
- 작업의 결과나 경고 등 중요한 메시지를 사용자에게 전달할 때 경고 다이어로그를 사용
- Dialog의 확장 클래스
1) 생성과 표시
- AlertDialog.Builder(Context)... 메소드를 이용
- Builder 객체를 생성한 후 경고 다이어로그의 세부 속성을 설정
- 매개변수인 Context 를 위해서는 다이어로그를 생성하는 부모 액티비티를 사용
- 세부 속성을 설정한 후 show()메소드를 사용하면 Builder 객체를 생성하고 화면에 표시
2) 세부 속성 설정
- public AlertDialog.Builder setIcon(int iconId 혹은 Drawable icon)
- public AlertDialog.Builder setMessage(int msgId / CharSequence msg)
- public AlertDialog.Builder setTitle(int titleId / CharSequence title)
3) 경고 다이어로그의 3가지 버튼 설정
- public AlertDialog.Builder setNegativeButton (CharSequence text / int textId, DialogInterface.OnClickListener listener)
- public AlertDialog.Builder setNeutralButton (CharSequence text / int textId, DialogInterface.OnClickListener listener)
- public AlertDialog.Builder setPositiveButton (CharSequence text / int textId, DialogInterface.OnClickListener listener)
- 구분용이므로 단어의 의미와는 상관이 없다.
4) Back 버튼에 의한 다이어로그 취소 금지
- public AlertDialog.Builder setCancelable(boolean cancelable)**
- false: [Back] 버튼 불가
- true: default, [Back] 버튼 가능
5) 3개의 버튼 외에 리스트 혹은 체크박스와 라디오버튼의 추가
- public AlertDialog.Builder setItems(CharSequence[] items, DialogInterface.OnClickListener listener)
- setSingleChoiceItems(CharSequence[] items, int checkedItem, DialogInterface.OnClickListener listener)
라디오 버튼 추가
9.2 피커 다이어로그
- 달력을 기준으로 월 / 일 / 연도를 선택하는 위젯
- DatePicker를 포함하는 간단한 다이어로그
- 날짜 선택 다이어로그에서 사용자가 선택한 날짜 값을 받아올 수 있도록 OnDateSetListener()를 설정
- 날짜 선택 다이어로그에서 연월일 값을 선택하면 onDateSet() 메소드를 호출하며 이 메소드를 통해 사용자가 선택한 연월일 값을 화면에 표시
- 자원을 많이 사용하기 때문에 이와 같은 다이어로그는 미리 생성하는 것이 효과적
- void showDialog(int id)
Dialog onCreateDialog(int id)
void onPrePareDialog(int id, Dialog dialog)
dialog1Demo
MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn = findViewById(R.id.button);
btn.setOnClickListener(view -> new AlertDialog.Builder(MainActivity.this)
.setTitle("Title")
.setMessage("message")
.setIcon(R.mipmap.ic_launcher)
.show());
}
}
- setItems()
public class MainActivity extends AppCompatActivity {
CharSequence items[] = {"Red", "Green", "Blue"};
int colors[] = {Color.RED, Color.GREEN, Color.BLUE};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn = findViewById(R.id.btn);
btn.setOnClickListener(view -> {
new AlertDialog.Builder(MainActivity.this)
.setTitle("Pick a color")
.setItems(items, (dialogInterface, i) -> {
btn.setBackgroundColor(colors[i]);
})
.setCancelable(false)
.show();
});
}
}
public class MainActivity extends AppCompatActivity {
CharSequence[] items = {"Red", "Green", "Blue"};
int[] colors = {Color.RED, Color.GREEN, Color.BLUE};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn = findViewById(R.id.btn);
btn.setOnClickListener(view -> {
new AlertDialog.Builder(this)
.setTitle("Pick a color")
.setSingleChoiceItems(items, -1,
(dialogInterface, i) -> {
btn.setBackgroundColor(colors[i]);
})
.setNegativeButton("종료", null)
.setCancelable(false)
.show();
});
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn = findViewById(R.id.button);
btn.setOnClickListener(view -> {
Calendar c = Calendar.getInstance();
new DatePickerDialog(this, null,
c.get(Calendar.YEAR),
c.get(Calendar.MONTH),
c.get(Calendar.DAY_OF_MONTH))
.show();
});
}
}
'sswu' 카테고리의 다른 글
오픈소스 소프트웨어 중간 정리 (0) | 2022.03.20 |
---|---|
[네트워크 분석 실습] 네분실 기말 정리 (0) | 2021.11.19 |
파이썬 기말 정리 (0) | 2021.11.15 |
[네트워크 분석 실습] 실습 정리 (0) | 2021.10.22 |
[네트워크 분석 실습] 정리1 (0) | 2021.09.20 |