This article has authorized the original launch of the WeChat public account "Hongyang", please be sure to indicate the source when reprinting.
use
Effect preview
Demo structure
One Activity
, one Service
and two layout files. The layout is very simple, so I won't post it here, just describe it. activity_main.xml
Two buttons in layout_window.xml
the middle, one in the middle TextView
. ok, let's take a look first MainActivity
. MainActivity
There are only two buttons, click to start WindowService
, click to stop WindowService
. Not much to say. Look directly WindowService
.
/**
* @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;
}
}
After setting various properties, WindowManager
add it directly to mWindowView
(that is, our own layout layout_window.xml
). Before that, you need AndroidManifest。xml
to register Service
and add the corresponding permissions in .
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.GET_TASKS" />
<service android:name=".WindowService"/>
Now click startBtn
, a floating window can already appear on the desktop. But there is no drag and click action. Small idea, rewrite the click event. According to the drag distance, it is judged whether to click or slide. Due to onTouchEvent()
the high priority onClick
ratio , return true
it is ok when dragging is where it needs to be intercepted. details as follows:
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;
}
Here onClick
, a judgment operation of the front and back of the program is carried out in the method as follows:
/**
*判断当前应用程序处于前台还是后台
*/
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;
}
So far. The floating window has been displayed, and the click-drag event has been completed. Although the gap with the 360 floating window is quite large, the only thing left is the specific implementation. Like addView()
, removeView()
and animation, etc., will not be implemented here. In the spirit of knowing why, the following is the source code analysis of the whole process.
Source code analysis
Initialize parsing
In WindowService
the process of getApplication().getSystemService(getApplication().WINDOW_SERVICE)
obtaining one WindowManager
, let's call this process initialization.
Source code location: frameworks/base/core/java/Android/app/Service.java
Service#getApplication()
public final Application getApplication() {
return mApplication;
}
First get the application's Application
object, then call Application#getSystemService()
. However, there Application
is no getSystemService()
such method in , so this method must be in the parent class or in an interface. Tracking is found in its parent class ContextWrapper
. follow up.
源码位置:frameworks/base/core/java/Android/content/ContextWrapper.java
ContextWrapper#getSystemServiceName()
@Override
public String getSystemServiceName(Class<?> serviceClass) {
return mBase.getSystemServiceName(serviceClass);
}
Member variables mBase
are Context
objects, follow up.
源码位置: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
The implementation class is ContextImpl
, the next way to obtain the service is the same as in the source code parsing of the Android XML layout file parsing process , in order to save space, directly enter SystemServiceRegistry
the static code in
Source code location: 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());
}});
...
}
The object is returned here WindowManagerImpl
, but the parent class is finally cast WindowManager
. So far, the WindowManager
object has been obtained, and various parameters have been initialized. Only one line follows WindowManager.addView()
. It's utterly simple. Extreme simplicity is often an illusion of cumbersomeness. Next, is the real beginning of this article.
WindowManager.addView () 解析
Source code location: 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);
}
First verify Token
, this is not the focus here. There is a addView()
follow up.
源码位置: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?
}
}
Give us the View
setting parameters and add them to mRoots
it, WindowManagerGlobal
and manage them, and then things have nothing to do with View. Then call ViewRootImpl#setView()
. follow up. The following is a key point, students should pay attention.
Source code location: 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
is the IWindowSession
object. ViewRootImpl
Instantiated when the object is created .
public ViewRootImpl(Context context, Display display) {
mWindowSession = WindowManagerGlobal.getWindowSession();
...
}
follow up.
源码位置: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;
}
}
Here getWindowManagerService()
by AIDL
returning the WindowManagerService
instance. call after WindowManagerService#openSession()
. follow up.
源码位置: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;
}
Returns an Session
object. That is to say , it ViewRootImpl#setView()
is called in Session#addToDisplay()
. follow up.
源码位置: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);
}
Here mService
is an WindowManagerService
object, which means that the last call is 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
It is an Map
instance that will be WindowManager
added WindowManagerService
to unified management. At this point, the entire add view operation is parsed.
WindowManager.updateViewLayout () 解析
As with addView()
the process, it will eventually enter 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);
}
After passing in the View
set parameters, update mRoot
the parameters in the View. Nothing to say. next one.
WindowManager.removeView()解析
Like the previous two processes, it will eventually enter 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);
}
}
}
This process is a little more cumbersome, calling first root.die()
, then View
adding it mDyingViews
. follow up.
Source code location: frameworks/base/core/java/android/view/ViewRootImpl.java
ViewRootImpl#die()
boolean die(boolean immediate) {
...
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
The parameters here are by immediate
default , which means that only an empty message false
is sent here . Executed upon receipt of this message .what=MSG_DIE
ViewRootHandler
doDie()
void doDie() {
checkThread();
...
WindowManagerGlobal.getInstance().doRemoveView(this);
}
follow up.
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();
}
}
After a lap of effectiveness, it was finally returned to WindowManagerGlobal
the middle and removed View
.
At this point, this article is all over, thank you for your patience to read to the end~
For more Framework source code analysis, please move to the Framework source code analysis series [Contents]