关于android:后台默默的劳动者探究服务

4次阅读

共计 12200 个字符,预计需要花费 31 分钟才能阅读完成。

服务作为 Android 四大组件之一,是一种可在后盾执行长时间运行操作而不提供界面的利用组件。服务可由其余利用组件启动,而且即便用户切换到其余利用,服务仍将在后盾持续运行。须要留神的是服务并不会主动开启线程,所有的代码都是默认运行在主线程当中的,所以须要在服务的外部手动创立子线程,并在这里执行具体的工作,否则就有可能呈现主线程被阻塞住的状况。

<!– more –>

Android 多线程编程

异步音讯机制

对于多线程编程其实和 Java 统一,无论是继承 Thread 还是实现 Runnable 接口都能够实现。在 Android 中须要把握的就是在子线程中更新 UI,UI 是由主线程来管制的,所以主线程又称为 UI 线程。

Only the original thread that created a view hierarchy can touch its views.

尽管不容许在子线程中更新 UI,然而 Android 提供了一套异步音讯解决机制,完满解决了在子线程中操作 UI 的问题,那就是应用 Handler。先来回顾一下应用 Handler 更新 UI 的用法:

public class MainActivity extends AppCompatActivity {
    private static final int UPDATE_UI = 1001;
    private TextView textView;

    private Handler handler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(@NonNull Message msg) {if(msg.what == UPDATE_UI) textView.setText("Hello Thread!");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.tv_main);
    }

    public void updateUI(View view) {// new Thread(()-> textView.setText("Hello Thread!")).start(); Error!
        new Thread(()->{Message message = new Message();
            message.what = UPDATE_UI;
            handler.sendMessage(message);
        }).start();}
}

应用这种机制就能够杰出地解决掉在子线程中更新 UI 的问题,上面就来剖析一下 Android 异步音讯解决机制到底的工作原理:Android 中的异步音讯解决次要由 4 个局部组成:Message,Handler,MessageQueue 和 Looper。
1、Message:线程之间传递的音讯,它能够在外部携带大量的信息,用于在不同线程之间替换数据。
2、Handler:解决者,它次要是用于发送和解决音讯的。发送音讯个别是应用 Handler 的 sendMessage() 办法,而收回的音讯通过一系列地辗转解决后,最终会传递到 Handler 的 handleMessage() 办法中。
3、MessageQueue:音讯队列,它次要用于寄存所有通过 Handler 发送的音讯。这部分音讯会始终存在于音讯队列中,期待被解决。每个线程中只会有一个 MessageQueue 对象。

4、Looper 是每个线程中的 MessageQueue 的管家,调用 Looper 的 loop() 办法后,就会进入到一个有限循环当中,而后每当发现 MessageQueue 中存在一条音讯,就会将它取出,并传递到 Handler 的 handleMessage() 办法中。每个线程中也只会有一个 Looper 对象。

异步音讯解决整个流程:首先须要在主线程当中创立一个 Handler 对象,并重写 handleMessage() 办法。而后当子线程中须要进行 UI 操作时,就创立一个 Message 对象,并通过 Handler 将这条音讯发送进来。之后这条音讯会被增加到 MessageQueue 的队列中期待被解决,而 Looper 则会始终尝试从 MessageQueue 中取出待处理音讯,最初散发回 Handler 的 handleMessage() 办法中。因为 Handler 是在主线程中创立的,所以此时 handleMessage() 办法中的代码也会在主线程中运行,于是咱们在这里就能够安心地进行 UI 操作了。整个异步音讯解决机制的流程如下图所示:

AsyncTask

不过为了更加不便咱们在子线程中对 UI 进行操作,Android 还提供了另外一些好用的工具,比方 AsyncTask。AsyncTask 背地的实现原理也是基于异步音讯解决机制,只是 Android 帮咱们做了很好的封装而已。首先来看一下 AsyncTask 的根本用法,因为 AsyncTask 是一个抽象类,所以如果咱们想应用它,就必须要创立一个子类去继承它。在继承时咱们能够为 AsyncTask 类指定 3 个泛型参数,这 3 个参数的用处如下:

