Lock screen interface development encountered various pits

background

This class is RemoteControlClient. This class is deprecated in API Level 21 and replaced by the new class MediaSession. In our music App, we used the native lock screen control API at the beginning. To be honest, this API is not easy to use, and there are some problems. The most important thing is that the lock screen interface is different for different brands of mobile phones. Domestic mobile phone manufacturers all think they have a strong aesthetic, and design strange lock screen control interface, MIUI is even stranger, MIUI 6 is changed on the basis of the original 4.4.4, unexpectedly there was no lock screen control interface for a period of time, later updated. In native Android 5.0, the lock screen and notification controls were combined, and the logic was confusing. We still decided to do a lock screen control page like netease Cloud music /QQ Music.

The solution

Similar to netease Cloud Music and QQ Music, generally register a broadcast to monitor ACTION_SCREEN_OFF/ACTION_SCREEN_ON operations, and then start an activity. The basic code is as follows:

private void addScreenChangeBroadCast() {
    if(mScreenBroadcastReceiver == null){
        mScreenBroadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                disableSystemLockScreen(context);
                Logger.d(TAG, "Intent.ACTION_SCREEN_OFF");
                Intent lockscreenIntent = new Intent();
                lockscreenIntent.setAction(LOCKSCREEN_ACTION);
                lockscreenIntent.setPackage(APP_PACKAGE);
                lockscreenIntent.putExtra("INTENT_ACTION", action); lockscreenIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(lockscreenIntent); }}; IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); try { registerReceiver(mScreenBroadcastReceiver, filter); } catch (Exception e) { e.printStackTrace(); } } } public voidremoveScreenChangeBroadCast() {
    if(mScreenBroadcastReceiver ! = null) { try { unregisterReceiver(mScreenBroadcastReceiver); } catch (Exception e) { e.printStackTrace(); } mScreenBroadcastReceiver = null; } } public static voiddisableSystemLockScreen(Context Context) {// The following code will cause some system home button to fail after startupif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        try {
            KeyguardManager keyGuardService = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
            KeyguardManager.KeyguardLock keyGuardLock = keyGuardService.newKeyguardLock("");
            keyGuardLock.disableKeyguard();
        } catch (Exception e) {
            Logger.e(TAG, "disableSystemLockScreen exception, cause: " + e.getCause()
                    + ", message: "+ e.getMessage()); }}}Copy the code

The Manifest as follows:

<activity
    android:name="com.activity.LockScreenActivity"
    android:excludeFromRecents="true"
    android:exported="false"
    android:noHistory="true"
    android:showOnLockScreen="true"
    android:launchMode="singleInstance"
    android:screenOrientation="portrait"
    android:taskAffinity="com.activity.LockScreenActivity"
    android:hardwareAccelerated="true"
    android:resizeableActivity="false"
    android:configChanges="keyboardHidden|orientation|screenSize|smallestScreenSize"
    android:theme="@style/LockScreenTheme">
    <intent-filter>
        <action android:name="com.android.lockscreen" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
Copy the code

The Activity in onCreate needs to add a flag that is displayed on the lock screen and does not respond to the Back button in onBackPress:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    Logger.d(TAG, getClass().getSimpleName() + ": onCreate");
    super.onCreate(savedInstanceState);
    Window window = getWindow();
    if(window ! = null) { window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); fitStatusBar(window); NavigationUtil.hideNavigationBar(window,true);
    }

    mSlideView = new SlideView(this, SlideView.TYPE_FRAMELAYOUT, R.color.framework_transparent);
    mSlideView.setFullSlideAble(true);
    LayoutInflater.from(this).inflate(R.layout.host_act_lockscreen, mSlideView.getContentView(), true);
    setContentView(mSlideView);
    initUi();
}

@Override
public void onBackPressed() {// The lock screen does not respond to the Back button, just override the Activity's onBackPressed method. }Copy the code

Then this began to fill the pit of my road, the following experience is just to the god of fun, to the later, don’t scold me behind the good.

Fill in the pit

1. The setAction startActivity page fails to get up

Before, because the page was in the upper layer, the launching operation was placed in the SDK layer, so the only way to start the page was setAction. It had been fine before, and then when it came back, it suddenly found that the page could not get up. After the big Hook ActivtyThread code, it found that the LAUNCH_ACTIVITY was actually called. But the fact that the page didn’t come up means the page wasn’t found. Finally, by limiting the Intent setPackage, everything is fine.

2. On some 4.x phones, you can’t return to the Home screen by pressing the Home button

Encounter this kind of problem scalp hemp, that can only Baidu. Good thing I found one. Done. Here’s why:

KeyguardLock, the inner class of KeyguardManager, has two methods to disable disableKeyguard and to enable reenableKeyguard screensaver

However, the disable method does not unlock the screen, but only disables the lock screen function, which also leads to the problem today. On some systems, the lock screen still exists and is not unlocked, resulting in the actual function of the Home is blocked by the lock screen when you press the Home button and cannot access the Home page. If disable is called, pressing the power button will result in a black screen and will not lock the screen unless the application process is killed.

Second, KeyguardLock objects must be the same in order to be reenabled after Disable. Therefore, to make reenable work, you need to save the object on which Disable is invoked for reenable, and simply calling reenable does not work. So you can’t lock screens opened by other programs, and sometimes you can’t even lock screens opened by yourself (if the objects are different).

Therefore, the disableKeyguard – screen protection method or can not be arbitrarily used ah, so simply remove this part of the code, the problem is perfect to solve!

3. The lock screen blinks

After the lock screen is displayed, turn off the power button and wait for a moment. When you open the screen again, the page blinks. LockScreenActivity life cycle found activty from onCreate to onDestroy executed once, why this phenomenon occurs, this is combined with many posts the whole code, looking at netease cloud music will not occur this situation, ok, Start deleting lines of code and find the field in noHistory. NoHistory indicates that activty executes finish when it is not visible to the user, leaving noHistory in statck. It is generally used for jump with empty shell Activty. So in the process of dying, the page is destroyed. You can refer to this post for noHistory.

4. Lock screens sometimes load slowly

In comparison with netease Cloud Music on my Xiaomi phone, in most cases, netease Cloud appeared first, and then its lock screen appeared late, sometimes it did not come out. In addition, when the APP is started for the first time, the screen cannot be locked for the first time, which is also the case with netease Cloud Music.

First, change the interface to only TextView, and then print the running time of each part of the code. I was surprised to find that startActivity takes about 3s to execute onCreate each time. Is it so slow for the system to find an activity? Android :showWhenLocked=”true” is fast but slow again in the afternoon. Well, since the post is useless, I can only go back to the source code of startActivity and find something like this:

boolean checkAppSwitchAllowedLocked(int sourcePid, int sourceUid,
        int callingPid, int callingUid, String name) {
    if (mAppSwitchesAllowedTime < SystemClock.uptimeMillis()) {
        return true;
    }

    int perm = checkComponentPermission(
            android.Manifest.permission.STOP_APP_SWITCHES, sourcePid,
            sourceUid, -1, true);
    if (perm == PackageManager.PERMISSION_GRANTED) {
        return true;
    }

    // If the actual IPC caller is different from the logical source.then
    // also see if they are allowed to control app switches.
    if(callingUid ! = -1 && callingUid ! =sourceUid) {
        perm = checkComponentPermission(
                android.Manifest.permission.STOP_APP_SWITCHES, callingPid,
                callingUid, -1, true);
        if (perm == PackageManager.PERMISSION_GRANTED) {
            return true;
        }
    }

    Slog.w(TAG, name + " request from " + sourceUid + " stopped");
    return false;
}
Copy the code
@Override
public void stopAppSwitches() {
    if(checkCallingPermission(android.Manifest.permission.STOP_APP_SWITCHES) ! = PackageManager.PERMISSION_GRANTED) { throw new SecurityException("viewquires permission "+ android.Manifest.permission.STOP_APP_SWITCHES); } synchronized(this) { // static final long APP_SWITCH_DELAY_TIME = 5*1000; MAppSwitchesAllowedTime = systemclock.uptimemillis () + APP_SWITCH_DELAY_TIME; mAppSwitchesAllowedTime = systemclock.uptimemillis () + APP_SWITCH_DELAY_TIME; mDidAppSwitch =false; mHandler.removeMessages(DO_PENDING_ACTIVITY_LAUNCHES_MSG); Message msg = mHandler.obtainMessage(DO_PENDING_ACTIVITY_LAUNCHES_MSG); mHandler.sendMessageDelayed(msg, APP_SWITCH_DELAY_TIME); }}Copy the code

So everything is clear, such as caller id, the alarm system application is by setting the android Manifest. Permission. STOP_APP_SWITCHES permissions in response to a background activities started, and ordinary applications can only be patient to wait. I decomcompiled the package of netease Cloud Music and added a bunch of flags to the common startActivity, which were all completed according to the Settings of netease Cloud. I think it may be the difference between the plug-in package and the production package, because the speed of the online package is ok, and it may be the cause of confusion. Jekins tried several times, but it didn’t work. Finally, I realized that the notification interface like QQ was launched with PendingIntent. I tried it in my own code and solved the problem by using PendingIntent instead.

Intent intent = new Intent(context, LockScreenActivity.class);
intent.setPackage(APP_PACKAGE);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
        | Intent.FLAG_ACTIVITY_SINGLE_TOP
        | Intent.FLAG_FROM_BACKGROUND
        | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
        | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
        | Intent.FLAG_ACTIVITY_NO_ANIMATION
        | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
PendingIntent pendingIntent =
        PendingIntent.getActivity(context, 0, intent, 0);
Logger.d(TAG, "pendingIntent.send() " + System.currentTimeMillis());
pendingIntent.send();
Copy the code

In this way, I did the lock screen page better than netease cloud music, which has two points of experience:

  1. The order in which the APP starts, registers and broadcasts may affect the sequence of page presentations. Careful reflection shows that the System is aware of the mListeners who register first
  1. If SCREEN_ON and SCREEN_OFF are used to lock the screen, only SCREEN_ON will hold the screen. SCREEN_ON launches a page that then holds and removes the previous hold index (i.e. MPendingActivityLaunches). If you want the APP lock screen to start faster, you can’t start your activity in SCREEN_ON
  1. Another neat way to do this without going through the hold process is to refer to the lock screen page of the Goodong app. The idea is to listen to SCREEN_OFF and then move the main activity to the foreground, so that the startActivity is not a background activity, but the user experience will be poor.

5. Android Q screen lock adaptation

In Android Q, Google explains:

Android Q imposes a limit on the amount of time an app can start an Activity. This behavior change helps minimize disruption to users and gives them more control over what is displayed on their screens. Specifically, an app running on Android Q can only start an Activity if it meets one or more of the following criteria:

The application has a visible window, such as an Activity running in the foreground.

Another application running in the foreground sends a PendingIntent belonging to that application. Examples include custom TAB providers that send menu items with pending intents.

The system sends a PendingIntent that belongs to the application, such as clicking on a notification. Only pending Intents that the application should launch the interface can be exempted.

The system sends a broadcast to the application, such as SECRET_CODE_ACTION. Only specific broadcasts for which the application should launch the interface are exempt.

See these, can not help but look up and sigh, won netease cloud music how, but lost to this era ah.

conclusion

Well, that’s it, I’m off to get some fresh air!

Keep my WX and welcome your comments.

The resources

Gityuan.com/2016/03/12/…

Wossoneri. Making. IO / 2018/06/03 /…

Blog.csdn.net/ixiaobu/art…

Stackoverflow.com/questions/5…

Developer.android.com/preview/pri…

Shoewann0402. Making. IO / 2019/03/16 /…