로그인 바로가기 하위 메뉴 바로가기 본문 바로가기
난이도
심화

안드로이드 앱 프로그래밍

임시 이미지 정재곤
http://www.boostcourse.org/mo316/notice/2526
좋아요 1436 수강생 20099
아래의 글은 BOOSTER 서포터즈로 활동했던 케니(kart***)님이
작성한 부스트코스 후기입니다.
여러분들의 성원에 더 노력하는 부스트코스가 되겠습니다.
감사합니다.
******************************************
1)링크: https://blog.naver.com/kartmon/221603622105
2)작성날짜: 19/08/02
<본문내용>

4. 서비스 사용하기

1) 서비스

서비스는 화면이 없어도 데이터를 주고받는 기능을 실행하고 데이터를 처리해야할 때 사용하게 된다.

화면이 없는 상태에서 백그라운드로 실행되며,

서비스는 프로세스가 종료되어도 시스템에서 자동으로 재시작된다.

서비스 생성

public class MyService extends Service { private static final String TAG = "MyService"; public MyService() { } //서비스의 특성상 한번만들어지면 계속 존재함 @Override public void onCreate() { super.onCreate(); Log.d(TAG,"onCreate() 호출됨."); } //명령어 처리 할때는 onStartCommand에서 처리 @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG,"onStartCommand() 호출됨."); if(intent == null){ return Service.START_STICKY; } else{ processCommand(intent); } return super.onStartCommand(intent, flags, startId); } //화면이 없을 때 처리 private void processCommand(Intent intent){ String command = intent.getStringExtra("command"); String name = intent.getStringExtra("name"); Log.d(TAG,"전달받은 데이터 : " + command+ ", " + name); try{ Thread.sleep(5000); } catch (Exception e){} Intent showIntent = new Intent(getApplicationContext(),MainActivity.class); //화면이 없는 곳에서 화면을 띄우기 위해서는 문제가 생긴다. //Task라는 정보가 없기 때문에 flag 옵션을 줘야한다. showIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK| Intent.FLAG_ACTIVITY_SINGLE_TOP| Intent.FLAG_ACTIVITY_CLEAR_TOP); showIntent.putExtra("command","show"); showIntent.putExtra("name",name+" from service."); startActivity(showIntent); } @Override public void onDestroy() { super.onDestroy(); Log.d(TAG,"onDestroy() 호출됨."); } @Override public IBinder onBind(Intent intent) { throw new UnsupportedOperationException("Not yet implemented"); } }

메인

public class MainActivity extends AppCompatActivity { EditText editText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); editText = (EditText) findViewById(R.id.editText); Button button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { String name = editText.getText().toString(); Intent intent = new Intent(getApplicationContext(),MyService.class); intent.putExtra("command","show"); intent.putExtra("name",name); startService(intent); } }); Intent passedIntent = getIntent(); processCommand(passedIntent); } //메인 액티비티가 이미 만들어져 있는 상태에서 확인할수 있는 메소드 @Override protected void onNewIntent(Intent intent) { processCommand(intent); super.onNewIntent(intent); } private void processCommand(Intent intent){ if(intent != null){ String command = intent.getStringExtra("command"); String name = intent.getStringExtra("name"); Toast.makeText(this,"서비스로부터 전달받은 데이터 : " +command + ", " + name,Toast.LENGTH_LONG).show(); } } }

출력결과

생각해보기

어떤 경우에 액티비티가 아닌 서비스에서 기능을 실행하도록 해야 하는 걸까요?

- 데이터를 주고받아야하는데 화면이 보이지 않을때 서비스에서 데이터 처리 기능을 만들어야 할 것 같습니다.

인터넷을 통해 데이터를 주고받는 기능을 액티비티에 넣어두는 경우 어떤 문제가 발생할 수 있을까요?

- 데이터를 주고 받는 기능이 액티비티에 있다면 아무래도 분리해서 기능을 처리하는 것보다 속도가 느려질 수 있다고 생각합니다.

