上一篇文章寫了使用WindowManager實現從自己的app界面撥打電話調用系統通話展示自定義佈局。這一篇文章主要介紹一下使用BroadcastReceiver、Service配合WindowManager實現當有電話打進來時,搜索該號碼是否是自己app內的用戶(好友),顯示特定的信息。
既然使用了BroadcastReceiver和Service,首先先介紹一下BroadcastReceiver和Service吧。
1.BroadcastReceiver
BroadcastReceiver顧名思義就是廣播接收器,廣播分爲廣播接收者和發送者,屬於Android四大組件之一,用於監聽 / 接收 應用 App 發出的廣播消息,並 做出響應。
BroadcastReceiver的註冊分爲兩種:靜態註冊和動態註冊。
(一)靜態註冊:在清單文件中通過標籤聲明,同時聲明要接收的action。
當此 App首次啓動時,系統會自動實例化該BroadcastReceiver類,並註冊到系統中,至此它的聲明週期就和該app沒什麼關係了,android不能自動銷燬廣播接收器,也就是說當應用程序關閉後,還是會接收廣播。
例如:
<receiver android:name=".PhoneReceive">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE"/>
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
<action android:name="com.ckw.CUSTOM_PHONE"/>
</intent-filter>
</receiver>
(二)動態註冊:在代碼中調用Context.registerReceiver()方法註冊,這裏不再多說。動態註冊的廣播接收器的聲明週期和宿主的聲明週期一致,當宿主被destroy了,它也就被銷燬了。
2.Service
Service也是Android的四大組件之一,可以使用Service在後臺執行耗時操作,例如下載。
Service的啓動方式也分爲兩種
(一)startService:
其他組件通過調用startService方法,啓動service。service會一直運行在後臺,即使啓動它的組件已經被銷燬了。
例如:
Intent ringingIntent = new Intent(mContext,PhoneService.class);
ringingIntent.putExtra("phoneState",TelephonyManager.CALL_STATE_RINGING);
mContext.startService(ringingIntent);
這種方式需要在清單文件中註冊服務:
<service android:name=".PhoneService"/>
(二)bindService:
其他組件調用bindService()方法綁定一個Service。多個組件可與一個service綁定,service不再與任何組件綁定時,該service會被destroy。
這種方式的service可以通過與Activity綁定,來實現與Activity的通信,這裏不再多說。
3.功能實現:
這裏用到了靜態註冊的BroadcastReceiver和startService方式創建的Service。
來電界面顯示的具體流程如圖:
來電之後,通過App內部的廣播通過PhoneStateListener接收系統廣播的Action,
接着開啓Service,之後將App內需要展示的信息(通過本地讀取或者網絡請求拿到),
將這些信息傳給Service。讓Service創建WindowManager來繪製自定義的界面,並將信息顯示到來電界面。
下面直接貼上代碼吧:
(一):Service
public class PhoneService extends Service{
/**
* 用於在線程中創建或移除懸浮窗。
*/
private Handler handler = new Handler();
/**
* 定時器,定時進行檢測當前應該創建還是移除懸浮窗。
*/
private Timer timer;
private int mPhoneState;//0 掛斷 1 來電 2 通話中 3 主動撥號
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
mPhoneState = intent.getIntExtra("phoneState",0);
String userName = "";
String userDep = "";
if(mPhoneState == 3 || mPhoneState == 1){//撥打電話 這裏後面還需要加個 1的類型
userName = intent.getStringExtra("userName");
userDep = intent.getStringExtra("userDep");
}
// 開啓定時器,每隔0.5秒刷新一次
if (timer == null) {
timer = new Timer();
timer.scheduleAtFixedRate(new RefreshTask(userName,userDep), 0, 500);
}
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
// Service被終止的同時也停止定時器繼續運行
timer.cancel();
timer = null;
}
class RefreshTask extends TimerTask {
private String userName;
private String userDep;
public RefreshTask(String userName,String userDep) {
this.userName = userName;
this.userDep = userDep;
}
@Override
public void run() {
if(!isHome() && !PhoneWindowManager.isWindowShowing()){
handler.post(new Runnable() {
@Override
public void run() {
PhoneWindowManager.createBigWindow(getApplicationContext(),userName,userDep);
}
});
}
// 當前界面是桌面,且有懸浮窗顯示,則移除懸浮窗。
if (isHome() || mPhoneState == 0) {
handler.post(new Runnable() {
@Override
public void run() {
PhoneWindowManager.removeBigWindow(getApplicationContext());
Intent intent = new Intent(getApplicationContext(), PhoneService.class);
getApplicationContext().stopService(intent);
}
});
}
}
}
/**
* 判斷當前界面是否是桌面
*/
private boolean isHome() {
ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> rti = mActivityManager.getRunningTasks(1);
return getHomes().contains(rti.get(0).topActivity.getPackageName());
}
/**
* 獲得屬於桌面的應用的應用包名稱
*
* @return 返回包含所有包名的字符串列表
*/
private List<String> getHomes() {
List<String> names = new ArrayList<String>();
PackageManager packageManager = this.getPackageManager();
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo ri : resolveInfo) {
names.add(ri.activityInfo.packageName);
}
return names;
}
}
(二)BroadcastReceiver:
public class PhoneReceive extends BroadcastReceiver implements PhoneInfoContract.View{
private Context mContext;
@Inject
PersonalAffairsApi personalAffairsApi;
@Inject
HttpManager httpManager;
@Override
public void onReceive(Context context, Intent intent) {
mContext = context;
if (intent.getAction().equals("com.ckw.CUSTOM_PHONE")) {//從自己的程序裏發出的,撥打電話
String userName = intent.getStringExtra("userName");
String userDep = intent.getStringExtra("userDep");
Intent phoneIntent = new Intent(context, PhoneService.class);
phoneIntent.putExtra("userName", userName);
phoneIntent.putExtra("userDep", userDep);
phoneIntent.putExtra("phoneState", 3);
context.startService(phoneIntent);
}
//如果是去電
else if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
//主動撥打電話的話,會經過這裏,但是我們用上面的代碼,因爲需要傳自己的數據過來
} else {
//設置一個監聽器
TelephonyManager tm = (TelephonyManager) context.getSystemService(Service.TELEPHONY_SERVICE);
tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
}
}
PhoneStateListener listener = new PhoneStateListener(){
@Override
public void onCallStateChanged(int state, String incomingNumber) {
//注意,方法必須寫在super方法後面,否則incomingNumber無法獲取到值。
super.onCallStateChanged(state, incomingNumber);
switch(state){
case TelephonyManager.CALL_STATE_IDLE:
Intent phoneIntent = new Intent(mContext,PhoneService.class);
phoneIntent.putExtra("phoneState",TelephonyManager.CALL_STATE_IDLE);
mContext.startService(phoneIntent);
break;
case TelephonyManager.CALL_STATE_OFFHOOK://應該是打電話的狀態,通話中
break;
case TelephonyManager.CALL_STATE_RINGING://響鈴,比如來電
String userId = DbHelper.getUserId(mContext);
PhoneInfoPresenter phoneInfoPresenter = new PhoneInfoPresenter(PhoneReceive.this,mContext);
phoneInfoPresenter.getPhoneInfo(incomingNumber,userId);
break;
}
}
};
@Override
public void showPhoneInfoResult(PhoneInfo info) {
//在這裏開啓service
Intent ringingIntent = new Intent(mContext,PhoneService.class);
ringingIntent.putExtra("userName",info.getXm());
ringingIntent.putExtra("userDep",info.getDepname());
ringingIntent.putExtra("phoneState",TelephonyManager.CALL_STATE_RINGING);
mContext.startService(ringingIntent);
}
@Override
public boolean isActive() {
return true;
}
@Override
public void showError(String msg) {
Log.d("----", "showError: "+msg);
}
@Override
public void setPresenter(PhoneInfoPresenter presenter) {
}
@Override
public void showProgressDialog(String msg) {
}
@Override
public void hideProgressDialog() {
}
}
(三)自定義的WindowManager:
public class PhoneWindowManager {
/**
* 顯示的view
*/
private static FloatWindowView mFloatWindowView;
/**
* 懸浮窗View的參數
*/
private static WindowManager.LayoutParams bigWindowParams;
/**
* 用於控制在屏幕上添加或移除懸浮窗
*/
private static WindowManager mWindowManager;
/**
* 用於獲取手機可用內存
*/
private static ActivityManager mActivityManager;
/**
* 創建一個懸浮窗。位置爲屏幕正中間。
*
* @param context
* 必須爲應用程序的Context.
*/
public static void createBigWindow(Context context,String userName,String userDep) {
WindowManager windowManager = getWindowManager(context);
if (mFloatWindowView == null) {
mFloatWindowView = new FloatWindowView(context,userName,userDep);
if (bigWindowParams == null) {
bigWindowParams = new WindowManager.LayoutParams();
bigWindowParams.x = 0;
bigWindowParams.y = 0;
bigWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
bigWindowParams.type = WindowManager.LayoutParams.TYPE_PHONE;
bigWindowParams.format = PixelFormat.RGBA_8888;
bigWindowParams.gravity = Gravity.CENTER;
bigWindowParams.height = FloatWindowView.viewHeight;
bigWindowParams.width = FloatWindowView.viewWidth;
}
windowManager.addView(mFloatWindowView, bigWindowParams);
}
}
/**
* 將懸浮窗從屏幕上移除。
*
* @param context
* 必須爲應用程序的Context.
*/
public static void removeBigWindow(Context context) {
if (mFloatWindowView != null) {
WindowManager windowManager = getWindowManager(context);
windowManager.removeView(mFloatWindowView);
mFloatWindowView = null;
}
}
/**
* 是否有懸浮窗顯示在屏幕上。
*
* @return 有懸浮窗顯示在桌面上返回true,沒有的話返回false。
*/
public static boolean isWindowShowing() {
return mFloatWindowView != null;
}
/**
* 如果WindowManager還未創建,則創建一個新的WindowManager返回。否則返回當前已創建的WindowManager。
*
* @param context
* 必須爲應用程序的Context.
* @return WindowManager的實例,用於控制在屏幕上添加或移除懸浮窗。
*/
private static WindowManager getWindowManager(Context context) {
if (mWindowManager == null) {
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
}
return mWindowManager;
}
/**
* 如果ActivityManager還未創建,則創建一個新的ActivityManager返回。否則返回當前已創建的ActivityManager。
*
* @param context
* 可傳入應用程序上下文。
* @return ActivityManager的實例,用於獲取手機可用內存。
*/
private static ActivityManager getActivityManager(Context context) {
if (mActivityManager == null) {
mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
}
return mActivityManager;
}
/**
* 獲取當前可用內存,返回數據以字節爲單位。
*
* @param context
* 可傳入應用程序上下文。
* @return 當前可用內存。
*/
private static long getAvailableMemory(Context context) {
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
getActivityManager(context).getMemoryInfo(mi);
return mi.availMem;
}
}
(四)自定義的顯示佈局:
根據自己的需求來,這裏就不貼了。