Android入门笔记(多线程和服务)

服务是 Android 中实现程序后台的解决方案,它非常适合执行那些不需要和用户交互而且还要求长期运行的任务。不过需要注意的是,服务并不是运行在一个独立的进程当中,而是依赖于创建服务是所在的引用程序进程,当某个应用程序进程被杀掉时,所有依赖于该进程的服务都会停止运行。
另外,实际上服务并不会自动开启线程,所有的代码都是默认运行在主线程当中,即 需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就可能有主线程被阻塞的情况。

1 Android 多线程编程

当我们执行一些耗时操作的时候,比如请求一些网络请求时,服务器未必会立刻响应请求,如果不将这些逻辑放置在子线程当中,就会导致主线程被阻塞。

1.1 线程的基本用法

Android 多线程编程其实与 Java 的多线程都是使用相同的语法,即定义一个线程只需创建一个类继承自 Thread,并重写 run 方法,里面就可以编写耗时逻辑。如:

class MyThread extends Thread {
	@override
	public void run() {
		// 具体逻辑
	}
}

然后 newMyThread 实例,再调用 start() 方法即可,如:

new MyThread().start();

可是使用继承的方式导致的 耦合性 高,所以更多时候会使用实现 Runnable 接口的方式,如:

class MyThread implements Runnable {
	@Override
	public void run() {
		// 处理具体逻辑
	}
}

此时启动的线程的方式就如下:

MyThread myThread = new MyThread();
new Thread(myThread).start();

1.2 异步消息处理方法

Android 的 UI 是线程不安全的,即如果想要更新应用程序中的 UI 元素就需要在主线程中进行,否则会出现异常,所以就需要使用使用一些异步消息处理的使用方法。

1.2.1 异步消息处理机制

Android 中的异步消息处理主要由 4 个部分组成,分别是:

  • MessageMessage 是在线程之间传递的消息,可以携带少量的消息,用于在不同的线程中交换消息,消息的携带可以放置在 Messagewhat 字段、arg1 字段、arg2 字段(这三个字段携带整型数据)和 obj 字段(携带 Object 对象)
  • HandlerHandler 主要用于发送和处理消息,其中 sendMessage() 方法用来发送消息,handleMessage() 方法用来处理信息
  • MessageQueue:消息队列,用来存放所有通过 Handler 发送的消息,这些消息会存在于消息队列中,等待被处理,而每个线程中只有一个 MessageQueue 对象
  • Looper:每个线程中的 MessageQueue 的管家,其中的 loop() 方法,就会进入无限循环中,此时每当 MessageQueue 中存在一个消息就会将它取出并传递到 HandlerhandleMessage() 方法中,而每个线程中只有一个 Looper 对象

在了解完异步消息处理的组成部分之后,就可以梳理一下它的流程:

  • 首先在主线程中创建 Handler 对象,并重写 handleMessage() 方法
  • 创建子线程,当子线程需要通知主线程进行某些操作比如 UI 操作时,就创建一个 Message 对象,并通过 Handler 对象将该消息发送出去
  • 此时,这条消息会被添加到 MessageQueue 队列中等待被处理,而 Looper 则会一直尝试从 MessageQueue 中取出待处理消息,最后分发到 HandlerhandleMessage() 方法中
public class MainActivity extends AppCompatActivity {

    // 用于消息处理者识别具体是哪条消息
    public static final int UPATE_TEXT = 1;

    // 消息处理者,注意需要在主线程中创建 handler 对象
    private Handler handler = new Handler() {
        public void handleMessage(Message message) {
            switch (message.what) {
                case UPATE_TEXT:
                    // 修改 UI
                    ((TextView) findViewById(R.id.text)).setText("1234");
            }
        }
    };

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

