安卓筆記:Service與BroadcastReveiver

Service是四大組件之一。與Activity相似,但Service一直在後臺運行,沒有用戶界面,具有自己的生命週期。

BroadcastReveiver是四大組件之一,就像一個全局事件的事件監聽器,用於監聽系統發出的Broadcast。通過使用BroadcastReceiver可在不同應用程序之間通信。

1.Service

創建配置Service

1.定義一個繼承Service的子類

2.在AndroidManifest.xml中配置該Service

Service 與Activity都是從Context派生出來,都可以調用Context裏定義的getResources()\getContentResolver()等方法。

Service中也定義了一些列生命週期。

  • IBinder onBind(Intent intent):該方法是Service子類必須實現的方法。個i啊方法返回一個Ibinder對象,應用程序可通過該對象與Service組件通信。
  • void onCreate():在該Service第一次被創建後將立即回調該方法
  • void onDestory():再改Service被關閉之前會回調該方法
  • void onStartCommand(Intent intent,int flags,int startId):該方法的早期版本是void onStart(Intent intent,Int StartId),每次客戶端調用startService(Intent)方法啓動該Service時都會調用該方法
  • boolean onUnbind(Intent intent):當該Service上綁定的所有客戶端都斷開連接時將會返回該方法

配置Service使用<service../>元素可指定如下常用屬性

  • name:指定該Service的實現類類名
  • exported:指定該Service是否能被其他APP啓動。如果在配置該Service時指定了<intent-filter../>子元素,則該屬性默認爲true.
  • permission:指定啓動該Service所需的權限
  • process:指定該Service所處的進程,該Service組件默認處於該App所在的進程中。

例子:

    <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=".FirstService"></service>


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

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

啓動和停止Service

代碼:

public class MainActivity extends AppCompatActivity {

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

        Button start = findViewById(R.id.button);
        Button close = findViewById(R.id.button2);
        Intent intent = new Intent(this, FirstService.class);

        //java8支持lamdba
        start.setOnClickListener(view -> startService(intent));
        close.setOnClickListener(view ->stopService(intent));

    }
}

注意:從安卓5.0開始,谷歌要氣必須使用顯式Intent啓動Service組件。

綁定本地Service與之通信(實例)

如果Service和訪問者之間需要進行方法調用或數據交換,則應該使用bindService()和unbindService()方法啓動、關閉Service。

  • bindService(Intent service,ServiceConnnection conn,int flags)

conn:該參數是一個ServiceConnection對象,用於監聽訪問者與Service之間的連接情況。

flags:制定綁定是否創建Service(如果Service未創建)。該參數可指定0(不自動創建)或BIND_AUTO_CREATE(自動創建)

實例:

 

Service生命週期

Service的生命週期隨Service的啓動方式。

 

IntentService

是Service子類。

Service存在的問題

  1. 不會專門啓動一個單獨的進程,Service與他所在應用位於同一個進程中。
  2. 不是一條新的線程,因此不應該在Service中直接處理耗時的任務。

IntentService彌補了Service的這兩個不足,IntentService使用隊列來管理請求Intent,每當客戶端代碼通過Intent請求啓動IntentService時,IntentService會將該Intent假如隊列中,然後開啓一條worker線程來處理該Intent。對於異步的startService()請求,IntentService會按次序依次處理隊列中的Intent,該線程保證同一時刻只處理一個Intent。

IntentService具有如下特徵:

  • 會創建單獨的worker線程來處理所有的Intent請求
  • 會創建單獨的worker線程來處理onHandleintent()方法實現的代碼,開發者無需處理多線程問題
  • 當所有請求處理完成後,IntentService會自動停止,開發者無須調用stopSelf()方法來停止該Service
  • 爲Service的onBind()方法提供了默認實現,默認實現的onBind()方法返回null
  • 爲Service的onStartCommand()方法提供了默認實現,該實現會將請求Intent添加到隊列中

實例

2.跨進程調用Service(AIDL Service)

簡介

爲了實現跨進程通信(Interprocess Communication,簡稱IPC),Android提供了AIDL Service.

AIDL Service與Java中的RMI(遠程方法調用)存在一定的相似之處,都是先定義一個遠程調用接口,然後爲該接口提供一個實現類。

不同點:客戶端訪問Service時,Android並不是直接返回Service對象給客戶端,Service將他的代理對象(IBinder對象)通過onBind()方法返回給客戶端。本地Service的onBind()方法會直接把IBinder對象本身傳給客戶端的ServiceConnection的onServiceConnected方法的第二個參數;遠程Service的onBind()方法只是將IBinder對象的代理傳給客戶端的ServiceConnection的onServiceConnection方法的第二個參數。

