10.6.3. Service开关

优质
小牛编辑
130浏览
2023-12-01

10.6.3.Service开关

在这个地方分别提供开/关两个按钮,不如只留一个按钮做开关。因此需要修改菜单,并为BaseActivity添加一个onMenuOpened()方法来动态控制按钮文本以及图标的变化。

首先修改munu.xml文件,添加新的菜单项也就是开关按钮。这时原先启动/关闭Service的两个按钮已经没必要了,删除即可。

例 10.10. res/menu/menu.xml

<?xml version="1.0" encoding="utf-8"?>

<menu xmlns:android="http://schemas.android.com/apk/res/android">

<item android:id="@+id/itemStatus" android:title="@string/titleStatus"

android:icon="@android:drawable/ic_menu_edit"></item>

<item android:title="@string/titleTimeline" android:id="@+id/itemTimeline"

android:icon="@android:drawable/ic_menu_sort_by_size"></item>

<item android:id="@+id/itemPrefs" android:title="@string/titlePrefs"

android:icon="@android:drawable/ic_menu_preferences"></item>

<item android:icon="@android:drawable/ic_menu_delete"

android:title="@string/titlePurge" android:id="@+id/itemPurge"></item>

<!-- -->

<item android:id="@+id/itemToggleService" android:title="@string/titleServiceStart"

android:icon="@android:drawable/ic_media_play"></item>

</menu>

  1. 将原先的itemServiceStart与itemServiceStop替换为itemToggleService。

例 10.11. BaseActivity.java

package com.marakana.yamba5;

import android.app.Activity;

import android.content.Intent;

import android.os.Bundle;

import android.view.Menu;

import android.view.MenuItem;

import android.widget.Toast;

/**

* The base activity with common features shared by TimelineActivity and

* StatusActivity

*/

public class BaseActivity extends Activity { //

YambaApplication yamba; //

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

yamba = (YambaApplication) getApplication(); //

}

// Called only once first time menu is clicked on

@Override

public boolean onCreateOptionsMenu(Menu menu) { //

getMenuInflater().inflate(R.menu.menu, menu);

return true;

}

// Called every time user clicks on a menu item

@Override

public boolean onOptionsItemSelected(MenuItem item) { //

switch (item.getItemId()) {

case R.id.itemPrefs:

startActivity(new Intent(this, PrefsActivity.class)

.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT));

break;

case R.id.itemToggleService:

if (yamba.isServiceRunning()) {

stopService(new Intent(this, UpdaterService.class));

} else {

startService(new Intent(this, UpdaterService.class));

}

break;

case R.id.itemPurge:

((YambaApplication) getApplication()).getStatusData().delete();

Toast.makeText(this, R.string.msgAllDataPurged, Toast.LENGTH_LONG).show();

break;

case R.id.itemTimeline:

startActivity(new Intent(this, TimelineActivity.class).addFlags(

Intent.FLAG_ACTIVITY_SINGLE_TOP).addFlags(

Intent.FLAG_ACTIVITY_REORDER_TO_FRONT));

break;

case R.id.itemStatus:

startActivity(new Intent(this, StatusActivity.class)

.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT));

break;

}

return true;

}

// Called every time menu is opened

@Override

public boolean onMenuOpened(int featureId, Menu menu) { //

MenuItem toggleItem = menu.findItem(R.id.itemToggleService); //

if (yamba.isServiceRunning()) { //

toggleItem.setTitle(R.string.titleServiceStop);

toggleItem.setIcon(android.R.drawable.ic_media_pause);

} else { //

toggleItem.setTitle(R.string.titleServiceStart);

toggleItem.setIcon(android.R.drawable.ic_media_play);

}

return true;

}

}

  1. BaseActivity是个Activity。
  2. 声明一个共享的YambaApplication对象,使之可为所有子类访问。
  3. 在onCreate()中获得yamba的引用。
  4. 将StatusActivity的onCreateOptionsMenu挪到了这里。
  5. onOptionsItemSelected也是同样来自StatusActivity。不过留意下这里的变动:它不再检查启动/停止Service的两个条目,改为检查itemToggleService一个条目。通过yamba中的标志变量我们可以得到Service的运行状态,基于此,决定启动还是关闭。
  6. onMenuOpened()是个新加入的方法,它在菜单打开时为系统所调用,参数menu即我们的选项菜单。我们可以在这里控制开关的显示。
  7. 在menu对象中找到我们新建的开关条目。
  8. 检查Service是否正在运行,如果是,则设置对应的标题与图标。留意我们在这里是通过Java的API手工地修改GUI的内容,而无关xml。
  9. 如果Service没有运行,则设置对应的标题与图标,用户点击它即可启动Service。这样我们就实现了开关按钮随Service状态的不同而变化。