        Button button = (Button) findViewById(R.id.btn);
        button.setOnClickListener(new View.OnClickListener() {
            // 创建线程
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                       // 发送消息
                        Message message = new Message();
                        // 设置消息
                        message.what = UPATE_TEXT;
                        // 可以设置 obj 携带对象
                        message.obj = (TextView) findViewById(R.id.text);
                        handler.sendMessage(message);
                    }
                }).start();
            }
        });
    }
}

在上一章中的 runOnUiThread() 方法其实就是一个异步消息处理机制的接口封装

1.2.2 AsyncTask

为了更方便在子线程中进行 UI 操作,Android 还提供了一些工具如 AsyncTaskAsyncTask 的实现原理基于异步消息处理机制,Android 只是帮忙做了封装。
AsyncTask 是一个抽象类,要使用它就必须创建一个子类继承它,继承的时候可以指定 3 个泛型参数:

  • Params:在执行 AsyncTask 时需要传入的参数,可在后台任务中使用
  • Progress:后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位
  • Result:当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型

同时还需要重写 AsyncTask 的 4 个方法才能正常地使用 AsyncTask

  • onPreExecute():这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作
  • doInBackground(Params...):这个方法中的所有代码都会在子线程中运行(即该方法不能进行 UI 操作),任务完成后可以通过 return 语句返回执行结果,此时如果 AsyncTask 的第三个泛型参数设置为 void,则可以不返回任务执行结果
  • onProgressUpdate(Progress...)在后台任务中调用 publishProgress(Progress...) 方法之后onProgressUpdate(Progress...) 这个方法很快就会被调用,该方法中携带的参数就是在后台任务中传递过来的,在这个方法中可以对 UI 进行操作
  • onPostExecute(Result):当后台任务执行完毕之后并通过 return 语句进行了返回时,这个方法就会很快被调用,返回的数据会作为参数传递到此方法中,此时也可以进行 UI 操作

例子代码如下:

/**
     * 设置了三个泛型参数,意思是:
     * 无需传入参数给后台任务
     * 使用整型类型作为进度显示
     * 返回布尔类型作为反馈结果
*/
class TestTask extends AsyncTask<Void, Integer, Boolean> {

    /**
     * 后台任务开始时调用
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        Log.d("后台服务输出台", "后台服务开始");
    }

    /**
     * 后台任务具体逻辑
     * @param voids
     * @return
     */
    @Override
    protected Boolean doInBackground(Void... voids) {
        Log.d("后台服务输出台", "后台服务执行中。。。");
        publishProgress(null);
        return null;
    }

    /**
     * 在 doInBackground 方法中调用 publishProgress() 方法后调用,本方法在主线程中执行
     * 可以执行 UI 操作
     * @param values
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        // 执行 UI 操作
        ((TextView) findViewById(R.id.text)).setText("AsyncTask 更新 UI");
        Log.d("后台服务输出台", "返回主线程中执行某些逻辑");
    }
    
    /**
     * 在 doInBackground 方法执行完毕并通过了 return 语句返回时调用
     * @param aBoolean
     */
    @Override
    protected void onPostExecute(Boolean aBoolean) {
        Log.d("后台服务输出台", "后台服务执行完成");
        super.onPostExecute(aBoolean);
    }
}

调用 new TestTask().execute(); 即可启动任务。

2 服务

2.1 创建、启动和停止服务

Android Studio 可以使用快捷方式创建服务 Service,右键项目点击 new -> Service -> Service 即可。
其中弹出框中的 Exported 属性表示是否允许当前程序之外的其它程序访问这个服务Enabled 属性表示是否启动这个服务。
Service 中可以重写三个常用的生命周期方法,分别是:

  • onCreate():服务创建时调用,只在服务第一次创建的时候调用
  • onStartCommand():服务启动时调用,每次启动服务都会调用
  • onDestory():服务销毁时调用,这里可以回收不再使用的资源
public class TestService extends Service {