當客戶端獲取了遠程Service的IBinder對象的代理之後,接下來可以通過該IBinder對象來回調遠程Service的屬性和方法了。

創建AIDL文件

Android需要AIDL(Android Interface Definition Language,Android接口定義語言)來定義遠程接口。

AIDL定義接口的源代碼必須以.aidl結尾

在AIDL接口中用到的數據類型,除基本類型、Spring、List、Map、CharSequence之外,其它類型的全都需要導包(即使在同一個包中也需要導包)

x.aidl

package xxxx.xxxx.xxxx;
interface Icat
{
    Stinrg getColor();
    String getWeight();
}

將接口暴露給客戶端

public class AidlService extends Service
{
	private CatBinder catBinder;
	private Timer timer = new Timer();
	private String[] colors = new String[]{"紅色", "黃色", "黑色"};
	private double[] weights = new double[]{2.3, 3.1, 1.58};
	private String color;
	private double weight;
	// 繼承Stub,也就是實現了ICat接口,並實現了IBinder接口
	class CatBinder extends ICat.Stub
	{
		@Override
		public String getColor()
		{
			return AidlService.this.color;
		}

		@Override
		public double getWeight()
		{
			return AidlService.this.weight;
		}
	}

	@Override public void onCreate()
	{
		super.onCreate();
		catBinder = new CatBinder();
		timer.schedule(new TimerTask()
		{
			@Override public void run()
			{
				// 隨機改變Service組件內color、weight屬性的值
				int rand = (int) (Math.random() * 3);
				color = colors[rand];
				weight = weights[rand];
			}
		},0, 800);
	}

	@Override public IBinder onBind(Intent intent)
	{
		/* 返回catBinder對象
		 * 在綁定本地Service的情況下,該catBinder對象會直接
		 * 傳給客戶端的ServiceConnection對象
		 * 的onServiceConnected方法的第二個參數
		 * 在綁定遠程Service的情況下,只將catBinder對象的代理
		 * 傳給客戶端的ServiceConnection對象
		 * 的onServiceConnected方法的第二個參數
		 */
		return catBinder; // ①
	}
	@Override public void onDestroy()
	{
		timer.cancel();
	}
}

配置

		<!-- 定義一個Service組件 -->
		<service android:name=".AidlService" >
			<intent-filter>
				<action android:name="org.crazyit.aidl.action.AIDL_SERVICE" />
			</intent-filter>
		</service>

客戶端訪問AIDL Service

1.創建SerivceConnection對象

5.以ServiceConnection對象作爲參數,調用Context的bindService()方法綁定原創Service

public class MainActivity extends Activity
{
	private ICat catService;
	private ServiceConnection conn = new ServiceConnection()
	{
		@Override public void onServiceConnected(ComponentName name, IBinder service)
		{
			// 獲取遠程Service的onBind方法返回的對象的代理
			catService = ICat.Stub.asInterface(service);
		}
		@Override public void onServiceDisconnected(ComponentName name)
		{
			catService = null;
		}
	};
	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		Button getBn = findViewById(R.id.get);
		TextView colorTv = findViewById(R.id.color);
		TextView weightTv = findViewById(R.id.weight);
		// 創建所需綁定的Service的Intent
		Intent intent = new Intent();
		intent.setAction("org.crazyit.aidl.action.AIDL_SERVICE");
		// 設置要啓動的Service所在包,也就是將該Intent變成所謂的顯式Intent
		intent.setPackage("org.crazyit.service");
		// 綁定遠程Service
		bindService(intent, conn, Service.BIND_AUTO_CREATE);
		getBn.setOnClickListener(view -> {
			// 獲取並顯示遠程Service的狀態
			try {
				colorTv.setText("名字:" + catService.getColor());
				weightTv.setText("重量:" + catService.getWeight());
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		});
	}
	@Override public void onDestroy()
	{
		super.onDestroy();
		// 解除綁定
		this.unbindService(conn);
	}
}

3.電話管理器(TelephonyManager)

4.短信管理器(SmsManager)

5.音頻管理器(AudioManager)

6.震動器(Vibrator)

vibrate(VibrationEffect vibe):控制手機按VibrationEffect效果執行震動

vibrate(VibrationEffect vibe,AudioAttributes attributes):控制手機按VibrationEffect效果執行震動,並執行AudioAttributes指定的聲音效果

cancel():關閉手機震動

7.手機鬧鐘服務(AlarmManager)

8.廣播接收器

簡介:

四大組件之一。用於監聽系統全局的廣播消息。Android8要求啓動BroadcastReceiver的Intent必須是顯式Intent。

程序啓動BroadcastReceiver需要兩步

1.創建需要啓動的BroadcastReceiver的Intent