5. 브로드캐스트 수신자 사용하기

1) 브로드캐스트 수신자

외부에 있는 데이터들을 브로드캐스트 리시버를 통해서 정보를 전달 할 수 있다.

예) sms 문자를 받아서 처리해서 여러개의 앱에 전달

sms를 broadcastReceiver로 가져온다. (이때 broadcastReceiver가 sms메세지만 가져와야 하기 때문에 manifest에서 처리를 해줘야한다.)

manifest.xml

<receiver android:name=".SmsReceiver" android:enabled="true" android:exported="true"><!-- 모든 intent를 받는 것이 아니라 필요한 내용만 받을수 있게 설정 --><intent-filter><action android:name="android.provider.Telephony.SMS_RECEIVED"/></intent-filter></receiver>

SmsReceiver.java

public class SmsReceiver extends BroadcastReceiver { private static final String TAG = "SmsReceiver"; //날짜 시간을 원하는 형식으로 변형 private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH;mm"); //sms 문자를 받을때 onReceive 함수가 자동으로 호출 @Override public void onReceive(Context context, Intent intent) { Log.d(TAG,"onReceive() 호출됨."); //해쉬 태그로 되어있는 bundle을 가져온다. Bundle bundle = intent.getExtras(); SmsMessage[] messages = parseSmsMessage(bundle); //messages에 데이터가 있다면 if(messages.length > 0){ //발신자 번호 String sender = messages[0].getOriginatingAddress(); Log.d(TAG,"sender : "+ sender); //발신 내용 String contents = messages[0].getMessageBody().toString(); Log.d(TAG,"contents : " + contents); //수신 시간 Date receivedDate = new Date(messages[0].getTimestampMillis()); Log.d(TAG,"received date : " + receivedDate); sendToActivity(context, sender, contents, receivedDate); } } //발신자, 내용, 수신 시간의 내용을 intent를 이용하여 activity로 전달하는 메소드 private void sendToActivity (Context context, String sender, String contents,Date receivedDate){ Intent intent = new Intent(context,SmsActivity.class); //브로드캐스트 수신자는 화면이 없기 때문에 flag가 필요하다. intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |Intent.FLAG_ACTIVITY_SINGLE_TOP |Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.putExtra("sender",sender); intent.putExtra("contents",contents); intent.putExtra("receivedDate",format.format(receivedDate)); context.startActivity(intent); } //bundle 에 있는 내용을 가져오는 메소드 private SmsMessage[] parseSmsMessage(Bundle bundle){ //sms 데이터를 처리 할때 pdus를 이용해서 데이터를 가져온다. Object[] objs = (Object[]) bundle.get("pdus"); SmsMessage[] messages = new SmsMessage[objs.length]; //messages 안에 있는 데이터의 갯수 만큼 가져온다. for(int i=0;i<objs.length;i++){ //마쉬멜로우 버전보다 빌드 버전이 같거나 크다면 // if(Build.VERSION.SDK_INT >=Build.VERSION_CODES.M){ //format의 정보를 두번째에 전달. String format = bundle.getString("format"); messages[i] = SmsMessage.createFromPdu((byte[])objs[i],format); } else{ //objs의 i번째 pdu를 가져온다. messages[i] = SmsMessage.createFromPdu((byte[])objs[i]); } } //bundle 데이터를 smsMessage의 데이터로 변환해서 return; return messages; } }

이제 smsReceiver를 통해 받은 데이터를 smsActivity로 뿌려주도록 하자.

SmsActivity.java

public class SmsActivity extends AppCompatActivity { EditText editText; //발신자를 보여줄 공간 EditText editText2; //수신 시간을 보여줄 공간 EditText editText3; //내용을 보여줄 공간 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sms); editText = (EditText) findViewById(R.id.editText); editText2 = (EditText) findViewById(R.id.editText2); editText3 = (EditText) findViewById(R.id.editText3); Button button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { } }); Intent passedIntent = getIntent(); processCommand(passedIntent); } //이미 만들어져있는 경우 @Override protected void onNewIntent(Intent intent) { processCommand(intent); super.onNewIntent(intent); } //전달받은 intent 처리 private void processCommand(Intent intent){ if(intent != null){ String sender = intent.getStringExtra("sender"); String contents = intent.getStringExtra("contents"); String receivedDate = intent.getStringExtra("receivedDate"); editText.setText(sender); editText3.setText(contents); editText2.setText(receivedDate); } } }

메세지를 수신 받게 되면 log에 다음과 같이 보여지는 것을 확인 할 수 있다.

출력 결과

생각해보기

여러분이 직접 브로드캐스팅 메시지를 보낼 수도 있을까요?

- 아마도 브로드캐스팅 전달도 가능할 것 같습니다

브로드캐스트 수신자로 전달받은 메시지를 다시 인텐트에 넣어 액티비티로 보내는 복잡한 과정을 거치지 않고 브로드캐스트 수신자로 전달받은 메시지를 바로 화면에 보여줄 수도 있을까요?

- 음.. 일반적으로 인텐트로 시스템에 접근한다고 알고 있기 때문에 안될 것 같습니다.

2) 위험권한 부여하기

위험 권한

- 앱 실행을 하게 되고 사용자의 정보를 접근하는 기능을 사용한다면 사용자가 수락해야만 동작한다.

(위치, 카메라, 마이크, 연락처, 전화, 문자, 일정, 센서 등)

mainActivity.java

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //권한이 있는지 확인 int permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_SMS); //권한 주어져 있다면 if(permissionCheck == PackageManager.PERMISSION_GRANTED){ Toast.makeText(this, "SMS 수신 권한 주어져 있음.",Toast.LENGTH_LONG).show(); } //권한이 없다면 else{ Toast.makeText(this,"SMS 수신 권한 없음",Toast.LENGTH_LONG).show(); //이 권한에 대한 설명이 필요한지 확인 if(ActivityCompat. shouldShowRequestPermissionRationale (this,Manifest.permission.RECEIVE_SMS)){ Toast.makeText(this,"SMS 권한 설명 필요함.",Toast.LENGTH_LONG).show(); } //시스템에서 권한 부여하는 대화상자 요청 else{ ActivityCompat.requestPermissions(this,new String[]{ Manifest.permission.RECEIVE_SMS },101); } } } //권한이 부여되었는지 확인 @Override public void onRequestPermissionsResult (int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode){ //requestCode를 전달 받음 case 1: if(grantResults.length > 0){ //사용자가 권한을 수락하면 if(grantResults[0] == PackageManager.PERMISSION_GRANTED){ Toast.makeText(this,"SMS 수신 권한을 사용자가 승인함." ,Toast.LENGTH_LONG).show(); } //사용자가 권한을 거부하면 else if(grantResults[0] == PackageManager.PERMISSION_DENIED) { Toast.makeText(this,"SMS 수신 권한을 사용자가 거부함." ,Toast.LENGTH_LONG).show(); } } //권한 부여 X else{ Toast.makeText(this,"SMS 수신 권한을 부여받지 못함." ,Toast.LENGTH_LONG).show(); } } } }

출력 결과

생각해보기

하나의 위험 권한에 대해서 매니페스트 파일(AndroidManifest.xml)에만 권한을 추가하고 코드에서는 위험 권한 부여 요청을 하지 않았을 경우 앱을 실행하면 어떤 현상이 벌어질까요?

- 권한이 부여 받지 못했기 때문에 권한을 사용하는 기능을 사용하지 못하게 될 것이라고 생각합니다.

SD 카드에 파일을 쓰는 기능은 위험 권한으로 분류된 두 가지 권한을 요구합니다. 그러면 이 기능을 사용하기 위해 어떤 과정을 거쳐야 할까요?

- 두개의 권한을 모두 허용 할때까지 권한 요청 작업을 반복 하도록 만들어야 합니다.

 


*********************************************