如何在 Android 中绘制其他应用程序?
有时我们需要我们的应用程序在主屏幕上显示一些内容,而不管在前台运行的应用程序如何,这个过程被称为绘制其他应用程序。有许多应用程序使用此功能以最小的屏幕覆盖范围提供最大的功能。这也使用户能够进行多任务处理。例如,Facebook Messenger 的聊天气泡、YourHour App 的 Mobile Over Usage 通知、Google 的 Voice Command 等等。我们可以使用 Android SDK 提供的 WindowManager 接口创建类似的功能。
安卓窗口管理器
Android WindowManager 是一个系统服务,用于控制显示哪些窗口以及它们在屏幕上的排列方式。当您启动或关闭应用程序或旋转屏幕时,它会自动执行窗口转换和动画等。
每个活动都有自己的窗口,可以在屏幕上显示其内容。当您对活动执行 setContentView 时,它会将视图添加到活动的默认窗口中。因为默认窗口会填满屏幕并隐藏任何其他活动,所以 WindowManager 将显示顶部的任何窗口。因此,在大多数情况下,您无需担心窗口;只需构建活动,Android 将负责其余的工作。为了操作窗口,我们需要与窗口管理器进行交互。现在为了显示在其他应用程序上,我们需要创建某种服务,因为当其他应用程序进入前台时,活动将关闭。
我们将在本文中构建什么?
下面给出了一个示例视频,以了解我们将在本文中做什么。请注意,我们将使用Java语言来实现这个项目。
分步实施
第 1 步:创建一个新项目
要在 Android Studio 中创建新项目,请参阅如何在 Android Studio 中创建/启动新项目。请注意,选择Java作为编程语言。
第 2 步:使用 AndroidManifest.xml 文件
为了绘制我们需要的其他应用程序, android.permission.SYSTEM_ALERT_WINDOW权限,对于API 版本 > 23 的android,我们需要在运行时要求这个。 android.permission.SYSTEM_ALERT_WINDOW 允许应用程序使用 WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY 类型创建窗口,显示在所有其他应用程序的顶部。
XML
XML
XML
Java
package com.raghav.gfgwindowmanager;
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Build;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import static android.content.Context.WINDOW_SERVICE;
public class Window {
// declaring required variables
private Context context;
private View mView;
private WindowManager.LayoutParams mParams;
private WindowManager mWindowManager;
private LayoutInflater layoutInflater;
public Window(Context context){
this.context=context;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// set the layout parameters of the window
mParams = new WindowManager.LayoutParams(
// Shrink the window to wrap the content rather
// than filling the screen
WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT,
// Display it on top of other application windows
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
// Don't let it grab the input focus
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
// Make the underlying application window visible
// through any transparent parts
PixelFormat.TRANSLUCENT);
}
// getting a LayoutInflater
layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// inflating the view with the custom layout we created
mView = layoutInflater.inflate(R.layout.popup_window, null);
// set onClickListener on the remove button, which removes
// the view from the window
mView.findViewById(R.id.window_close).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
close();
}
});
// Define the position of the
// window within the screen
mParams.gravity = Gravity.CENTER;
mWindowManager = (WindowManager)context.getSystemService(WINDOW_SERVICE);
}
public void open() {
try {
// check if the view is already
// inflated or present in the window
if(mView.getWindowToken()==null) {
if(mView.getParent()==null) {
mWindowManager.addView(mView, mParams);
}
}
} catch (Exception e) {
Log.d("Error1",e.toString());
}
}
public void close() {
try {
// remove the view from the window
((WindowManager)context.getSystemService(WINDOW_SERVICE)).removeView(mView);
// invalidate the view
mView.invalidate();
// remove all views
((ViewGroup)mView.getParent()).removeAllViews();
// the above steps are necessary when you are adding and removing
// the view simultaneously, it might give some exceptions
} catch (Exception e) {
Log.d("Error2",e.toString());
}
}
}
Java
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
public class ForegroundService extends Service {
public ForegroundService() {
}
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate();
// create the custom or default notification
// based on the android version
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
startMyOwnForeground();
else
startForeground(1, new Notification());
// create an instance of Window class
// and display the content on screen
Window window=new Window(this);
window.open();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
// for android version >=O we need to create
// custom notification stating
// foreground service is running
@RequiresApi(Build.VERSION_CODES.O)
private void startMyOwnForeground()
{
String NOTIFICATION_CHANNEL_ID = "example.permanence";
String channelName = "Background Service";
NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_MIN);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
assert manager != null;
manager.createNotificationChannel(chan);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
Notification notification = notificationBuilder.setOngoing(true)
.setContentTitle("Service running")
.setContentText("Displaying over other apps")
// this is important, otherwise the notification will show the way
// you want i.e. it will show some default notification
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setPriority(NotificationManager.IMPORTANCE_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.build();
startForeground(2, notification);
}
}
Java
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkOverlayPermission();
startService();
}
// method for starting the service
public void startService(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// check if the user has already granted
// the Draw over other apps permission
if(Settings.canDrawOverlays(this)) {
// start the service based on the android version
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(new Intent(this, ForegroundService.class));
} else {
startService(new Intent(this, ForegroundService.class));
}
}
}else{
startService(new Intent(this, ForegroundService.class));
}
}
// method to ask user to grant the Overlay permission
public void checkOverlayPermission(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
// send user to the device settings
Intent myIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivity(myIntent);
}
}
}
// check for permission again when user grants it from
// the device settings, and start the service
@Override
protected void onResume() {
super.onResume();
startService();
}
}
步骤 3:使用 activity_main.xml 文件
导航到app > res > layout > activity_main.xml并将以下代码添加到该文件中。下面是activity_main.xml文件的代码。
XML
第 4 步:创建 popup_window.xml 布局文件
XML
第 5 步:使用 Window。Java
Java
package com.raghav.gfgwindowmanager;
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Build;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import static android.content.Context.WINDOW_SERVICE;
public class Window {
// declaring required variables
private Context context;
private View mView;
private WindowManager.LayoutParams mParams;
private WindowManager mWindowManager;
private LayoutInflater layoutInflater;
public Window(Context context){
this.context=context;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// set the layout parameters of the window
mParams = new WindowManager.LayoutParams(
// Shrink the window to wrap the content rather
// than filling the screen
WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT,
// Display it on top of other application windows
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
// Don't let it grab the input focus
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
// Make the underlying application window visible
// through any transparent parts
PixelFormat.TRANSLUCENT);
}
// getting a LayoutInflater
layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// inflating the view with the custom layout we created
mView = layoutInflater.inflate(R.layout.popup_window, null);
// set onClickListener on the remove button, which removes
// the view from the window
mView.findViewById(R.id.window_close).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
close();
}
});
// Define the position of the
// window within the screen
mParams.gravity = Gravity.CENTER;
mWindowManager = (WindowManager)context.getSystemService(WINDOW_SERVICE);
}
public void open() {
try {
// check if the view is already
// inflated or present in the window
if(mView.getWindowToken()==null) {
if(mView.getParent()==null) {
mWindowManager.addView(mView, mParams);
}
}
} catch (Exception e) {
Log.d("Error1",e.toString());
}
}
public void close() {
try {
// remove the view from the window
((WindowManager)context.getSystemService(WINDOW_SERVICE)).removeView(mView);
// invalidate the view
mView.invalidate();
// remove all views
((ViewGroup)mView.getParent()).removeAllViews();
// the above steps are necessary when you are adding and removing
// the view simultaneously, it might give some exceptions
} catch (Exception e) {
Log.d("Error2",e.toString());
}
}
}
步骤 6:创建 ForegroundService 类
Java
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
public class ForegroundService extends Service {
public ForegroundService() {
}
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate();
// create the custom or default notification
// based on the android version
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
startMyOwnForeground();
else
startForeground(1, new Notification());
// create an instance of Window class
// and display the content on screen
Window window=new Window(this);
window.open();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
// for android version >=O we need to create
// custom notification stating
// foreground service is running
@RequiresApi(Build.VERSION_CODES.O)
private void startMyOwnForeground()
{
String NOTIFICATION_CHANNEL_ID = "example.permanence";
String channelName = "Background Service";
NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_MIN);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
assert manager != null;
manager.createNotificationChannel(chan);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
Notification notification = notificationBuilder.setOngoing(true)
.setContentTitle("Service running")
.setContentText("Displaying over other apps")
// this is important, otherwise the notification will show the way
// you want i.e. it will show some default notification
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setPriority(NotificationManager.IMPORTANCE_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.build();
startForeground(2, notification);
}
}
第 7 步:使用 MainActivity。Java
转到主活动。 Java文件,参考如下代码。下面是MainActivity的代码。 Java文件。代码中添加了注释以更详细地理解代码。
Java
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkOverlayPermission();
startService();
}
// method for starting the service
public void startService(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// check if the user has already granted
// the Draw over other apps permission
if(Settings.canDrawOverlays(this)) {
// start the service based on the android version
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(new Intent(this, ForegroundService.class));
} else {
startService(new Intent(this, ForegroundService.class));
}
}
}else{
startService(new Intent(this, ForegroundService.class));
}
}
// method to ask user to grant the Overlay permission
public void checkOverlayPermission(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
// send user to the device settings
Intent myIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivity(myIntent);
}
}
}
// check for permission again when user grants it from
// the device settings, and start the service
@Override
protected void onResume() {
super.onResume();
startService();
}
}
输出: