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

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

.

.

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

.

.

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

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

.

.

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

.

.

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

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

.

.

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

.

.

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

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

.

.

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/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.");
    }
}

 original source : https://youtu.be/1Tn7TuHUl4Y

MainActivity

package com.architecturecomponents.learn.s3_v2_viewmodeldemo;

import android.arch.lifecycle.ViewModelProviders;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private String TAG = this.getClass().getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

        TextView mTextView = findViewById(R.id.tvNumber);
//        MainActivityDataGenerator myData = new MainActivityDataGenerator();
        MainActivityViewModel model = ViewModelProviders.of(this).get(MainActivityViewModel.class);
        LiveData<String> myRandomNumber = model.getNumber();
        myRandomNumber.observe(this, new Observer<String>(){ ...내용들어감... })
        mTextView.setText(myRandomNumber);

        Log.i(TAG, "Random Number Set");
    }
}


MainActivityViewModel

package com.architecturecomponents.learn.s3_v2_viewmodeldemo;

import android.arch.lifecycle.ViewModel;
import android.util.Log;

import java.util.Random;

public class MainActivityViewModel extends ViewModel {

    private String TAG = this.getClass().getSimpleName();
    private MutableLiveData<String> myRandomNumber;

    public String getNumber() {
        Log.i(TAG, "Get number");
        if (myRandomNumber == null) { myRandomNumber = new MutableLiveData<>
            createNumber();
        }
        return myRandomNumber;
    }

    private void createNumber() {
        Log.i(TAG, "Create new number");
        Random random = new Random();
        myRandomNumber.setValue("Number: " + (random.nextInt(10 - 1) + 1));
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        Log.i(TAG, "ViewModel Destroyed");
    }
}

original source : https://youtu.be/V-fQn0z4Y_0

MainActivity

package com.architecturecomponents.learn.s3_v2_viewmodeldemo;

import android.arch.lifecycle.ViewModelProviders;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private String TAG = this.getClass().getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

        TextView mTextView = findViewById(R.id.tvNumber);
//        MainActivityDataGenerator myData = new MainActivityDataGenerator();
        MainActivityViewModel model = ViewModelProviders.of(this).get(MainActivityViewModel.class);
        String myRandomNumber = model.getNumber();
        mTextView.setText(myRandomNumber);

        Log.i(TAG, "Random Number Set");
    }
}

위의 코드에서 ViewModelProviders.of(this).get(MainActivityViewModel.class) 를 통해 MainActivityDataGenerator obj를 가져와서 model.getNumber() 와 같이 method를 통해 값을 가져온다. method를 통해 가져오게 되는 값도 activity cycle과 무관하게 일정하게 유지 된다는 것에 유의한다.

MainActivityViewModel

package com.architecturecomponents.learn.s3_v2_viewmodeldemo;

import android.arch.lifecycle.ViewModel;
import android.util.Log;

import java.util.Random;

public class MainActivityViewModel extends ViewModel {

    private String TAG = this.getClass().getSimpleName();
    private String myRandomNumber;

    public String getNumber() {
        Log.i(TAG, "Get number");
        if (myRandomNumber == null) {
            createNumber();
        }
        return myRandomNumber;
    }

    private void createNumber() {
        Log.i(TAG, "Create new number");
        Random random = new Random();
        myRandomNumber = "Number: " + (random.nextInt(10 - 1) + 1);
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        Log.i(TAG, "ViewModel Destroyed");
    }
}