Android: サービス コンポーネントとその単純なアプリケーション

サービスコンポーネント

  • UI を提供せずにバックグラウンドで長時間実行される操作を実行するアプリケーション コンポーネント
  • ユーザーが他のアプリケーションに切り替えると、サービスもバックグラウンドで実行されます。
  • アクティブに実行できません。サービスを実行するにはメソッドを呼び出す必要があります

作成

  1. Serviceクラスを継承したクラスを作成する
public class MyService1 extends Service {
    
    
    private static final String TAG = MyService1.class.getName();
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
    
    
        return null;
    }

    //生命周期相关函数
    @Override
    public void onCreate() {
    
    
        super.onCreate();
        Log.i(TAG, "onCreate: ");
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    
    
        Log.i(TAG, "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }
    @Override
    public void onDestroy() {
    
    
        Log.i(TAG, "onDestroy: ");
        super.onDestroy();
    }
    @Override
    public boolean onUnbind(Intent intent) {
    
    
        Log.i(TAG, "onUnbind: ");
        return super.onUnbind(intent);
    }
}
  1. Mainifast に登録する
<service android:name=".MyService1"/>

カテゴリー

  • Context.startService()

  • Context.bindService()

異なる起動方法は異なるライフサイクルに対応します

startService(インテント)

startService によって開始されたサービスは、デフォルトで無期限に実行されます (Context の stopService または Service の stopSelf メソッドによって停止できます)。

//开启Service
Intent intent = new Intent();
intent.setClass(this, MyService1.class);
startService(intent);

サービスが作成されていない場合は、2 つのライフサイクル関数 onCreate と OnStartCommand が呼び出されます。サービスがすでに作成されている場合は、onStartCommand のみを呼び出します

//停止Service
Intent intent = new Intent();
intent.setClass(this, MyService1.class);
stopService(intent);

サービスが停止すると、onDestroy ライフサイクル関数が呼び出され、サービスが破棄されます。

bindService(インテント サービス、ServiceConnection conn、int フラグ)

bindService によって開始されるサービスは、呼び出し元とサービスの間の典型的なクライアント/サーバー インターフェイスです。つまり、呼び出し元がクライアント、サービスがサーバーであり、サービスは 1 つだけですが、サービスに接続されているクライアントは次のことを行うことができます。1 つ以上である必要があります

ここで言うクライアントとは、アクティビティなどのコンポーネントを指すことに注意してください。

クライアントが破棄されると、クライアントは自動的に Service からバインド解除されます。もちろん、クライアントは Context の unbindService メソッドを明示的に呼び出して Service からバインド解除することもできます。

クライアントがサービスにバインドされていない場合、サービスはそれ自体を破棄します。

クライアントがサービスにバインドされている場合、stopService を使用してクライアントを破棄することはできません

//绑定服务
//若该服务未被创建则创建一个新服务

Intent intent = new Intent();
intent.setClass(this, MyService1.class);
//connection生命为MainActivity的成员变量  
// private ServiceConnection connection = null;
connection = new ServiceConnection() {
    
    
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
    
    
        // when Service connect
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
    
    
        //when Service disconnect
    }
};

// flag = BIND_AUTO_CREATE  自动创建Service
bindService(intent, connection, BIND_AUTO_CREATE);
//解绑服务
unbindService(connection);

Service コンポーネントを使用してアプリのダウンロードをシミュレートする

1. 新しいプロジェクトを作成し、Service クラスを継承する MyService クラスを作成します。

public class MyService extends Service {
    
    

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
    
    
        Log.i(TAG, "onBind: ");
        return null;
    }

    @Override
    public void onCreate() {
    
    
        Log.i(TAG, "onCreate: ");
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    
    
        Log.i(TAG, "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public boolean onUnbind(Intent intent) {
    
    
        Log.i(TAG, "onUnbind: ");
        return super.onUnbind(intent);
    }

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

ここでは、サービス実行の各ライフ サイクルが正常かどうかをテストするために、最初に Log.i print ステートメントを各重要なサイクル関数に追加するのが最善です。

2.MainAcitvityのonCreateでbindServiceバインディングサービスを呼び出す

//绑定服务
Intent bind_intent = new Intent();
bind_intent.setClass(this, MyService.class);
ServiceConnection connection = new ServiceConnection() {
    
    
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
    
    
        localBinder = (MyService.LocalBinder) service;
        Log.i(TAG, "onServiceConnected: ");
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
    
    
		Log.i(TAG, "onServiceDisconnected: ");
    }
};
bindService(bind_intent, connection, BIND_AUTO_CREATE);

3. activity_main.xml レイアウトに 2 つのボタンを追加します (それぞれ [ダウンロード開始] と [ダウンロードキャンセル])。カスタム ID と onClick イベント関数名に注意してください。

<Button
        android:id="@+id/DownloadButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="50dp"
        android:layout_marginBottom="20dp"
        android:layout_gravity="center_horizontal"
        android:onClick="StartDownload"
        android:text="开始下载" />

<Button
        android:id="@+id/CancelDownloadButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:onClick="CancelDownload"
        android:text="取消下载" />

4. これら 2 つの関数を MainActivity.java に記述します。

ダウンロードされたプログラムはサービスに保存されるため、クリック イベント StartDownload() で startService() を呼び出し、ダウンロードしたスレッドをサービスの onStartCommand ライフサイクル関数で実行します。

// MainActivity.java

//开始下载按钮点击事件
public void StartDownload(View view) {
    
    
    Intent intent = new Intent();
    intent.setClass(this, MyService.class);
    startService(intent);

}
// MyService.java

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    
    
    Log.i(TAG, "onStartCommand: ");
    myThread = new MyThread();
    myThread.start();
    return super.onStartCommand(intent, flags, startId);
}

ここで使用する MyThread クラスは、スレッド クラス Thread を継承しています (run メソッドは Thread で書き換えられ、スレッドの一時停止とハング解除は一時停止変数によって実現されます)。ここでは、MyThread クラスを MyService クラス (内部クラス) に書き込むだけです。

public class MyThread extends Thread{
    
    
        private final Object lock = new Object();
        private boolean pause = false;

        /**
         * 调用该方法实现线程的暂停
         */
        void pauseThread(){
    
    
            pause = true;
        }
        /*
        调用该方法实现恢复线程的运行
         */
        void resumeThread(){
    
    
            pause = false;
            synchronized (lock){
    
    
                lock.notify();
            }
        }
        /**
         * 这个方法只能在run 方法中实现,不然会阻塞主线程,导致页面无响应
         */
        void onPause() {
    
    
            synchronized (lock) {
    
    
                try {
    
    
                    lock.wait();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
        @Override
        public void run() {
    
    
            super.run();
            int index = 0;
            setDownloadStatus(true);
            ServiceSendStatusBroadCast();
            try {
    
    
                //模拟下载线程:30秒睡眠,这里使用30次for循环是为了每隔一秒能在界面中显示下载进度
                for(int i=0; i<30; i++){
    
    
                    //当pause为true时,调用onPause挂起该线程
                    while(pause) {
    
    
                        onPause();
                    }
                    TimeUnit.SECONDS.sleep(1);
                    Log.i(TAG, "run: "+i);//可以先打印是否能运行成功
                }
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }

ダウンロードをキャンセルしたい場合は、Thread の中断() メソッドを使用してスレッドを中断する必要がありますが、スレッドはサービスに保存されているため、ボタンのクリック イベントを通じてスレッドを直接中断する方法はありません。

そこで、バインドされたサービス (Service をバインドするときに実行される) で ServiceConnection の onServiceConnected メソッドを使用しました。サービスがバインドされると、このメソッドの IBinder クラスのパラメーター サービスを介して Binder クラスのインスタンスを受け取ることができます (型変換が必要です)。その後、localBinder.getService() を通じてバインドされた Service を取得できます。

// MainActivity.java
Intent bind_intent = new Intent();
bind_intent.setClass(this, MyService.class);
ServiceConnection connection = new ServiceConnection() {
    
    
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
    
    
        // 通过localBinder.getService()即可得到绑定的Service
        localBinder = (MyService.LocalBinder) service;
        Log.i(TAG, "onServiceConnected: ");
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
    
    
        Log.i(TAG, "onServiceDisconnected: ");
    }
};
bindService(bind_intent, connection, BIND_AUTO_CREATE);
// MyService.java
@Nullable
@Override
public IBinder onBind(Intent intent) {
    
    
    Log.i(TAG, "onBind: ");
    //onServiceConnected()返回的参数IBinder service 就是onBind的返回值
    //这里返回一个Binder类的继承类LocalBinder
    return new LocalBinder();
}

//创建本地Binder类用于返回MyService实例
public class LocalBinder extends Binder{
    
    
    //调用getService可以返回绑定的Service
    public MyService getService(){
    
    
        return MyService.this;
    }
}

したがって、このメソッドを使用すると、サービスのダウンロード スレッドがボタンのクリック イベントで中断される可能性があります。

// MainActivity.java

//取消下载
public void cancelDownload(){
    
    
    if(myThread != null){
    
    
        myThread.interrupt();
    }
}

この時点で、APP を実行し、LogCat インターフェイスがダウンロードを開始およびキャンセルできるかどうかを確認します。(ダウンロードをキャンセルする場合、通常は割り込み例外InterruptedExceptionがスローされます)

5. ダウンロード中はダウンロード開始ボタンを無効に設定し、ダウンロードしていない場合はダウンロードキャンセルボタンを無効に設定します

これは難しいことではありませんが、途中で学習した Android ローカル ブロードキャスト メカニズムと組み合わせる必要があります。

基本的な考え方は、サービスにメンバー変数を記述して、ダウンロード中かどうかを示すことです (MainActivity で定義しないでください。そうしないと、アプリがバックグラウンドに切り替わってから元に戻ると変数がリセットされる可能性があります)。ダウンロード開始(ダウンロードキャンセル)ボタンをクリックすると、ダウンロード状況に応じて変数の値を変更し、ローカルブロードキャスト LocalBroadcast を送信してボタンの状態を変更するように MainActivity に通知します(前述したように、MyService のプロパティまたはメソッドを取得します)ローカルバインダー)

public class MyService extends Service {
    
    
    private boolean DownloadStatus = false;
	public boolean getDownloadStatus() {
    
    
        return DownloadStatus;
    }
    public void setDownloadStatus(boolean downloadStatus) {
    
    
        DownloadStatus = downloadStatus;
    }
    
    public class MyThread extends Thread{
    
    

      // ...
        @Override
        public void run() {
    
    
            super.run();
            int index = 0;
            setDownloadStatus(true);//设置下载状态变量
            ServiceSendStatusBroadCast();//发送本地广播
            try {
    
    
                for(int i=0; i<30; i++){
    
    
                    //当pause为true时,调用onPause挂起该线程
                    while(pause) {
    
    
                        onPause();
                    }
                    TimeUnit.SECONDS.sleep(1);
                }
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            setDownloadStatus(false);//下载结束或被中断,设置下载状态变量
            ServiceSendStatusBroadCast();//发送本地广播

        }
    }
    //取消下载
    public void cancelDownload(){
    
    
        if(myThread != null){
    
    
            //中断下载线程,将下载状态置为false, 发送本地广播消息通知app更改按钮状态
            myThread.interrupt();
            
            setDownloadStatus(false);//设置下载状态变量
            ServiceSendStatusBroadCast();//发送本地广播
        }
    }
}

// MainActivity.java

public class MainActivity extends AppCompatActivity {
    
    
	public Button DownloadButton;
    public Button CancelButton;
	@Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DownloadButton = (Button) findViewById(R.id.DownloadButton);
        CancelButton = (Button) findViewById(R.id.CancelDownloadButton);

        //...
        
        //注册用于接收Service下载状态通知的本地广播
        IntentFilter statusFilter = new IntentFilter();
        statusFilter.addAction("DOWNLOAD_STATUS_CHANGED");
        BroadcastReceiver statusReceiver = new BroadcastReceiver() {
    
    
            @Override
            public void onReceive(Context context, Intent intent) {
    
    
                //在接收时根据MyService中的下载状态成员变量改变按钮状态
                MonitorDownloadStatus(localBinder.getService().getDownloadStatus());
            }
        };
        LocalBroadcastManager.getInstance(this).registerReceiver(statusReceiver, statusFilter);
        
    }
    
	//根据下载状态布尔值参数改变布局中按钮的状态
    public void MonitorDownloadStatus(boolean status){
    
    
        if(status){
    
    
            //正在下载
            DownloadButton.setEnabled(false);
            CancelButton.setEnabled(true);
        }else{
    
    
            //未在下载
            DownloadButton.setEnabled(true);
            CancelButton.setEnabled(false);
        }
    }
}

アプリを実行し、LogCat 印刷情報が期待どおりであるかどうかを観察します (ボタンの使用可能な状態が変わらない場合は、ローカル ブロードキャストが送信されているかどうか、およびダウンロード状態が変化するたびにダウンロード状態変数に従ってボタンの状態が変更されるかどうかを確認します) )。

6. ダウンロードの進行状況バーを表示する

プログレスバーのダウンロードの考え方はダウンロードステータスの変更と同じで、ローカルブロードキャストを通じて毎秒(スレッド内のforループごと)にint型の進捗状況が送信されます。MainActivity は、ブロードキャスト受信後の進行状況の値に応じて、ProgressBar コントロールの値を変更できます。

7. Java スレッド クラス Thread を使用して wait() を一時停止し、notify() を再開して、ダウンロードを一時停止および再開します。

MyThread クラスの一時停止メソッドと一時停止解除メソッドを呼び出します。次に、古いルールでは、MainActivity で localBinder.getService() を使用します。

// MyService.java
public class MyService extends Service {
    
    
    // ...
    public void pauseDownload(){
    
    
            myThread.pauseThread();
            setDownloadStatus(false);
            ServiceSendStatusBroadCast();
    }
    public void resumeDownload(){
    
    
            myThread.resumeThread();
            setDownloadStatus(true);
            ServiceSendStatusBroadCast();
    }
}
public class MainActivity extends AppCompatActivity {
    
    
	// ...
    public void ContinueDownload(View view) {
    
    
    	localBinder.getService().resumeDownload();
    }
    public void SuspendDownload(View view) {
    
    
        localBinder.getService().pauseDownload();
    }
}
<Button
        android:id="@+id/SuspendButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="50dp"
        android:layout_marginBottom="20dp"
        android:layout_gravity="center_horizontal"
        android:onClick="SuspendDownload"
        android:text="暂停下载" />

<Button
        android:id="@+id/ContinueButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:onClick="ContinueDownload"
        android:text="继续下载" />

ダウンロードを一時停止し、ダウンロードの実行を再開するときは、ダウンロード状態変数に従ってボタンの状態を変更することを忘れないでください。

実行して、模擬ダウンロード アプリがほぼ完成しました。

やっと

ここでソース コードが必要です。Android
サービス コンポーネント アプリケーション シミュレーションをダウンロードしてください。

おすすめ

転載: blog.csdn.net/Dae_Lzh/article/details/121585403