Params:在执行 AsyncTask 时须要传入的参数,可用于在后台任务中应用。
Progress:后台任务执行时,如果须要在界面上显示以后的进度,则应用这里指定的泛型作为进度单位。
Result:当工作执行结束后,如果须要对后果进行返回,则应用这里指定的泛型作为返回值类型。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private final int REQUEST_EXTERNAL_STORAGE = 1;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void startDownload(View view) {verifyStoragePermissions(this);
        ProgressBar progressBar = findViewById(R.id.download_pb);
        TextView textView = findViewById(R.id.download_tv);
        new MyDownloadAsyncTask(progressBar, textView).execute("http://xxx.zip");
    }


    class MyDownloadAsyncTask extends AsyncTask<String, Integer, Boolean> {
        private ProgressBar progressBar;
        private TextView textView;

        public MyDownloadAsyncTask(ProgressBar progressBar, TextView textView) {
            this.progressBar = progressBar;
            this.textView = textView;
        }

        @Override
        protected Boolean doInBackground(String... strings) {String urlStr = strings[0];
            try {URL url = new URL(urlStr);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                InputStream inputStream = conn.getInputStream();
                // 获取文件总长度
                int length = conn.getContentLength();
                File downloadsDir = new File("...");
                File descFile = new File(downloadsDir, "xxx.zip");
                int downloadSize = 0;
                int offset;
                byte[] buffer = new byte[1024];
                FileOutputStream fileOutputStream = new FileOutputStream(descFile);
                while ((offset = inputStream.read(buffer)) != -1){
                    downloadSize += offset;
                    fileOutputStream.write(buffer, 0, offset);
                    
                    // 抛出工作执行的进度
                    publishProgress((downloadSize * 100 / length));
                }
                fileOutputStream.close();
                inputStream.close();
                Log.i(TAG, "download: descFile =" + descFile.getAbsolutePath());
            } catch (IOException e) {e.printStackTrace();
                return false;
            }
            return true;
        }

        // 在主线程中执行后果解决
        @Override
        protected void onPostExecute(Boolean aBoolean) {super.onPostExecute(aBoolean);
            if(aBoolean){textView.setText("下载实现,文件位于..xx.zip");
            }else{textView.setText("下载失败");
            }
        }

        // 工作进度更新
        @Override
        protected void onProgressUpdate(Integer... values) {super.onProgressUpdate(values);
            // 收到新进度,执行解决
            textView.setText("已下载" + values[0] + "%");
            progressBar.setProgress(values[0]);
        }

        @Override
        protected void onPreExecute() {super.onPreExecute();
            textView.setText("未点击下载");
        }
    }
}

1、onPreExecute():办法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比方显示一个进度条对话框等。

2、doInBackground():办法中的所有代码都会在子线程中运行,咱们应该在这里去解决所有的耗时工作。工作一旦实现就能够通过 return 语句来将工作的执行后果返回,如果 AsyncTask 的第三个泛型参数指定的是 Void,就能够不返回工作执行后果。留神,在这个办法中是不能够进行 UI 操作的,如果须要更新 UI 元素,比如说反馈当前任务的执行进度,能够调用 publishProgress() 办法来实现。

3、onProgressUpdate():当在后台任务中调用了 publishProgress() 办法后,onProgressUpdate() 办法就会很快被调用,该办法中携带的参数就是在后台任务中传递过去的。在这个办法中能够对 UI 进行操作,利用参数中的数值就能够对界面元素进行相应的更新。

4、onPostExecute():当后台任务执行结束并通过 return 语句进行返回时,这个办法就很快会被调用。返回的数据会作为参数传递到此办法中,能够利用返回的数据来进行一些 UI 操作,比如说揭示工作执行的后果,以及敞开掉进度条对话框等。

服务的根本用法

服务首先作为 Android 之一,天然也要在 Manifest 文件中申明,这是 Android 四大组件共有的特点。新建一个 MyService 类继承自 Service,而后再清单文件中申明即可。

服务的创立与启动

MyService.java:

public class MyService extends Service {
    private static final String TAG = "MyService";

    public MyService() {}
    