2.調用Context的sendBroadcast()或sendOrderedBroadcast()方法來啓動制定的BroadcastReceiver

BroadcastReceiver屬於系統級的監聽器,擁有自己的進程,只要存在與之匹配的Intent被廣播出來,BroadcastReceiver就會被激發。

實現BroadcastReceiver:重寫BroadcastReceiver的onReceive(Context context,Intent intent)方法

接着指定該BroadcastReceiver能匹配的Intent

1.使用代碼進行指定

Intent filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED")
IncomingSMSReceiver receiver = new IncomingSMSReceiver()
registerReceiver(receiver, filter)

2.按AndroidManifest.xml文件中配置

		<receiver android:name=".MyReceiver" android:enabled="true"
			android:exported="false">
			<intent-filter>
				<!-- 指定該BroadcastReceiver所響應的Intent的Action -->
				<action android:name="org.crazyit.action.CRAZY_BROADCAST" />
			</intent-filter>
		</receiver>

在配置<reveiver.../>元素時可指定如下常用屬性

  • name
  • exported
  • label
  • permission
  • process

每次系統Broadcast事件發生後,系統都會創建對應的BroadcastReceiver實例,並自動觸發他的onReceiver()方法,執行完該方法,BroadcastReceiver實例就會被銷燬。

如果BroadcastReceiver的onReceiver()方法不能再10秒內完成,Android會認爲該程序無響應,彈出ANR(Application No Response)對話框。

如果需要根據Broadcast完成比較耗時的操作,可以考慮通過Intent啓動一個Service來完成,

如果BroadcastReceiver所在的進程結束了,雖然該進程內還有用戶啓動的新線程,但由於進程內部包含任何活動組件,系統可能在內存緊張時優先結束該進程,導致BroadcastReceiver啓動的子線程無法完成,

發送廣播

public class MainActivity extends Activity
{
	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		// 獲取程序界面中的按鈕
		Button sendBbn = findViewById(R.id.send);
		sendBbn.setOnClickListener(view -> {
			// 創建Intent對象
			Intent intent = new Intent();
			// 設置Intent的Action屬性
			intent.setAction("org.crazyit.action.CRAZY_BROADCAST");
			intent.setPackage("org.crazyit.broadcast");
			intent.putExtra("msg", "簡單的消息");
			// 發送廣播
			sendBroadcast(intent);
		});
	}
}
public class MyReceiver extends BroadcastReceiver
{
	@Override
	public void onReceive(Context context, Intent intent)
	{
		Toast.makeText(context, "接收到的Intent的Action爲:" + intent.getAction()
			+ "\n消息內容是:" + intent.getStringExtra("msg"),
			Toast.LENGTH_LONG).show();
	}
}

配置 

		<receiver android:name=".MyReceiver" android:enabled="true"
			android:exported="false">
			<intent-filter>
				<!-- 指定該BroadcastReceiver所響應的Intent的Action -->
				<action android:name="org.crazyit.action.CRAZY_BROADCAST" />
			</intent-filter>
		</receiver>

有序廣播

Broadcast分爲Normal Broadcast(普通廣播)Ordered Broadcast(有序廣播)

Normal Broadcast(普通廣播)

是完全異步的,可以在同一時刻(邏輯上)被所有接收者收到,消息的傳遞效率比較高。但接收者不能將處理結果傳遞給下一個接收者,並且無法終止Broadcast Intent的傳播

Ordered Broadcast(有序廣播)

Ordered Broadcast的接收者將按照預先聲明的優先級依次接收Broadcast。優先接收到Broadcast的接收者可以通過setResultExtras(Bundle)方法將處理結果存入Broadcast中,然後傳給下一個接收者,下一個接收者通過Bundle bundle = getResultExtras(true)獲取上一個接收者存入的數據。優先接收到Broadcast的接收者可以調用BroadcastReceiver的abortBroadcast()方法終於Broadcast。後面的廣播接收者無法收到。

