一個Activity
、一個Service
和兩個佈局文件。佈局十分簡單,這裏就不貼了,大概描述下。activity_main.xml
中倆按鈕,layout_window.xml
中一個TextView
。ok,首先看下MainActivity
。MainActivity
中只有倆按鈕,點擊啓動WindowService
,點擊中止WindowService
。沒啥好說的。直接看WindowService
。html
/** * @author CSDN 一口仨饃 */ public class WindowService extends Service { private final String TAG = this.getClass().getSimpleName(); private WindowManager.LayoutParams wmParams; private WindowManager mWindowManager; private View mWindowView; private TextView mPercentTv; private int mStartX; private int mStartY; private int mEndX; private int mEndY; @Override public void onCreate() { super.onCreate(); Log.i(TAG, "onCreate"); initWindowParams(); initView(); addWindowView2Window(); initClick(); } private void initWindowParams() { mWindowManager = (WindowManager) getApplication().getSystemService(getApplication().WINDOW_SERVICE); wmParams = new WindowManager.LayoutParams(); // 更多type:https://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#TYPE_PHONE wmParams.type = WindowManager.LayoutParams.TYPE_PHONE; wmParams.format = PixelFormat.TRANSLUCENT; // 更多falgs:https://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_NOT_FOCUSABLE wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; wmParams.gravity = Gravity.LEFT | Gravity.TOP; wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT; wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT; } private void initView() { mWindowView = LayoutInflater.from(getApplication()).inflate(R.layout.layout_window, null); mPercentTv = (TextView) mWindowView.findViewById(R.id.percentTv); } private void addWindowView2Window() { mWindowManager.addView(mWindowView, wmParams); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(TAG, "onStartCommand"); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); if (mWindowView != null) { //移除懸浮窗口 Log.i(TAG, "removeView"); mWindowManager.removeView(mWindowView); } Log.i(TAG, "onDestroy"); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } }
在設置各類屬性以後,直接向WindowManager
中添加mWindowView
(也就是咱們本身的佈局layout_window.xml
)。在此以前須要在AndroidManifest。xml
中註冊Service
和添加相應的限權。java
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name="android.permission.GET_TASKS" /> <service android:name=".WindowService"/>
如今點擊startBtn
,桌面上已經能夠出現懸浮窗。可是沒有拖動啦點擊啦這些動做。小意思,重寫點擊事件。根據拖動距離,判斷是點擊仍是滑動。因爲onTouchEvent()
的優先級比onClick
高,拖動時在須要的攔截的地方,return true
就ok了。具體以下:android
private void initClick() { mPercentTv.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mStartX = (int) event.getRawX(); mStartY = (int) event.getRawY(); break; case MotionEvent.ACTION_MOVE: mEndX = (int) event.getRawX(); mEndY = (int) event.getRawY(); if (needIntercept()) { //getRawX是觸摸位置相對於屏幕的座標,getX是相對於按鈕的座標 wmParams.x = (int) event.getRawX() - mWindowView.getMeasuredWidth() / 2; wmParams.y = (int) event.getRawY() - mWindowView.getMeasuredHeight() / 2; mWindowManager.updateViewLayout(mWindowView, wmParams); return true; } break; case MotionEvent.ACTION_UP: if (needIntercept()) { return true; } break; default: break; } return false; } }); mPercentTv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (isAppAtBackground(WindowService.this)) { Intent intent = new Intent(WindowService.this, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } } }); } /** * 是否攔截 * @return true:攔截;false:不攔截. */ private boolean needIntercept() { if (Math.abs(mStartX - mEndX) > 30 || Math.abs(mStartY - mEndY) > 30) { return true; } return false; }
這裏在onClick
中進行了一個程序先後臺的判斷操做,方法以下:緩存
/** *判斷當前應用程序處於前臺仍是後臺 */ private boolean isAppAtBackground(final Context context) { ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1); if (!tasks.isEmpty()) { ComponentName topActivity = tasks.get(0).topActivity; if (!topActivity.getPackageName().equals(context.getPackageName())) { return true; } } return false; }
至此爲止。懸浮窗已經顯示出來,點擊拖動事件也已經搞定。雖然和360懸浮窗差距還蠻大,可是剩下的只剩具體實現。像addView()
,removeView()
和動畫等等,這裏就再也不具體實現。本着知其然知其因此然的精神,下文是整個流程的源碼解析。session
在WindowService
中經過getApplication().getSystemService(getApplication().WINDOW_SERVICE)
獲取到一個WindowManager
,姑且稱這麼過程爲初始化。app
源碼位置:frameworks/base/core/Java/Android/app/Service.java
Service#getApplication()ide
public final Application getApplication() { return mApplication; }
首先獲取應用程序的Application
對象,而後調用Application#getSystemService()
。可是,在Application
中並無getSystemService()
這個方法,那麼這個方法確定在父類中或在某個接口中。追蹤發如今其父類ContextWrapper
中。跟進。佈局
源碼位置:frameworks/base/core/java/android/content/ContextWrapper.java
ContextWrapper#getSystemServiceName()動畫
@Override public String getSystemServiceName(Class<?> serviceClass) { return mBase.getSystemServiceName(serviceClass); }
成員變量mBase
爲Context
對象,跟進。this
源碼位置:frameworks/base/core/java/Android/content/Context.java
Context#getSystemServiceName()
public final <T> T getSystemService(Class<T> serviceClass) { String serviceName = getSystemServiceName(serviceClass); return serviceName != null ? (T)getSystemService(serviceName) : null; } public abstract Object getSystemService(@ServiceName @NonNull String name);
Context
的實現類是ContextImpl
,接下來獲取服務的方式和Android XML佈局文件解析過程源碼解析中同樣,爲了節省篇幅,直接進入到SystemServiceRegistry
中的靜態代碼快
源碼位置:frameworks/base/core/java/android/app/SystemServiceRegistry.java
static { ... registerService(Context.WINDOW_SERVICE, WindowManager.class, new CachedServiceFetcher<WindowManager>() { @Override public WindowManager createService(ContextImpl ctx) { return new WindowManagerImpl(ctx.getDisplay()); }}); ... }
這裏返回了WindowManagerImpl
對象,不過最後強轉稱了父類WindowManager
。目前爲止,已經獲取到了WindowManager
對象,各類參數也已經初始化完成。接下來只有一行WindowManager.addView()
。真可謂簡單到極致。極度的簡單每每是繁瑣的假象。接下來,纔是本文真正的開始。
源碼位置:frameworks/base/core/Java/Android/view/WindowManagerImpl.java
WindowManagerImpl#addView()
@Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mDisplay, mParentWindow); }
首先驗證Token
,這裏不做爲重點。接下來還有個addView()
跟進。
源碼位置:frameworks/base/core/Java/Android/view/WindowManagerGlobal.java
WindowManagerGlobal#addView()
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { // 參數效驗 ... ViewRootImpl root; synchronized (mLock) { // 查找緩存,類型效驗 ... root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); } try { root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { // who care? } }
給咱們的View
設置參數並添加到mRoots
中,由WindowManagerGlobal
進行管理,以後的事情就和View沒什麼關係了。接着調用ViewRootImpl#setView()
。跟進。下面是個關鍵點,同窗們注意力要集中。
源碼位置:frameworks/base/core/Java/Android/view/ViewRootImpl.java
ViewRootImpl#setView()
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { // 各類屬性讀取,賦值及效驗 ... try { ... res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); } catch (RemoteException e) { ... }
mWindowSession
是IWindowSession
對象。在建立ViewRootImpl
對象時被實例化。
public ViewRootImpl(Context context, Display display) { mWindowSession = WindowManagerGlobal.getWindowSession(); ... }
跟進。
源碼位置:frameworks/base/core/Java/Android/view/WindowManagerGlobal.java
WindowManagerGlobal#getWindowSession()
public static IWindowSession getWindowSession() { synchronized (WindowManagerGlobal.class) { if (sWindowSession == null) { try { InputMethodManager imm = InputMethodManager.getInstance(); IWindowManager windowManager = getWindowManagerService(); sWindowSession = windowManager.openSession( new IWindowSessionCallback.Stub() { @Override public void onAnimatorScaleChanged(float scale) { ValueAnimator.setDurationScale(scale); } }, imm.getClient(), imm.getInputContext()); } catch (RemoteException e) { Log.e(TAG, "Failed to open window session", e); } } return sWindowSession; } }
這裏getWindowManagerService()
經過AIDL
返回WindowManagerService
實例。以後調用WindowManagerService#openSession()
。跟進。
源碼位置:frameworks/base/services/java/com/android/server/wm/WindowManagerService.java
WindowManagerService#getWindowSession()
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client, IInputContext inputContext) { if (client == null) throw new IllegalArgumentException("null client"); if (inputContext == null) throw new IllegalArgumentException("null inputContext"); Session session = new Session(this, callback, client, inputContext); return session; }
返回一個Session
對象。也就是說在ViewRootImpl#setView()
中調用的是Session#addToDisplay()
。跟進。
源碼位置:frameworks/base/services/java/com/android/server/wm/Session.java
Session#addToDisplay()
@Override public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outStableInsets, outOutsets, outInputChannel); }
這裏的mService
是個WindowManagerService
對象,也就是說最後調用的是WindowManagerService#addWindow()
。
源碼位置:frameworks/base/services/java/com/android/server/wm/WindowManagerService.java
WindowManagerService#addWindow()
public int addWindow(...) { ... WindowState win = new WindowState(this, session, client, token, attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent); win.attach(); mWindowMap.put(client.asBinder(), win); ... }
mWindowMap
是個Map
實例,將WindowManager
添加進WindowManagerService
統一管理。至此,整個添加視圖操做解析完畢。
和addView()
過程同樣,最終會進入到WindowManagerGlobal#updateViewLayout()
。
源碼位置:frameworks/base/core/Java/Android/view/WindowManagerGlobal.java
WindowManagerGlobal#getWindowSession()
if (view == null) { throw new IllegalArgumentException("view must not be null"); } if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; view.setLayoutParams(wparams); synchronized (mLock) { int index = findViewLocked(view, true); ViewRootImpl root = mRoots.get(index); mParams.remove(index); mParams.add(index, wparams); root.setLayoutParams(wparams, false); }
將傳入的View
設置參數以後,更新mRoot
中View的參數。沒撒好說的。next one。
和上兩個過程同樣,最終會進入到WindowManagerGlobal#removeView()
。
public void removeView(View view, boolean immediate) { if (view == null) { throw new IllegalArgumentException("view must not be null"); } synchronized (mLock) { int index = findViewLocked(view, true); View curView = mRoots.get(index).getView(); removeViewLocked(index, immediate); if (curView == view) { return; } throw new IllegalStateException("Calling with view " + view + " but the ViewAncestor is attached to " + curView); } } private void removeViewLocked(int index, boolean immediate) { ViewRootImpl root = mRoots.get(index); View view = root.getView(); ... boolean deferred = root.die(immediate); if (view != null) { view.assignParent(null); if (deferred) { mDyingViews.add(view); } } }
這個過程要稍微麻煩點,首先調用root.die()
,接着將View
添加進mDyingViews
。跟進。
源碼位置:frameworks/base/core/java/android/view/ViewRootImpl.java
ViewRootImpl#die()
boolean die(boolean immediate) { ... mHandler.sendEmptyMessage(MSG_DIE); return true; }
這裏的參數immediate
默認爲false
,也就是說這裏只是發送了一個what=MSG_DIE
的空消息。ViewRootHandler
收到這條消息會執行doDie()
。
void doDie() { checkThread(); ... WindowManagerGlobal.getInstance().doRemoveView(this); }
跟進。
void doRemoveView(ViewRootImpl root) { synchronized (mLock) { final int index = mRoots.indexOf(root); if (index >= 0) { mRoots.remove(index); mParams.remove(index); final View view = mViews.remove(index); mDyingViews.remove(view); } } if (HardwareRenderer.sTrimForeground && HardwareRenderer.isAvailable()) { doTrimForeground(); } }
通過一圈效驗最終仍是回到WindowManagerGlobal
中移除View
。
至此,本文已經所有結束,感謝耐心閱讀到最後~