好,已经有了BaseActivity类,接下来修改TimelineActivity也使用它。如下为TimelineActivity的完整实现:

例 10.12. TimelineActivity.java, final version

package com.marakana.yamba5;

import android.content.Intent;

import android.database.Cursor;

import android.os.Bundle;

import android.text.format.DateUtils;

import android.view.View;

import android.widget.ListView;

import android.widget.SimpleCursorAdapter;

import android.widget.TextView;

import android.widget.Toast;

import android.widget.SimpleCursorAdapter.ViewBinder;

public class TimelineActivity extends BaseActivity { //

Cursor cursor;

ListView listTimeline;

SimpleCursorAdapter adapter;

static final String[] FROM = { DbHelper.C_CREATED_AT, DbHelper.C_USER,

DbHelper.C_TEXT };

static final int[] TO = { R.id.textCreatedAt, R.id.textUser, R.id.textText };

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.timeline);

// Check if preferences have been set

if (yamba.getPrefs().getString("username", null) == null) { //

startActivity(new Intent(this, PrefsActivity.class));

Toast.makeText(this, R.string.msgSetupPrefs, Toast.LENGTH_LONG).show();

}

// Find your views

listTimeline = (ListView) findViewById(R.id.listTimeline);

}

@Override

protected void onResume() {

super.onResume();

// Setup List

this.setupList(); //

}

@Override

public void onDestroy() {

super.onDestroy();

// Close the database

yamba.getStatusData().close(); //

}

// Responsible for fetching data and setting up the list and the adapter

private void setupList() { //

// Get the data

cursor = yamba.getStatusData().getStatusUpdates();

startManagingCursor(cursor);

// Setup Adapter

adapter = new SimpleCursorAdapter(this, R.layout.row, cursor, FROM, TO);

adapter.setViewBinder(VIEW_BINDER); //

listTimeline.setAdapter(adapter);

}

// View binder constant to inject business logic for timestamp to relative

// time conversion

static final ViewBinder VIEW_BINDER = new ViewBinder() { //

public boolean setViewValue(View view, Cursor cursor, int columnIndex) {

if (view.getId() != R.id.textCreatedAt)

return false;

// Update the created at text to relative time

long timestamp = cursor.getLong(columnIndex);

CharSequence relTime = DateUtils.getRelativeTimeSpanString(view

.getContext(), timestamp);

((TextView) view).setText(relTime);

return true;

}

};

}

  1. 首先将基类由系统提供的Activity改为我们的BaseActivity。由此也就继承来了yamba对象,以及选项菜单的相关支持。
  2. 在这里检查用户的个人选项是否存在,若不存在,则切换到选项界面。
  3. 在界面显示时,初始化消息列表。这是个私有方法,定义在后面。
  4. 我们希望在界面关闭时,关闭数据库并释放资源。数据库是在yamba对象中的getStatusUpdates()方法中打开的。
  5. 辅助方法setupList()用以获取数据、设置Adapter并将其绑定于ListView。
  6. 在这里将ViewBinder绑定于List。参见"ViewBinder:TimelineAdapter之外的更好选择"一节。
  7. ViewBinder的定义。

到这里,我们已经将TimelineActivity重构的差不多了。按照如上的步骤,我们可以进一步简化StatusActivity,将选项菜单相关的代码清掉,从而使得BaseActivity、StatusDate、TimelineActivity的职责更加分明。

图10.3 "TimelineActivity"展示了Timeline界面的成品图。

图10.3. TimelineActivity