Skip to content

Commit

Permalink
to study Service Life Cycle.
Browse files Browse the repository at this point in the history
  • Loading branch information
remixgrjp committed Oct 7, 2021
1 parent d412ded commit a7ddd1d
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 6 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -13,3 +13,4 @@
.externalNativeBuild
.cxx
local.properties
/app/release
15 changes: 15 additions & 0 deletions README.md
@@ -0,0 +1,15 @@
# to study Service Life Cycle

<img width="300px" src="./Service.gif" alt="ffmpeg -i Service.mp4 -vf scale=320:-1 -r 10 Service.gif">

fit Android 8.0(API 26), Android 9.0(API 28) <a href="Service.V1.0.apk">Install APK</a>.

## confirmed
HTC HTL23 (Android 5.0.2)

Samsung Galaxy S7 edge (Andorid 7.0)

ASUS_X01AD(Android 9)

## article
https://www.remix.asia/blog/remix/2020/12/post_250.html
Binary file added Service.V1.0.apk
Binary file not shown.
Binary file added Service.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions app/src/main/AndroidManifest.xml
Expand Up @@ -4,6 +4,9 @@
package="asia.remix.service"
>

<!-- API28 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
Expand All @@ -19,6 +22,12 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<service
android:name=".MainService"
android:enabled="true"
android:exported="false"
/>
</application>

</manifest>
69 changes: 67 additions & 2 deletions app/src/main/java/asia/remix/service/MainActivity.java
@@ -1,14 +1,79 @@
package asia.remix.service;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity{
final static String TAG = "MainActivity";

@Override
protected void onCreate( Bundle savedInstanceState ){
super.onCreate( savedInstanceState );
setContentView( R.layout.activity_main );
}

boolean isServiceBound = false;
MainService mainService;
ServiceConnection connection = new ServiceConnection(){
@Override
public void onServiceConnected( ComponentName cn, IBinder ib ){
Log.d( TAG, "ServiceConnection#onServiceConnected()" );
MainService.MainBinder binder = (MainService.MainBinder)ib;
mainService = binder.getService();
isServiceBound = true;
}

@Override
public void onServiceDisconnected( ComponentName cn ){
Log.d( TAG, "ServiceConnection#onServiceDisconnected() Serviceが深刻なエラーで停止した" );
unbindMyService();
}
};

void unbindMyService(){
if( isServiceBound ){
isServiceBound = false;
unbindService( connection );
}
}

public void onClickServiceStart( View v ){
Intent intent = new Intent( getApplication(), MainService.class );
if( Build.VERSION.SDK_INT >= 26 ){
startForegroundService( intent );
}else{
startService( intent );
}
}

public void onClickServiceBind( View v ){
Intent intent = new Intent( getApplication(), MainService.class );
bindService( intent, connection, Context.BIND_AUTO_CREATE );
}

public void onClickServiceMethod( View v ){
if( isServiceBound ){
mainService.doMethod( "Hello MyService!" );
}
}

public void onClickServiceUnbind( View v ){
unbindMyService();
}

public void onClickServiceStop( View v ){
unbindMyService();
Intent intent = new Intent( getApplication(), MainService.class );
stopService( intent );
}
}
116 changes: 116 additions & 0 deletions app/src/main/java/asia/remix/service/MainService.java
@@ -0,0 +1,116 @@
package asia.remix.service;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;

public class MainService extends Service{
final static String TAG = "MainService";
String sChannelId = "Channel ID";

@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" );
Context context = getApplicationContext();
Toast.makeText( context, "onStartCommand", Toast.LENGTH_SHORT ).show();
String sTitle = context.getString( R.string.app_name );

if( Build.VERSION.SDK_INT >= 26 ){
//API26 Android 8以上 通知を送信する前に通知チャネルを作成する必要がある
NotificationChannel channel
= new NotificationChannel( sChannelId, sTitle, NotificationManager.IMPORTANCE_DEFAULT );
NotificationManager notificationManager
= (NotificationManager)context.getSystemService( Context.NOTIFICATION_SERVICE );
notificationManager.createNotificationChannel( channel );
}

NotificationCompat.Builder builder;
if( Build.VERSION.SDK_INT >= 26 ){
builder = new NotificationCompat.Builder( context, sChannelId );
}else{
builder = new NotificationCompat.Builder( context );
}
builder.setContentTitle( sTitle );
builder.setSmallIcon( android.R.drawable.btn_star );
builder.setColor( android.graphics.Color.RED );
builder.setContentText( "Text" );
builder.setSubText( "SubText" );
builder.setContentInfo( "Info" );
builder.setTicker( "Ticker" );//通知到着時に通知バー表示(4.4まで)
//通知はタップ応答する必要があり、ここではMainActivityを立ち上げる
Intent i =new Intent( this, MainActivity.class );
PendingIntent pendingIntent = PendingIntent.getActivity( context, 0, i, 0 );
builder.setContentIntent( pendingIntent );

Notification notification = builder.build();
startForeground( 1, notification );//0は通知表示されない

return super.onStartCommand( intent, flags, startId );
}

// オーバーライド必須、バインド不要の場合はnullを返す。
// IBinderを継承したクラスを返す必要がある
@Override
public IBinder onBind( Intent intent ){
Log.d( TAG, "●onBind" );
Toast.makeText( getApplicationContext(), "onBind", Toast.LENGTH_SHORT ).show();
return new MainBinder();
}

@Override
public boolean onUnbind( Intent intent ){
Log.d( TAG, "●onUnbind" );
Toast.makeText( getApplicationContext(), "onUnbind", Toast.LENGTH_SHORT ).show();
return true;
}

@Override
public void onRebind( Intent intent ){
Log.d( TAG, "●onRebind" );
Toast.makeText( getApplicationContext(), "onRebind", Toast.LENGTH_SHORT ).show();
}

@Override
public void onDestroy(){
Log.d( TAG, "●onDestroy" );
Toast.makeText( getApplicationContext(), "onDestroy", Toast.LENGTH_SHORT ).show();
super.onDestroy();
}

public class MainBinder extends Binder{
public MainService getService(){
return MainService.this;
}
}

public void doMethod( String msg ){
Log.d( TAG, "●doMethod" );
Toast.makeText( getApplicationContext()
, String.format( "isUiThread %s / %s", isUiThread(), msg ), Toast.LENGTH_SHORT ).show();
}

boolean isUiThread(){
return Thread.currentThread() == Looper.getMainLooper().getThread();
}
}
54 changes: 50 additions & 4 deletions app/src/main/res/layout/activity_main.xml
Expand Up @@ -8,13 +8,59 @@
tools:context=".MainActivity"
>

<TextView
android:layout_width="wrap_content"
<Button
android:id="@+id/startService"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
android:text="Start Service"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:onClick="onClickServiceStart"
/>

<Button
android:id="@+id/bindService"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Bind Service"
app:layout_constraintTop_toBottomOf="@id/startService"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:onClick="onClickServiceBind"
/>

<Button
android:id="@+id/callServiceMethod"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Call Bound Service's Method"
app:layout_constraintTop_toBottomOf="@id/bindService"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:onClick="onClickServiceMethod"
/>

<Button
android:id="@+id/unbindService"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Unbind Service"
app:layout_constraintTop_toBottomOf="@id/callServiceMethod"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:onClick="onClickServiceUnbind"
/>

<Button
android:id="@+id/stopService"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Stop Service"
app:layout_constraintTop_toBottomOf="@id/unbindService"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:onClick="onClickServiceStop"
/>

</androidx.constraintlayout.widget.ConstraintLayout>

0 comments on commit a7ddd1d

Please sign in to comment.