original source : https://youtu.be/uru2mtT4mLY

image
image
image

versionCode 는 스트링으로 표현되어 있는데 integer형태를 가지고 있다.(전환가능한 형태)

image

=========================================================

.

.

image
image

checkSelfPermission()을 통해 app이 사용자로 부터 획득한 permission이 있는지 확인할수 있다.  shouldShowRequestPermissionRationale()을 통해 사용자로 부터 permission 획득시 왜 필요한지 설명이 필요한지 아닌지를 확인할수 있다. requestPermissions()를 통해 사용자로부터 permission을 획득하는 ui를 작동할수 있다.

=========================================================

.

.

image
image

=========================================================

.

.

image
image

=========================================================

.

.

image
image
image
image

아래에서 values-ko가 맞는 내용이다. ko는 locale을 나타낼때 사용하고 kr는 국가 코드이다.

image

=========================================================

.

.

image

=========================================================

.

.

image
image
image

=========================================================

.

.

image
image

=========================================================

.

.

image
image

=========================================================

.

.

image
image

=========================================================

.

.

image

=========================================================

.

.

image

=========================================================

.

.

image

=========================================================

.

.

image

=========================================================

.

.

image

original source : https://youtu.be/bc6wW-g6uss

image
image

=========================================================

.

.

image

=========================================================

.

.

image

=========================================================

.

.

image

content provider에서 data set이란 database의 table이라고 생각할수 있다.

=========================================================

.

.

content provider에서 data를 가져올때 cursor에 넣어서 되돌린다. 즉 cursor를 통해 data에 접근하게 된다. 

image
image

=========================================================

.

.

image

Contract class는 아래와 같이 만들수 있다. authority, data set이름, uri, 칼럼이름, type 이름등을 지정해서 여러곳에 햇갈리지 않고 일관되게 사용할수 있게 한다. 

image
image

content provider의 data에 접근하기위해 <uses-permission> 를 이용해서 permission을 얻을수 있다. content provider는 다른 app이 어떤 permission을 얻어야 하는지 지정해주어야 한다. 경우에 따라 다른 app에게 임시 permission은 허용하기 위해서는 content provider에 android:grantUriPermissions를 설정하거나 하위에 <grant-uri-permission>설정한다. 임시 permission 이 필요한 app의 경우 호출 intent에 위와 같이 Flag를 지정해 준다. 

image
image
image

=========================================================

.

.

image
image
image
image
image

=========================================================

.

.

image
image
image

=========================================================

.

.

참고자료) UriMatcher 실제 사용 예시 https://youtu.be/6ZbAsvifQq8

image

=========================================================

.

.

image
image

=========================================================

.

.

image

=========================================================

.

.

authority, data set이름, uri, 칼럼이름, type 이름등을 지정해서 여러곳에 햇갈리지 않고 일관되게 사용할수 있게 한다. 

image
image

=========================================================

.

.

image
image
image
image
image
image
image
image

=========================================================

.

.

image

original source : https://youtu.be/UyUmQaUtUVI

=========================================================

.

.

=========================================================

.

.

아래는 service에서 activity로 data를 전달하는 경우이다.

=========================================================

.

.

아래는 service에서 activity로 data를 전달하고 다시 data를 전달 받는 경우를 보여주고 있다.

=========================================================

.

.

=========================================================

.

.

application A는 permission #1을 정의하고 normal level로 정의했다. 이경우 application은 permission #1을 가지고 있으므로 application A에 접근가능하다. 그러나 application B는 permission level이 signature이므로 application C에서 permission #2를 가지고 있음에도 불구하고 application B에 접근할수 없다.

=========================================================

.

.

=========================================================

.

.

original source : https://youtu.be/hLPXmoU9D4s

image
image

=========================================================

.

.

image
image

=========================================================

.

.

image
image

Sticky Broadcast의 경우 이미 발생한 이벤트가 시스템에 저장되어있다가 앱이 사용자에 의해 구동되고 앱이 진행되는 가운데 코드상에서 registerReceiver()를 통해 receiver가 등록되면 저장되었던 이벤트가 전달되는 경우를 말한다.

=========================================================

.

.

image
image

=========================================================

.

.

아래는 android studio를 이용 receiver를 추가하는 방법을 설명한다.

image
image
image
image
image
image
image
image

=========================================================

.

.

image

=========================================================

.

.

image
image
image

registerReceiver()를 사용한 경우 memory leak방지를 위해 반드시 unregisterReceiver()를 수행한다.

=========================================================

.

.

image

original source : https://youtu.be/hLPXmoU9D4s

=========================================================

.

.

=========================================================

.