    /**
     * 服务创建时调用
     */
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("TestService 服务输出台", "创建服务");
    }

    /**
     * 服务启动时调用
     * @param intent
     * @param flags
     * @param startId
     * @return
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("TestService 服务输出台", "服务启动");
        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * 服务销毁时启动
     */
    @Override
    public void onDestroy() {
        Log.d("TestService 服务输出台", "关闭服务");
        super.onDestroy();
    }

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

服务也有着作为 Android 四大组件的共有特点,那就是需要在 AndroidManifest.xml 中进行注册才能生效,不过如果使用快捷方式创建服务,那么 AS 就会自动地帮忙注册,如:

<service
    android:name=".services.TestService"
    android:enabled="true"
    android:exported="true"></service>

而启动和关闭服务主要是借助 Intent 实现,就类似打开新的活动。
首先构建一个 Intent 对象,并传入到 startService() 方法来构建和启动服务,同样的使用 stopService() 来停止服务,startServicestopService 方法都是已定义在 Context 类中,所以在活动中可以直接调用者两个方法。
而服务自身也可以在任何位置中调用 stopSelf() 方法能够让自身服务停止下来。
如:

// 开启服务
Intent startIntent = new Intent(MainActivity.this, TestService.class);
startService(startIntent);
// 关闭服务
Intent stopIntent = new Intent(MainActivity.this, TestService.class);
stopService(startIntent);

2.2 活动和服务进行通信

上述代码虽然服务是在活动启动的,但启动活动之后,此时服务或许一直处于运行状态,但活动与服务之间就没有关系了,服务具体运行的逻辑,活动就控制不了。
那如何能在活动中指挥服务呢?

  • 首先需要创建一个 Binder 的子类,它用于声明一系列活动需要调用服务运行的逻辑
  • 然后重写服务中 onBind 方法了,它就用来返回上面的 Binder 子类 实例
  • 接着就需要绑定活动和服务了,在绑定之后活动就可以调用 Binder 实例的一系列逻辑了
  • 绑定的第一步就是,在创建一个 ServiceConnection 类,重写 onServiceConnected() 方法和 onServiceDisconnected() 方法,前者会在活动与服务成功绑定时调用,而后者当服务所在进程 crash 或者被 kill 的时候才会被呼叫,以及断开的时候调用,,在 onServiceConnected 方法中可以通过向下转型得到由服务初始化而来的 Binder 实例,有了这个实例,就可以实现活动指挥服务了
  • 而实际绑定的逻辑就是使用 Context 中声明的 bindService 方法即可,该方法接受三个参数,分别是:Intent 对象,ServiceConnection 对象,标志位(传入 BIND_AUTO_CREATE 表示在活动和服务进行绑定之后自动创建服务,此时服务的 onCreate() 会执行,而 onStartCommand() 则不会执行)

在服务类中声明 Binder 类并初始化:

public class TestService extends Service {

    // 初始化 binder 实例
    private TestBinder testBinder = new TestBinder();

    // 声明 binder 子类
    public class TestBinder extends Binder {
        public void startDownload() {
            Log.d("binder 输出台:", "开始模拟下载");
        }
        public void stopDownload() {
            Log.d("binder 输出台", "终止模拟下载");
        }
    }

    /**
     * 返回 binder 实例
     * @param intent
     * @return
     */
    @Override
    public IBinder onBind(Intent intent) {
        return testBinder;
    }

    /**
     * 服务创建时调用
     */
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("TestService 服务输出台", "创建服务");
    }

    /**
     * 服务启动时调用
     * @param intent
     * @param flags
     * @param startId
     * @return
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("TestService 服务输出台", "服务启动");
        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * 服务销毁时启动
     */
    @Override
    public void onDestroy() {
        Log.d("TestService 服务输出台", "关闭服务");
        super.onDestroy();
    }
}

绑定服务代码:

public class MainActivity extends AppCompatActivity {

    // 存储初始化后的 TestBinder
    private TestService.TestBinder testBinder;