public class MainActivity extends Activity
{
	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		// 獲取程序中的send按鈕
		Button sendBn = findViewById(R.id.send);
		sendBn.setOnClickListener(view -> {
			// 創建Intent對象
			Intent intent = new Intent();
			intent.setAction("org.crazyit.action.CRAZY_BROADCAST");
			intent.setPackage("org.crazyit.broadcast");
			intent.putExtra("msg", "簡單的消息");
			// 發送有序廣播
			sendOrderedBroadcast(intent, null);
		});
	}
}
public class MyReceiver1 extends BroadcastReceiver
{
	@Override
	public void onReceive(Context context, Intent intent)
	{
		Toast.makeText(context, "接收到的Intent的Action爲:" +
			intent.getAction() + "\n消息內容是:" + intent.getStringExtra("msg"),
			Toast.LENGTH_SHORT).show();
		// 創建一個Bundle對象,並存入數據
		Bundle bundle = new Bundle();
		bundle.putString("first", "第一個BroadcastReceiver存入的消息");
		// 將bundle放入結果中
		setResultExtras(bundle);
		// 取消Broadcast的繼續傳播
		abortBroadcast(); // ①
	}
}
public class MyReceiver2 extends BroadcastReceiver
{
	@Override
	public void onReceive(Context context, Intent intent)
	{
		Bundle bundle = getResultExtras(true);
		// 解析前一個BroadcastReceiver所存入的key爲first的消息
		String first = bundle.getString("first");
		Toast.makeText(context, "第一個Broadcast存入的消息爲:"
				+ first, Toast.LENGTH_LONG).show();
	}
}

        <receiver android:name=".MyReceiver1">
			<intent-filter android:priority="20">
				<action android:name="org.crazyit.action.CRAZY_BROADCAST" />
			</intent-filter>
		</receiver>
		<receiver android:name=".MyReceiver2">
			<intent-filter android:priority="0">
				<action android:name="org.crazyit.action.CRAZY_BROADCAST" />
			</intent-filter>
		</receiver>

 

9.接收系統廣播消息

Android常見的廣播Action常量(參考Android API 關於Intent的說明)

  • ACTION_AIRPLANE_MODE_CHANGED//關閉或打開飛行模式時的廣播
  • ACTION_BATTERY_CHANGED//充電狀態,或者電池的電量發生變化
  • ACTION_BATTERY_LOW//表示電池電量低
  • ACTION_BATTERY_OKAY//表示電池電量充足,即從電池電量低變化到飽滿時會發出廣播
  • ACTION_BOOT_COMPLETED//在系統啓動完成後,這個動作被廣播一次(只有一次)。
  • ACTION_SHUTDOWN//系統被關閉

通過使用BroadcastReceiver來監聽特殊的廣播,即可讓應用隨系統執行特定的操作。

實例:開機自動運行的Activity

 

public class LaunchReceiver extends BroadcastReceiver
{
	@Override
	public void onReceive(Context context, Intent intent)
	{
		Intent tIntent = new Intent(context
				, MainActivity.class);
		tIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		// 啓動指定Activity
		context.startActivity(tIntent);
	}
}

配置BroadcastReceiver 

		<!-- 定義一個BroadcastReceiver,監聽系統開機廣播  -->
		<receiver android:name=".LaunchReceiver">
			<intent-filter>
				<action android:name="android.intent.action.BOOT_COMPLETED" />
			</intent-filter>
		</receiver>

增加權限

	<!-- 授予應用程序訪問系統開機事件的權限 -->
	<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

實例:手機電量提示

手機電量發生改變時,系統會對外發送Intent的Ation爲ACTION_BATTERY_CHANGED常量的廣播;電量過低,發送Intent的Ation爲ACTION_BATTERY_LOW常量的廣播。通過開發對應Intent的BroadcastReceiver,可讓系統對手機點亮進行提示。

public class BatteryReceiver extends BroadcastReceiver
{
	@Override
	public void onReceive(Context context, Intent intent)
	{
		Bundle bundle = intent.getExtras();
		// 獲取當前電量
		int current = bundle.getInt("level");
		// 獲取總電量
		int total = bundle.getInt("scale");
		// 如果當前電量小於總電量的15%
		if (current * 1.0 / total < 0.15)
		{
			Toast.makeText(context, "電量過低,請儘快充電!", Toast.LENGTH_LONG).show();
		}
	}
}

主: 

		IntentFilter batteryfilter = new IntentFilter();
		// 設置該Intent的Action屬性
		batteryfilter.addAction(Intent.ACTION_BATTERY_CHANGED);
		// 註冊BatteryReceiver
		registerReceiver(new BatteryReceiver(), batteryfilter);

		BatteryManager bm = (BatteryManager) getSystemService(Context.BATTERY_SERVICE);
		// 獲取電池的狀態
		int st = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS);
		// 獲取電池的剩下電量(剩下的百分比)
		int a = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
		// 獲取電池的剩下的電量(以納瓦時爲單位)
        //int a = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER);
		// 獲取電池的平均電流(以毫安爲單位),正值表示正在充電,負值表示正在放電
		int b = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE);
		// 獲取電池的瞬時電流(以毫安爲單位),正值表示正在充電,負值表示正在放電
		int c = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_NOW);

權限: 

	<!-- 授權應用讀取電量信息 -->
	<uses-permission android:name="android.permission.BATTERY_STATS" />

 

發佈了36 篇原創文章 · 獲贊 7 · 訪問量 7324
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章