.

Sticky Broadcast의 경우 이미 발생한 이벤트가 시스템에 저장되어있다가 앱이 사용자에 의해 구동되고 앱이 진행되는 가운데 코드상에서 registerReceiver()를 통해 receiver가 등록되면 저장되었던 이벤트가 전달되는 경우를 말한다.

=========================================================

.

.

=========================================================

.

.

아래는 android studio를 이용 receiver를 추가하는 방법을 설명한다.

=========================================================

.

.

=========================================================

.

.

registerReceiver()를 사용한 경우 memory leak방지를 위해 반드시 unregisterReceiver()를 수행한다.

=========================================================

.

.

original source : https://youtu.be/VLZDGPStEp4

image
image

service가 background에서 작업은 하지만 main thread에서 수행된다.

=========================================================

.

.

image
image
image
image
image
image
image

=========================================================

.

.

image
image

=========================================================

.

.

아래 코드에서 i.putExtra(”value”,”activity”)의 경우 data를 pending intent에 넣고 나중에 다시 activity 의 onActivityResult()에서 data.getStringExtra(”value”)를 통해 다시 되돌려 받게 된다. 

image
image
image

=========================================================

.

.

image

AIDL android interface definition language

aidl 은 interface와 작성방법이 거의비슷하다. aidl을 아래와 같이 정의하면 코드상에서 사용될때 아래와 확인할수 있듯이 Stub()을 사용할수 있게 된다. Stub()을 통해 IBinder obj를 얻을수 있고 이를 variable에 저장하고 이를 onBind()를 통해 되돌린다. 

activity에서 bindService()를 통해 bound service를 시작하게 되면 service내의 onBind()가 호출되고 이 메소드 내에서 되돌려진 IBinder를 통해 activity에서 AIDL에 명시된 함수에 접근 할수 있게 되고 이를 통해 service와 소통할수 있게 된다.

image
image
image

=========================================================

.

.

JobScheduler는 service의 일종이며 특정 조건에 발생한 경우 작업이 수행되는 service이다.

image
image
image
image
image
image
image
image
image

original source : https://youtu.be/_xNkVNaC9AI

기본 전제)

service는 server 이며 이에 연결되는 activity는 client라고 이해하면 이해한다.


MainActivity

package com.codingwithmitch.boundserviceexample1;

import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";


    // UI Components
    private ProgressBar mProgressBar;
    private TextView mTextView;
    private Button mButton;


    // Vars
    private MyService mService;
    private MainActivityViewModel mViewModel;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mProgressBar = findViewById(R.id.progresss_bar);
        mTextView = findViewById(R.id.text_view);
        mButton = findViewById(R.id.toggle_updates);

        mViewModel = ViewModelProviders.of(this).get(MainActivityViewModel.class);
        setObservers();

        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                toggleUpdates();
            }
        });
    }

    private void toggleUpdates(){
        if(mService != null){
            if(mService.getProgress() == mService.getMaxValue()){
                mService.resetTask();
                mButton.setText("Start");
            }
            else{
                if(mService.getIsPaused()){
                    mService.unPausePretendLongRunningTask();
                    mViewModel.setIsProgressBarUpdating(true);
                }
                else{
                    mService.pausePretendLongRunningTask();
                    mViewModel.setIsProgressBarUpdating(false);
                }
            }

        }
    }

    private void setObservers(){
        mViewModel.getBinder().observe(this, new Observer<MyService.MyBinder>() {
            @Override
            public void onChanged(@Nullable MyService.MyBinder myBinder) {
                if(myBinder == null){
                    Log.d(TAG, "onChanged: unbound from service");
                }
                else{
                    Log.d(TAG, "onChanged: bound to service.");
                    mService = myBinder.getService();
                }
            }
        });

        mViewModel.getIsProgressBarUpdating().observe(this, new Observer<Boolean>() {
            @Override
            public void onChanged(@Nullable final Boolean aBoolean) {
                final Handler handler = new Handler();
                final Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        if(mViewModel.getIsProgressBarUpdating.getValue()){
                            if(mViewModel.getBinder().getValue() != null){ // meaning the service is bound
                                if(mService.getProgress() == mService.getMaxValue()){
                                    mViewModel.setIsProgressBarUpdating(false);
                                }
                                mProgressBar.setProgress(mService.getProgress());
                                mProgressBar.setMax(mService.getMaxValue());
                                String progress =
                                        String.valueOf(100 * mService.getProgress() / mService.getMaxValue()) + "%";
                                mTextView.setText(progress);
                            }
                            handler.postDelayed(this, 100);
                        }
                        else{
                            handler.removeCallbacks(this);
                        }
                    }
                };

                // control what the button shows
                if(aBoolean){
                    mButton.setText("Pause");
                    handler.postDelayed(runnable, 100);

                }
                else{
                    if(mService.getProgress() == mService.getMaxValue()){
                        mButton.setText("Restart");
                    }
                    else{
                        mButton.setText("Start");
                    }
                }
            }
        });
    }


    @Override
    protected void onResume() {
        super.onResume();
        startService();
    }


    @Override
    protected void onStop() {
        super.onStop();
        if(mViewModel.getBinder() != null){
            unbindService(mViewModel.getServiceConnection());
        }
    }

    private void startService(){
        Intent serviceIntent = new Intent(this, MyService.class);
        startService(serviceIntent);

        bindService();
    }

    private void bindService(){
        Intent serviceBindIntent =  new Intent(this, MyService.class);
        bindService(serviceBindIntent, mViewModel.getServiceConnection(), Context.BIND_AUTO_CREATE);
    }


}

