现在市面上很多app上都有小游戏(或叫h5游戏),它们借助小游戏来提高整个app的粘性。像微信app、趣头条app、哈啰app上已经出现了小游戏模块。
用户聊天、刷资讯、骑车之余可以打开小游戏休息放松,如果玩游戏时突然间来消息了或者累了想看资讯,关闭游戏页面后,再次打开,一般来说是需要重新加载游戏的,因为游戏关闭后内存资源就被回收了。但是作为游戏来讲,重新加载这种体验就不太好,有可能用户是暂时离开一会儿,再重新加载,不仅加载需要耗时而且之前游戏状态也没了。
那么微信小游戏是如何做到关闭游戏后,重新打开,保留了之前游戏状态,不需要重新加载呢?
我们知道activity关闭后,再次打开肯定要走onCreate生命周期,那么页面相关资源就要重建,这样就得重新加载游戏了。
实际上,微信小游戏关闭页面关不是finish掉了,而且跳转到其它页面了,小游戏页面还在后台运行,游戏也处于暂停状态,相关资源还在内存保留,没有被回收。再次打开同一个游戏,那么就不会再重新加载了。
那么这里面就涉及两个问题。
第一个问题,从游戏页面返回上一个页面或者跳转到其它页面,如何保证目标页面不会重建呢?
这个很简单,把目标页面启动模式设置成SingleTask或者SingleTop就可以了:
<activity
android:name="com.devnn.demo.MainActivity"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
或者在跳转的Intent上加上标记:clearTop或者singleTop也能达到同样的效果:
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
//intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
clearTop类似于SingleTask,是清除目标activity之上的activity来保证目标activity不重建,这个适用于回到主页面。
SginleTop是保证栈顶的activity唯一,这个适用于从一个栈跳转到另一个栈的栈顶页面。(请接着看第二个问题。)
第二个问题,再次回到游戏,如何保证游戏页面不会重建呢?
这个其实也涉及到启动模式知识。将小游戏页面启动模式设置成SingleInstance即可。SingleInstance表示,将activity存在在单独的task栈中,下次再次启动这个activity就直接切换到这个activity。那么两个栈中的页面来回切换就方便自如了。配置如下:
<activity
android:name="com.devnn.demo.ActivityA"
android:launchMode="singleInstance">
</activity>
从小游戏切回上一个页面,使用startActivity,并且在intent上添加SingleTop标记即可防止栈顶activity重建:
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
其实SingleInstance的使用场景很罕见,小游戏是一种很典型的场景了。
笔者做了一个demo实现了小游戏这种场景。其中MainActivity是主页面,ActivityA是仿小游戏页面。两个页面互相跳转不会finish页面也不会重建页面。代码如下:
MainActivity.java
package com.devnn.demo;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
public class MainActivity extends AppCompatActivity {
private String TAG="Demo_MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.i(TAG,"onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onResume() {
Log.i(TAG,"onResume");
super.onResume();
}
@Override
protected void onNewIntent(Intent intent) {
Log.i(TAG,"onNewIntent");
super.onNewIntent(intent);
}
public void toActivityA(View view) {
Intent intent = new Intent(this, ActivityA.class);
startActivity(intent);
}
}
ActivityA.java
package com.devnn.demo;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
public class ActivityA extends AppCompatActivity {
private String TAG = "Demo_ActivityA";
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_a);
}
@Override
public void onBackPressed() {
Log.i(TAG, "onBackPressed");
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
//intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
// moveTaskToBack(true);//回到上一个任务栈,不推荐使用,如果上一个任务栈是最近任务,就会回到桌面
}
@Override
protected void onNewIntent(Intent intent) {
Log.i(TAG, "onNewIntent");
super.onNewIntent(intent);
}
@Override
protected void onResume() {
super.onResume();
Log.i(TAG, "onResume");
}
@Override
protected void onPause() {
Log.i(TAG, "onPause");
super.onPause();
}
@Override
protected void onDestroy() {
Log.i(TAG, "onDestroy");
super.onDestroy();
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.devnn.demo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Devnn">
<activity
android:name="com.devnn.demo.ActivityA"
android:launchMode="singleInstance"></activity>
<activity
android:name="com.devnn.demo.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
MainActivity页面显示如下:
ActivityA页面显示如下:
其中ActivityA返回到MainActivity,笔者是重写了物理返回键,不会finish当前页面,而是显式跳转到MainActivity,模拟了跳转按钮。
经过MainActivty与Activity多次来回跳转,LogCat日志如下:
2021-06-09 19:13:42.687 8098-8098/com.devnn.demo I/Demo_MainActivity: onCreate
2021-06-09 19:13:42.766 8098-8098/com.devnn.demo I/Demo_MainActivity: onResume
2021-06-09 19:13:44.677 8098-8098/com.devnn.demo I/Demo_ActivityA: onCreate
2021-06-09 19:13:44.711 8098-8098/com.devnn.demo I/Demo_ActivityA: onResume
2021-06-09 19:13:46.552 8098-8098/com.devnn.demo I/Demo_ActivityA: onBackPressed
2021-06-09 19:13:46.573 8098-8098/com.devnn.demo I/Demo_ActivityA: onPause
2021-06-09 19:13:46.631 8098-8098/com.devnn.demo I/Demo_MainActivity: onNewIntent
2021-06-09 19:13:46.634 8098-8098/com.devnn.demo I/Demo_MainActivity: onResume
2021-06-09 19:14:10.446 8098-8098/com.devnn.demo I/Demo_ActivityA: onNewIntent
2021-06-09 19:14:10.451 8098-8098/com.devnn.demo I/Demo_ActivityA: onResume
2021-06-09 19:14:24.356 8098-8098/com.devnn.demo I/Demo_ActivityA: onBackPressed
2021-06-09 19:14:24.386 8098-8098/com.devnn.demo I/Demo_ActivityA: onPause
2021-06-09 19:14:24.431 8098-8098/com.devnn.demo I/Demo_MainActivity: onNewIntent
2021-06-09 19:14:24.434 8098-8098/com.devnn.demo I/Demo_MainActivity: onResume
2021-06-09 19:14:26.228 8098-8098/com.devnn.demo I/Demo_ActivityA: onNewIntent
2021-06-09 19:14:26.231 8098-8098/com.devnn.demo I/Demo_ActivityA: onResume
2021-06-09 19:14:29.044 8098-8098/com.devnn.demo I/Demo_ActivityA: onBackPressed
2021-06-09 19:14:29.075 8098-8098/com.devnn.demo I/Demo_ActivityA: onPause
2021-06-09 19:14:29.113 8098-8098/com.devnn.demo I/Demo_MainActivity: onNewIntent
2021-06-09 19:14:29.115 8098-8098/com.devnn.demo I/Demo_MainActivity: onResume
2021-06-09 19:19:08.836 8098-8098/com.devnn.demo I/Demo_ActivityA: onNewIntent
2021-06-09 19:19:08.840 8098-8098/com.devnn.demo I/Demo_ActivityA: onResume
从日志来看,两个页面切换并没有重新创建,而是走onNewIntent生命周期。
微信小游戏再次打开同一个游戏是不会重新加载的,打开不同的游戏就会重新加载,这是如何做到的呢?这个很简单,在onNewIntent回调中拿到传递过来的intent数据,取出游戏id,如果是同一个游戏忽略,不同的话就去重新加载。