[Android Series] A Reliable Process Living Method

Keywords: Android MediaPlayer xml Java

Written at the beginning

Before reading this article, you need to understand that the Android process survival method proposed in this paper is only relatively reliable and may not work in some cases, including but not limited to:

  1. Users actively kill the process;
  2. Use a variety of cell phone housekeepers to kill the process;
  3. The evolution of Android system and the customization of Android system by major domestic manufacturers will also have an impact on the process survival.
  4. Other.

In addition, it should be noted that the Android process lifesaving method introduced in this paper refers to a large number of articles on the Internet, not the author's original, and lists the links to the referenced articles at the end of this article.

background

Currently, many Android applications require processes to be resident in the background to support some Continuous tasks. One of the typical persistent tasks is pedometric applications, such as music power and spring rain pedometer; the other is instant messaging applications, such as QQ and Wechat. Although the method proposed in this paper is not as good as these mature applications, it is also relatively reliable.

At present, the main methods of online process maintenance are as follows:

  • Two processes pull each other;
  • One-pixel method;
  • Join the White List;
  • Monitor all kinds of broadcasts;
  • Use the front desk service;
  • Play silent music;
  • Use a variety of push;
  • Use JobService;
  • Long connection and heartbeat detection;
  • Use AccountSync;
  • Other.

In fact, some of these methods are no longer effective, and these methods can not absolutely guarantee that the application will survive in the background. Since there is no guarantee that the application will survive in the background, it is necessary to wake up the application in time and effectively after the application has been killed.

Method

The process life-preserving method introduced in this paper does not jump out of the scope of the methods listed above, but combines several methods effectively, and tests on Millet 6 show that the process can be more effective. The whole framework of life-preserving is shown in the following figure:

Among them:

  • MainActivity is responsible for starting AcquireService, LocalService and RemoteService.
  • LocalService and RemoteService run in different processes respectively. If LocalService is killed by the system, RemoteService will restart LocalService, and vice versa.
  • AcquireService works by opening the front desk service, playing silent music, and pulling each other with LocalService, and AcquireService is responsible for performing ongoing tasks.
  • Awaken Receiver is responsible for monitoring system broadcasting. After receiving the broadcasting, it judges whether the application is running or not. If the application is not running, then start Acquire Service, Local Service and RemoteService.

Two-process pull-out

Because of the different processes involved, readers need to master the principles and methods of interprocess communication (IPC) in Android. There are many articles on IPC on the Internet, which will not be repeated here. Next, look at the source code of IDaemonInterface and LocalService in turn:

// IDaemonInterface.aidl

interface IDaemonInterface {
    String getServerName();
    void startServer();
    void stopServer();
    boolean isServerRunning();
}
// LocalService.java

public class LocalService extends Service {
    private static final String SERVICE_NAME = 
            LocalService.class.getSimpleName();

    private IBinder localBinder;
    private ServiceConnection remoteConnection;

    // if you would like to keep some services alive,
    // please declare corresponding service connections here
    private ServiceConnection acquireConnection;

    @Override
    public void onCreate() {
        super.onCreate();

        localBinder = new LocalServiceBinder();
        remoteConnection = new RemoteServiceConnection();

        // please assign values to corresponding service connections here
        acquireConnection = new AcquireServiceConnection();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        bindService(new Intent(LocalService.this, RemoteService.class), 
                remoteConnection, Context.BIND_IMPORTANT);

        // please bind corresponding service connections here
        bindService(new Intent(LocalService.this, AcquireService.class), 
                acquireConnection, Context.BIND_IMPORTANT);

        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return localBinder;
    }

    private class LocalServiceBinder extends IDaemonInterface.Stub {
        @Override
        public String getServerName() throws RemoteException {
            return SERVICE_NAME;
        }

        @Override
        public void startServer() throws RemoteException {}

        @Override
        public void stopServer() throws RemoteException {}

        @Override
        public boolean isServerRunning() throws RemoteException {
            return true;
        }
    }

