📌  相关文章
📜  如何在 Android 中绘制其他应用程序?

📅  最后修改于: 2022-05-13 01:54:28.041000             🧑  作者: Mango

如何在 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();
    }
}

输出: