如何使用TWA使Android应用程序全屏显示(沉浸式)

3
我们制作了一个 Android 应用,目前使用 WebView 全屏显示网页内容。这种方式可以工作,但是性能强烈依赖于 WebView 组件的版本,并且当 Chrome 浏览器更新时并不总是会更新(WebView 组件和不同 Android 版本上的 Chrome 浏览器之间存在相对复杂的关系)。从 Google 的相关演示中,我们得出结论:使用 TWA 很可能会获得更好和更一致的性能,因为 TWA 功能将与 Chrome 浏览器一起更新。因此,我们希望在 TWA 无法使用时回退到 WebView(我们的应用程序运行在 Android 4.4 及更高版本上)。
应用程序需要执行一些逻辑以外的任务,而不仅仅是显示网页内容,因此我们不能只在 Manifest 中定义 TWA/WebView。在 MainActivity.java 中实现了检查是否能够使用 TWA,并启动 TWA 或回退到 WebView。然而,在使用 TWA 时,URL/地址栏和底部导航栏仍然可见。 URL/Address Bar: 据我们所知,为了使TWA不显示URL/地址栏,TWA中显示的域名必须有一个/.well-known/assetlinks.json文件,该文件“匹配”Android应用程序的证书。关于此问题的两个信息和有用链接页面分别是https://developers.google.com/web/android/trusted-web-activity/integration-guidehttps://developer.android.com/training/app-links/verify-site-associations。assetlinks.json是使用https://developers.google.com/digital-asset-links/tools/generator创建的,并已成功检查https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=website_url&relation=delegate_permission/common.handle_all_urls
在虚拟设备上测试(Android API级别29,Chrome v83),我们启用了Chrome的“在非root设备上启用命令行”标志,并在终端中执行了以下操作。
$ adb shell "echo '_ --disable-digital-asset-link-verification-for-url=\"website_url"' > /data/local/tmp/chrome-command-line"

接下来,Chrome会显示一个警告信息,但URL/地址栏仍然存在。

底部导航栏: Chrome v80及以上版本应支持使用沉浸式选项删除底部导航栏:https://bugs.chromium.org/p/chromium/issues/detail?id=965329#c18 尽管使用描述创建全屏应用程序的选项(https://developer.android.com/training/system-ui/immersive#java),底部导航栏仍然显示。

我们如何删除URL/地址栏和底部导航栏,基本上如何使TWA中显示的Web内容全屏?

我们查看了以下示例应用程序,以了解获取TWA工作所需做的事情,但没有找到任何有效的方法(尽管我们可能错过了一些关键性的东西):

我们项目文件的相关内容:

Manifest.json

    <application
        android:name="main_application"
        android:hardwareAccelerated="true"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:banner="@drawable/banner"
        android:label="@string/app_name"
        android:usesCleartextTraffic="true"
        android:networkSecurityConfig="@xml/network_security_config"
        android:theme="@style/AppTheme" >
        <activity
            android:name="main_activity"
            android:hardwareAccelerated="true"
            android:label="@string/app_name"
            android:launchMode = "singleInstance"
            android:keepScreenOn="true" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.LAUNCHER" />
                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    </application>

MainActivity.java

public class MainActivity extends Activity implements IServiceCallbacks {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Set up looks of the view
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        View decorView = getWindow().getDecorView();
        decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
            @Override
            public void onSystemUiVisibilityChange(int visibility) {
                // Note that system bars will only be "visible" if none of the
                // LOW_PROFILE, HIDE_NAVIGATION, or FULLSCREEN flags are set.
                if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
                    // bars are visible => user touched the screen, make the bars disappear again in 2 seconds
                    Handler handler = new Handler();
                    handler.postDelayed(new Runnable() {
                        public void run() {
                            hideBars();
                        }
                    }, 2000);
                } else {
                    // The system bars are NOT visible => do nothing
                }
            }
        });
        decorView.setKeepScreenOn(true);
        setContentView(R.layout.activity_main);

        // create Trusted Web Access or fall back to a WebView
        String chromePackage = CustomTabsClient.getPackageName(this, TrustedWebUtils.SUPPORTED_CHROME_PACKAGES, true);
        if (chromePackage != null) {
            if (!chromeVersionChecked) {
                TrustedWebUtils.promptForChromeUpdateIfNeeded(this, chromePackage);
                chromeVersionChecked = true;
            }

            if (savedInstanceState != null && savedInstanceState.getBoolean(MainActivity.TWA_WAS_LAUNCHED_KEY)) {
                this.finish();
            } else {
                this.twaServiceConnection = new MainActivity.TwaCustomTabsServiceConnection();
                CustomTabsClient.bindCustomTabsService(this, chromePackage, this.twaServiceConnection);
            }
        } else {
            // set up WebView
        }
    }


    private class TwaCustomTabsServiceConnection extends CustomTabsServiceConnection {
        public void onCustomTabsServiceConnected(ComponentName componentName, CustomTabsClient client) {
            CustomTabsSession session = MainActivity.this.getSession(client);
            CustomTabsIntent intent = MainActivity.this.getCustomTabsIntent(session);
            Uri url = Uri.parse("http://our_url");
            TrustedWebUtils.launchAsTrustedWebActivity(MainActivity.this, intent, url);
            MainActivity.this.twaWasLaunched = true;
        }

        public void onServiceDisconnected(ComponentName componentName) {
        }
    }


    protected void hideBars() {
        if (getWindow() != null) {
            View decorView = getWindow().getDecorView();
            decorView.setSystemUiVisibility(
                // Hide the nav bar and status bar
                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_FULLSCREEN
                // Set the content to appear under the system bars so that the
                // content doesn't resize when the system bars hide and show.
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                // Enables regular immersive mode
                | View.SYSTEM_UI_FLAG_IMMERSIVE
            );
        }
        // Remember that you should never show the action bar if the
        // status bar is hidden, so hide that too if necessary.
        ActionBar actionBar = getActionBar();
        if (actionBar != null) {
            actionBar.hide();
        }
    }
}

build.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        jcenter()
        google()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:4.0.0'
    }
}

allprojects {
    repositories {
        jcenter()
        google()
        maven { url "https://jitpack.io" }
    }
}

build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion '29.0.2'

    defaultConfig {
        applicationId application_id
        minSdkVersion 19
        targetSdkVersion 29

    }

    buildTypes {
        release {
            minifyEnabled true
            debuggable false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
        debug {
            minifyEnabled false
            debuggable true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
            jniDebuggable true
            renderscriptDebuggable true
            renderscriptOptimLevel 3
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    implementation 'androidx.leanback:leanback:1.0.0'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    implementation 'androidx.cardview:cardview:1.0.0'
    implementation 'androidx.webkit:webkit:1.2.0'
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'com.github.GoogleChrome.custom-tabs-client:customtabs:master'
}

/.well-known/assetlinks.json

[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target" :  { "namespace": "android_app", "package_name": "our package name",
                  "sha256_cert_fingerprints": ["11:22:33:44"]
                }
  },
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target" :  { "namespace": "android_app", "package_name": "our other package name",
                  "sha256_cert_fingerprints": ["11:22:33:44"]
                }
  }
]

我在实现 TWA 时遇到的一件事是,在我们的情况下,显示模式应由 PWA 设置,而不是由 Android TWA 应用程序设置。检查您的 PWA 的 Web Manifest,确保显示模式设置为所需的模式,即 fullscreen。 请参阅 https://developer.mozilla.org/en-US/docs/Web/Manifest/display 了解显示模式文档。 请参阅 https://superpwa.com/doc/web-app-manifest-display-modes/ 以更好地了解每种模式在通过 TWA 显示时的外观。 - Dat Pham Tat
1
你找到任何解决方案了吗? - jkr
1个回答

0
关于数字资产链接验证的问题,我建议安装Peter's Asset Links Tool并使用它来检查配置。确保在快速入门指南中仔细检查App签名部分, 因为在使用它时签名会发生更改,导致应用下载自Play Store时验证失败(您必须更新assetlinks.json以使其工作)。
您似乎还使用了custom-tabs-client库,该库已被弃用,并建议移至android-browser-helper,这是推荐用于受信任的Web活动的库。如果您确实想使用较低级别的库,则应使用androidx.browser ( 我真的建议使用android-browser-helper)。

android-browser-helper 包含一个 LauncherActivity,使得大多数方面都可以从 AndroidManifest.xml 中配置,但它也期望从主屏幕启动 Trusted Web Activity。twa-basic demo 演示了如何使用 LauncherActivity

对于其他用例,可以使用 TwaLauncher(它由 LauncherActivity 本身使用)。twa-custom-launcher 演示了如何使用它。LauncherActivity 的源代码也可能有所帮助。

最后,如果目标只是从主屏幕启动渐进式Web应用程序,则Bubblewrap是一种Node.js命令行工具,可自动化该过程。

关于沉浸模式,这里是twa-basic演示中的设置方法。如果使用TwaLauncher,则要使用的LauncherActivity代码在此处

使用Bubblewrap时,在创建项目以以沉浸模式启动应用程序时,请选择全屏作为显示模式。

奖励: 由于您提到想要使用 WebView 回退实现,您可能会对知道 android-browser-helper 包含了一个 WebView 回退(默认禁用)感兴趣。twa-webview-fallback demo 展示了 如何使用它


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接