    private class RemoteServiceConnection implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            try {
                IDaemonInterface binder = IDaemonInterface.Stub.asInterface(service);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            startService(new Intent(LocalService.this, RemoteService.class));
            bindService(new Intent(LocalService.this, RemoteService.class), 
                    remoteConnection, Context.BIND_IMPORTANT);
        }
    }

    private class AcquireServiceConnection implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            try {
                IDaemonInterface binder = IDaemonInterface.Stub.asInterface(service);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            startService(new Intent(LocalService.this, AcquireService.class));
            bindService(new Intent(LocalService.this, AcquireService.class), 
                    acquireConnection, Context.BIND_IMPORTANT);
        }
    }
}

AIDL is used for inter-process communication. In Local Service:

  • The LocalServiceBinder class implements the IDaemonInterface interface, and the LocalServiceBinder object will be used as the medium of inter-process communication.
  • The RemoteService Connection class implements the Service Connection interface. The RemoteService Connection object is responsible for monitoring the connection status between LocalService and RemoteService, and reconnecting when the connection is disconnected.
  • The AcquireService Connection class implements the Service Connection interface. The AcquireService Connection object is responsible for monitoring the connection status between LocalService and AcquireService and re-establishing the connection when the connection is disconnected.

RemoteService is similar to LocalService in that they are declared in Android Manifest. XML as follows:

<!-- AndroidManifest.xml -->

<service
    android:name=".daemon.LocalService"
    android:enabled="true"
    android:exported="true" />
<service
    android:name=".daemon.RemoteService"
    android:enabled="true"
    android:exported="true"
    android:process="com.zzw.squirrel.remote" />