    // 初始化 ServiceConnection 对象,主要声明绑定服务时的一些生命周期函数
    // 在这里可以获取到服务中初始化的 binder 实例
    private ServiceConnection connection = new ServiceConnection() {

        /**
         * 绑定成功
         * @param name
         * @param service
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 获取到服务中的 Binder 对象实例
            testBinder = (TestService.TestBinder) service;
            // 连接成功后,可以立即调用 Binder 实例的方法
            testBinder.startDownload();
        }

        /**
         * 当 service 所在进程 crash 或者被 kill 的时候,onServiceDisconnected 才会被呼叫
         * @param name
         */
        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d("服务解绑", "服务解绑了");
        }
    };

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

        Button button4 = (Button) findViewById(R.id.btn5);
        button4.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 绑定服务
                Intent intent = new Intent(MainActivity.this, TestService.class);
                bindService(intent, connection, BIND_AUTO_CREATE);
            }
        });

        Button button5 = (Button) findViewById(R.id.btn6);
        button5.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 解绑服务
                unbindService(connection);
            }
        });
    }
}

注:任何一个服务在整个应用程序范围内都是通用的,即一个服务和一个活动进行绑定之后,还可以和任何一个其他活动进行绑定,绑定之后,这些活动获取到的 Binder 实例都是相同的。
当调用 startService() 方法之后,再调用 stopService() 方法,此时服务中的 onDestroy() 方法就会被执行,类似的,调用 bindSerivce() 方法后再调用 unbindService() 方法,服务中的 onDestroy() 方法也会执行,而一个服务只要被启动或者被绑定之后,就会一直处于运行状态,所以如果服务开启且被绑定,此时就需要调用 stopServiceunbindService() 方法,onDestory() 方法才会执行

2.3 前台服务

服务几乎都是后台运行的,同时它的系统优先级低,当系统出现内存不足的情况时,就可能会回收正在后台运行的服务,如果想避免这样的情况发生,可以使用前台服务。它会一直有一个正在运行的图标在系统的状态栏显示,类似于通知的效果。
创建一个前台服务只需在声明服务类时修改它的 onCreate() 方法即可,在方法里创建一个通知并调用 startForeground() 方法,该方法接受两个参数,第一个参数是通知的 id,第二个就是通知 Notification 对象实例本身了。

@Override
public void onCreate() {
    super.onCreate();
    Log.d("TestService 服务输出台", "创建服务");
    Intent intent = new Intent(this, MainActivity.class);
    PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
    Notification notification = new NotificationCompat.Builder(this, null)
            .setContentTitle("this is content title")
            .setContentText("this is content text")
            .setWhen(System.currentTimeMillis())
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentIntent(pi)
            .build();
    // 调用 startForeground 将本服务设置为前台服务
    startForeground(1, notification);
}

2.4 IntentService

如果想要实现让一个服务在执行完毕后自动停止的功能,可以这么写(只显示 onStartCommand() 方法中的代码):

/**
 * 服务启动时调用
 * @param intent
 * @param flags
 * @param startId
 * @return
 */
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.d("TestService 服务输出台", "服务启动");
    new Thread(new Runnable() {
        @Override
        public void run() {
            // 处理具体的逻辑
            // 暂停服务
            stopSelf();
        }
    }).start();
    return super.onStartCommand(intent, flags, startId);
}

Android 专门提供了一个 IntentService 类,它可以达到简单地创建一个异步、会自动停止的服务。
创建一个 IntentService 类的子类,它需要提供一个无参的构造函数,并在其内部调用父类的有参构造函数,实现 onHandleIntent() 抽象方法,这个方法中的逻辑都是在子线程中运行。
例子代码如下:

public class MyIntentService extends IntentService {
	
	//
	public MyIntentService() {
		super("MyIntentService");
	}
	
	@Override
	protected void onHandleIntent(Intent intent) {
		// 打印当前线程的 id
		Log.d("MyIntentService", "线程id是:" + Thread.currentThread().getId());
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		Log.d("MyIntentService", "onDestroy executed");
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值