위 코드에서 runnable 호출을 runnable 자체내에서 수행함으로써 looping를 한 부분이 생소했다.아래 그림 참조

또한 bindService()도 처음에는 이해하기 힘들었다. 두번째 파라미터 service connection을 전달부분이 어려웠다. 아래 MainActivityViewModel 코드를 확인하면 ServiceConnection obj 생성시에는 특별한 파라미터를 필요로 하지 않는다. ServiceConnection 는 activity에 service가 연결될때 호출되는 callback obj 로 추측한다. 

MainActivityViewModel

package com.codingwithmitch.boundserviceexample1;

import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.ViewModel;
import android.content.ComponentName;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;

public class MainActivityViewModel extends ViewModel {

    private static final String TAG = "MainActivityViewModel";

    private MutableLiveData<Boolean> mIsProgressBarUpdating = new MutableLiveData<>();
    private MutableLiveData<MyService.MyBinder> mBinder = new MutableLiveData<>();


    // Keeping this in here because it doesn't require a context
    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className, IBinder iBinder) {
            Log.d(TAG, "ServiceConnection: connected to service.");
            // We've bound to MyService, cast the IBinder and get MyBinder instance
            MyService.MyBinder binder = (MyService.MyBinder) iBinder;
            mBinder.postValue(binder);
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            Log.d(TAG, "ServiceConnection: disconnected from service.");
            mBinder.postValue(null);
        }
    };


    public ServiceConnection getServiceConnection(){
        return serviceConnection;
    }

    public LiveData<MyService.MyBinder> getBinder(){
        return mBinder;
    }


    public LiveData<Boolean> getIsProgressBarUpdating(){
        return mIsProgressBarUpdating;
    }

    public void setIsProgressBarUpdating(boolean isUpdating){
        mIsProgressBarUpdating.postValue(isUpdating);
    }


}


MyService

package com.codingwithmitch.boundserviceexample1;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;

public class MyService extends Service {

    private static final String TAG = "MyService";

    private final IBinder mBinder = new MyBinder();
    private Handler mHandler;
    private int mProgress, mMaxValue;
    private Boolean mIsPaused;

    @Override
    public void onCreate() {
        super.onCreate();
        mHandler = new Handler();
        mProgress = 0;
        mIsPaused = true;
        mMaxValue = 5000;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }


    public class MyBinder extends Binder{

        MyService getService(){
            return MyService.this;
        }

    }

    public Boolean getIsPaused(){
        return mIsPaused;
    }

    public int getProgress(){
        return mProgress;
    }

    public int getMaxValue(){
        return mMaxValue;
    }

    public void pausePretendLongRunningTask(){
        mIsPaused = true;
    }

    public void unPausePretendLongRunningTask(){
        mIsPaused = false;
        startPretendLongRunningTask();
    }

    public void startPretendLongRunningTask(){
        final Runnable runnable = new Runnable() {
            @Override
            public void run() {
                if(mProgress >= mMaxValue || mIsPaused){
                    Log.d(TAG, "run: removing callbacks");
                    mHandler.removeCallbacks(this); // remove callbacks from runnable
                    pausePretendLongRunningTask();
                }
                else{
                    Log.d(TAG, "run: progress: " + mProgress);
                    mProgress += 100; // increment the progress
                    mHandler.postDelayed(this, 100); // continue incrementing
                }
            }
        };
        mHandler.postDelayed(runnable, 100);
    }

    public void resetTask(){
        mProgress = 0;
    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        super.onTaskRemoved(rootIntent);
        Log.d(TAG, "onTaskRemoved: called.");
        stopSelf();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy: called.");
    }
}