As you can see, RemoteService is running in a separate process called `com.zzw.squirrel.remote', while LocalService is still running in the default process.

Open Front Desk Service-Play Silent Music

In AcquireService, it is saved by opening the front desk service, playing voiceless music and pulling each other with LocalService:

// AcquireService.java

public class AcquireService extends Service {
    private static final String SERVICE_NAME = 
            AcquireService.class.getSimpleName();
    private ServiceConnection localConnection;
    public static final int NOTIFICATION_ID = 88;
    private MediaPlayerHelper mediaPlayerHelper;
    private boolean isRunning = false;

    @Override
    public void onCreate() {
        super.onCreate();
        localConnection = new LocalServiceConnection();
        doInitialize();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        bindService(new Intent(AcquireService.this, LocalService.class), 
                localConnection, Context.BIND_IMPORTANT);
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        startService(new Intent(getApplicationContext(), AcquireService.class));
    }

    @Override
    public IBinder onBind(Intent intent) {
        return new AcquireServiceBinder();
    }

    private synchronized void doInitialize() {
        mediaPlayerHelper = new MediaPlayerHelper(this);
    }

    private synchronized void doStart() {
        if (isRunning) {
            return;
        } else {
            isRunning = true;
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            Notification notification = new Notification.Builder(this).
                    setSmallIcon(R.mipmap.ic_launcher).
                    setContentTitle("Squirrel").
                    setContentText("data acquisition running...").
                    build();
            startForeground(NOTIFICATION_ID, notification);
        } else {
            startForeground(NOTIFICATION_ID, new Notification());
        }

        mediaPlayerHelper.start();
    }

    private synchronized void doStop() {
        if (!isRunning) {
            return;
        } else {
            isRunning = false;
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            NotificationManager manager = (NotificationManager) 
                    getSystemService(NOTIFICATION_SERVICE);
            manager.cancel(NOTIFICATION_ID);
            stopForeground(true);
        }

        mediaPlayerHelper.stop();
    }

    private class AcquireServiceBinder extends IDaemonInterface.Stub {
        @Override
        public String getServerName() throws RemoteException {
            return SERVICE_NAME;
        }

        @Override
        public void startServer() throws RemoteException {
            doStart();
        }

        @Override
        public void stopServer() throws RemoteException {
            doStop();
        }

        @Override
        public boolean isServerRunning() throws RemoteException {
            return isRunning;
        }
    }

    private class LocalServiceConnection implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            try {
                IDaemonInterface binder = IDaemonInterface.Stub.asInterface(service);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            startService(new Intent(AcquireService.this, LocalService.class));
            bindService(new Intent(AcquireService.this, LocalService.class), 
                    localConnection, Context.BIND_IMPORTANT);
            startService(new Intent(AcquireService.this, RemoteService.class));
        }
    }
}

In AcquireService:

  • The AcquireServiceBinder class implements the IDaemonInterface interface, and the AcquireServiceBinder object will be used as the medium of inter-process communication.
  • The LocalService Connection class implements the Service Connection interface. The LocalService Connection object is responsible for monitoring the connection status between LocalService and AcquireService, and reconnecting when the connection is disconnected.
  • The MediaPlayerHelper class is responsible for starting/stopping the playback of silent music. Its source code is as follows:
// MediaPlayerHelper.java

public class MediaPlayerHelper {
    private Context context;
    private MediaPlayer mediaPlayer;

    public MediaPlayerHelper(Context context) {
        this.context = context;
    }

    public synchronized void start() {
        if (mediaPlayer == null) {
            mediaPlayer = MediaPlayer.create(context, R.raw.silent);
            mediaPlayer.setLooping(true);
        }
        if (!mediaPlayer.isPlaying()) {
            mediaPlayer.start();
        }
    }

    public synchronized void stop() {
        if (mediaPlayer != null) {
            if (mediaPlayer.isPlaying()) {
                mediaPlayer.stop();
            }
            mediaPlayer.release();
            mediaPlayer = null;
        }
    }
}
AcquireService's statement in AndroidManifest.xml is as follows:
<!-- AndroidManifest.xml -->

<service
    android:name=".acquire.AcquireService"
    android:enabled="true"
    android:exported="true"
    android:process="com.zzw.squirrel.acquire" />
As you can see, AcquireService, like LocalService, runs in a separate process.

Monitoring System Broadcasting

In addition to the above "active" process lifesaving methods, in case, we need to add a "passive" process lifesaving method - monitoring system broadcasting.

Awaken Receiver is responsible for monitoring system broadcasts such as boot-up, installation/uninstall applications, unlock, mount/uninstall media and network changes:

// AwakenReceiver.java

public class AwakenReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        boolean isAlive = false;
        ActivityManager activityManager = (ActivityManager) 
                context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> appProcessInfos = 
                activityManager.getRunningAppProcesses();
        for (ActivityManager.RunningAppProcessInfo appProcessInfo : appProcessInfos) {
            if (appProcessInfo.processName.equals(AppHelper.PACKAGE_NAME)) {
                isAlive = true;
                break;
            }
        }
        if (!isAlive) {
            startServices(context);
        }
    }

    private void startServices(Context context) {
        context.startService(new Intent(context, LocalService.class));
        context.startService(new Intent(context, RemoteService.class));
        context.startService(new Intent(context, AcquireService.class));
    }
}
Awaken Receiver's statement in Android Manifest. XML is as follows:
<!-- AndroidManifest.xml -->

<receiver
    android:name=".daemon.AwakenReceiver"
    android:enabled="true"
    android:exported="true">
    <!-- device reboot broadcast -->
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <!-- install/uninstall broadcast -->
    <intent-filter>
        <action android:name="android.intent.action.PACKAGE_ADDED" />
        <action android:name="android.intent.action.PACKAGE_REMOVED" />
        <data android:scheme="package" />
    </intent-filter>
    <!-- unlock broadcast -->
    <intent-filter>
        <action android:name="android.intent.action.ACTION_USER_PRESENT" />
    </intent-filter>
    <!--sdcard mounted/unmounted broadcast -->
    <intent-filter >
        <action android:name="android.intent.action.MEDIA_MOUNTED"/>
        <action android:name="android.intent.action.MEDIA_UNMOUNTED"/>
        <data android:scheme="file" />
    </intent-filter>
    <!--network state broadcast -->
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
        <action android:name="android.net.wifi.WIFI_STATE_CHANGED" />
        <action android:name="android.net.wifi.STATE_CHANGE" />
    </intent-filter>
</receiver>
In addition, don't forget the necessary permissions to apply:
<!-- AndroidManifest.xml -->

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

Startup service

We started AcquireService, LocalService and RemoteService in MainActivity:

// MainActivity.java

public class MainActivity extends AppCompatActivity implements ServiceConnection {
    private static final String ACTIVITY_NAME = MainActivity.class.getSimpleName();
    private Button startBt;
    private Button stopBt;
    private Handler acquireHandler;
    private IDaemonInterface acquireBinder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initEvent();
        acquireHandler = new AcquireHandle(MainActivity.this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        activeBinder();
    }

    @Override
    protected void onPause() {
        super.onPause();
        unbindService(MainActivity.this);
    }

    private void initView() {
        startBt = findViewById(R.id.start_bt);
        stopBt = findViewById(R.id.stop_bt);
    }

    private void initEvent() {
        startBt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    acquireBinder.startServer();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
        stopBt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    acquireBinder.stopServer();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private void activeBinder() {
        if (acquireBinder == null) {
            acquireHandler.sendEmptyMessage(AcquireHandle.SHOW_WAIT_DIALOG);
            startService(new Intent(MainActivity.this, LocalService.class));
            startService(new Intent(MainActivity.this, RemoteService.class));
            startService(new Intent(MainActivity.this, AcquireService.class));
            bindService(new Intent(MainActivity.this, AcquireService.class),
                    MainActivity.this,
                    Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
        } else {
            try {
                acquireBinder.getServerName();
            } catch (RemoteException e) {
                acquireHandler.sendEmptyMessage(AcquireHandle.SHOW_WAIT_DIALOG);
                startService(new Intent(MainActivity.this, LocalService.class));
                startService(new Intent(MainActivity.this, RemoteService.class));
                startService(new Intent(MainActivity.this, AcquireService.class));
                bindService(new Intent(MainActivity.this, AcquireService.class),
                        MainActivity.this,
                        Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
            }
        }
    }

    // ==================== ServiceConnection ====================
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        acquireBinder = IDaemonInterface.Stub.asInterface(service);
        if (acquireHandler != null) {
            acquireHandler.sendEmptyMessage(AcquireHandle.HIDE_WAIT_DIALOG);
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        acquireBinder = null;
    }
    // ==================== ServiceConnection ====================

    // ==================== Custom Handle ====================
    private static class AcquireHandle extends Handler {
        public static final int SHOW_WAIT_DIALOG = 0;
        public static final int HIDE_WAIT_DIALOG = 1;
        private ProgressDialog dialog;

        public AcquireHandle(Context context) {
            dialog = new ProgressDialog(context);
            dialog.setTitle("Synchronizing");
            dialog.setMessage("In sync, please wait a moment....");
            dialog.setCancelable(false);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SHOW_WAIT_DIALOG: {
                    if (!dialog.isShowing()) {
                        dialog.show();
                    }
                    break;
                }
                case HIDE_WAIT_DIALOG: {
                    if (dialog.isShowing()) {
                        dialog.dismiss();
                    }
                    break;
                }
                default: {
                    super.handleMessage(msg);
                    break;
                }
            }
        }
    }
    // ==================== Custom Handle ====================
}

summary

The Android process surviving method proposed in this paper is only for reference. This method is only relatively reliable. After testing on millet 6, it is found that the process surviving method is more successful.

This article refers to a large number of articles about Android Process Livelihood on the Internet. The main reference articles include:

Thank you to the authors of these articles.

In addition, the relevant code in this article can be found on GitHub, with the following links:
https://github.com/jzyhywxz/Squirrel.git

Written at the end

Although the Android process lifesaving method proposed in this paper is very useful in some cases, it also has obvious shortcomings:

  • Easy to reduce system fluency;
  • Easy to consume mobile phone power;
  • Easy to breed all kinds of rogue applications;
  • Other.

This strongly appeals to all readers not to do black technology that reduces or even violates the user experience. Just do your own research and play. You can not use it in real project development.


Welcome to the public number fightingZh of Wechat

Posted by dt192 on Wed, 15 May 2019 19:46:10 -0700