    @Override
    public IBinder onBind(Intent intent) {Log.i(TAG, "onBind:");
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.tim.basic_service">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        
        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true" />

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

能够看到,MyService 的服务标签中有两个属性,exported 属性示意是否容许除了以后程序之外的其余程序拜访这个服务,enabled 属性示意是否启用这个服务。而后在 MainActivity.java 中启动这个服务:

// 启动服务
startService(new Intent(this, MyService.class));

服务的进行 (销毁)

如何进行服务呢?在 MainActivity.java 中进行这个服务:

Intent intent = new Intent(this, MyService.class);
// 启动服务
startService(intent);
// 进行服务
stopService(intent);

其实 Service 还能够重写其余办法:

public class MyService extends Service {
    private static final String TAG = "MyService";

    public MyService() {}

    // 创立
    @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 IBinder onBind(Intent intent) {Log.i(TAG, "onBind:");
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    // 解绑
    @Override
    public void unbindService(ServiceConnection conn) {super.unbindService(conn);
        Log.i(TAG, "unbindService:");
    }

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

其实 onCreate() 办法是在服务第一次创立的时候调用的,而 onStartCommand() 办法则在每次启动服务的时候都会调用,因为方才咱们是第一次点击 Start Service 按钮,服务此时还未创立过,所以两个办法都会执行,之后如果再间断多点击几次 Start Service 按钮,就只有 onStartCommand() 办法能够失去执行了:

服务绑定与解绑

在下面的例子中,尽管服务是在流动里启动的,但在启动了服务之后,流动与服务根本就没有什么关系了。这就相似于流动告诉了服务一下:你能够启动了!而后服务就去忙本人的事件了,但流动并不知道服务到底去做了什么事件,以及实现得如何。所以这就要借助服务绑定了。

比方在 MyService 里提供一个下载性能,而后在流动中能够决定何时开始下载,以及随时查看下载进度。实现这个性能的思路是创立一个专门的 Binder 对象来对下载性能进行治理,批改 MyService.java:

public class MyService extends Service {
    private static final String TAG = "MyService";

    private DownloadBinder mBinder = new DownloadBinder();
    
    static class DownloadBinder extends Binder {public void startDownload() {
            // 模仿开始下载
            Log.i(TAG, "startDownload executed");
        }

        public int getProgress() {
            // 模仿返回下载进度
            Log.i(TAG, "getProgress executed");
            return 0;
        }
    }

    public MyService() {}

    // 创立
    @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 IBinder onBind(Intent intent) {Log.i(TAG, "onBind:");
        return mBinder;
    }

    // 解绑
    @Override
    public void unbindService(ServiceConnection conn) {super.unbindService(conn);
        Log.i(TAG, "unbindService:");
    }

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

MainActivity.java 如下:

public class MainActivity extends AppCompatActivity {

    private MyService.DownloadBinder downloadBinder;

    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();}

        @Override
        public void onServiceDisconnected(ComponentName name) {}};

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

    public void aboutService(View view) {int id = view.getId();
        Intent intent = new Intent(this, MyService.class);
        switch (id){
            case R.id.start_btn:
                startService(intent);
                break;
            case R.id.stop_btn:
                stopService(intent);
                break;
            case R.id.bind_btn:
                // 这里传入 BIND_AUTO_CREATE 示意在流动和服务进行绑定后主动创立服务
                bindService(intent, connection, BIND_AUTO_CREATE);
                break;
            case R.id.unbind_btn:
                unbindService(connection);
                break;
        }
    }
}

这个 ServiceConnection 的匿名类外面重写了 onServiceConnected() 办法和 onServiceDisconnected() 办法,这两个办法别离会在流动与服务胜利绑定以及解除绑定的时候调用。在 onServiceConnected() 办法中,通过向下转型失去 DownloadBinder 的实例,有了这个实例,流动和服务之间的关系就变得十分严密了。当初咱们能够在流动中依据具体的场景来调用 DownloadBinder 中的任何 public() 办法,即实现了指挥服务干什么服务就去干什么的性能(尽管实现 startDownload 与 getProgress 实现很简略)。

须要留神的是,任何一个服务在整个应用程序范畴内都是通用的,即 MyService 不仅能够和 MainActivity 绑定,还能够和任何一个其余的流动进行绑定,而且在绑定实现后它们都能够获取到雷同的 DownloadBinder 实例。

服务的生命周期

一旦调用了 startServices() 办法,对应的服务就会被启动且回调 onStartCommand(),如果服务未被创立,则会调用 onCreate() 创立 Service 对象。服务被启动后会始终放弃运行状态,直到 stopService() 或者 stopSelf() 办法被调用。不论 startService() 被调用了多少次,然而只有 Service 对象存在,onCreate() 办法就不会被执行,所以只须要调用一次 stopService() 或者 stopSelf() 办法就会进行对应的服务。

在通过 bindService() 来获取一个服务的长久连贯的时候,这时就会回调服务中的 onBind() 办法。相似地,如果这个服务之前还没有创立过,oncreate() 办法会先于 onBind() 办法执行。之后,调用方能够获取到 onBind() 办法里返回的 IBinder 对象的实例,这样就能自在地和服务进行通信了。只有调用方和服务之间的连贯没有断开,服务就会始终放弃运行状态。

那么即调用了 startService() 又调用了 bindService() 办法的,这种状况下该如何能力让服务销毁掉呢?依据 Android 零碎的机制,一个服务只有被启动或者被绑定了之后,就会始终处于运行状态,必须要让以上两种条件同时不满足,服务能力被销毁。所以,这种状况下要同时调用 stopService() 和 unbindService() 办法,onDestroy() 办法才会执行。

服务的更多技巧

下面讲述了服务最根本的用法,上面来看看对于服务的更高级的技巧。

应用前台服务

服务简直都是在后盾运行的,服务的零碎优先级还是比拟低的,当零碎呈现内存不足的状况时,就有可能会回收掉正在后盾运行的服务。如果你心愿服务能够始终放弃运行状态,而不会因为零碎内存不足的起因导致被回收,就能够应用前台服务。比方 QQ 电话的悬浮窗口,或者是某些天气利用须要在状态栏显示天气。

public class FrontService extends Service {
    String mChannelId = "1001";

    public FrontService() {}

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {super.onCreate();
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
        Notification notification = new NotificationCompat.Builder(this, mChannelId)
                .setContentTitle("This is content title.")
                .setContentText("This is content text.")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(),
                        R.mipmap.ic_launcher))
                .setContentIntent(pi)
                .build();
        startForeground(1, notification);
    }
}

应用 IntentService

服务中的代码都是默认运行在主线程当中的,如果间接在服务里去解决一些耗时的逻辑,就很容易呈现 ANR 的状况。所以须要用到多线程编程,遇到耗时操作能够在服务的每个具体的办法里开启一个子线程,而后在这里去解决那些耗时的逻辑。就能够写成如下模式:

public class OtherService extends Service {public OtherService() {}

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {new Thread(()->{// TODO 执行耗时操作}).start();
        return super.onStartCommand(intent, flags, startId);
    }
    ...
}

然而,这种服务一旦启动之后,就会始终处于运行状态,必须调用 stopService() 或者 stopSelf() 办法能力让服务停止下来。所以,如果想要实现让一个服务在执行结束后主动进行的性能,就能够这样写:

public class OtherService extends Service {public OtherService() {}

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {new Thread(()->{
            // TODO 执行耗时操作
            stopSelf();}).start();
        return super.onStartCommand(intent, flags, startId);
    }
    ...
}

尽管这种写法并不简单,然而总会有一些程序员遗记开启线程,或者遗记调用 stopSelf() 办法。为了能够简略地创立一个异步的、会主动进行的服务,Android 专门提供了一个 IntentService 类,这个类就很好地解决了后面所提到的两种难堪,上面咱们就来看一下它的用法:

MyIntentService.java

public class MyIntentService extends IntentService {
    private static final String TAG = "MyIntentService";
    private int count = 0;
    public MyIntentService() {super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        count++;
        Log.i(TAG, "onHandleIntent: count =" + count);
    }
}

MainActivity.java:

for (int i = 0; i < 10; i++) {Intent intent = new Intent(MainActivity.this, MyIntentService.class);
    startService(intent);
}

参考资料:《第一行代码》

原文地址:《后盾默默的劳动者,探索服务》

正文完
 0