【问题标题】:How to make Android app using TWA full screen (immersive)如何使用 TWA 全屏制作 Android 应用(沉浸式)
【发布时间】:2020-10-29 00:10:40
【问题描述】:

我们制作了一个当前使用 WebView 全屏显示网页内容的 Android 应用。

这可行,但性能很大程度上取决于 WebVeiw 组件的版本,并且 Chrome 浏览器更新时并不总是更新(在不同的 Android 版本中,WebView 组件和 Chrome 浏览器之间的关系相对复杂) .从谷歌关于该主题的演示中,我们得出结论,使用 TWA 我们可能会获得更好和更一致的性能,因为 TWA 功能与 Chrome 浏览器一起更新。因此,我们希望在 TWA 不存在时使用 TWA 并回退到 WebView(我们的应用程序在 Android 4.4 和更高版本上运行)。

应用程序需要执行更多的逻辑,而不仅仅是显示 Web 内容,因此我们不能只在 Manifest 中定义 TWA/WebView。检查是否能够使用 TWA 并在 MainActivity.java 中启动 TWA 或回退到 WebView 已实现。但是,使用 TWA 时,URL/地址栏和底部导航栏仍然可见。

网址/地址栏: 据我们所知,要使 TWA 不显示 URL/地址栏,TWA 中显示的域必须有一个“匹配”Android 应用程序证书的 /.well-known/assetlinks.json 文件。包含有关此信息和有用链接的两个页面是 https://developers.google.com/web/android/trusted-web-activity/integration-guidehttps://developer.android.com/training/app-links/verify-site-associations。使用https://developers.google.com/digital-asset-links/tools/generator 创建assetlinks.json 并使用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 中的网页内容全屏显示?

我们查看了以下示例应用,以了解我们需要做些什么才能使 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"]
                }
  }
]

【问题讨论】:

标签: java android trusted-web-activity twa


【解决方案1】:

关于数字资产链接验证,我建议安装Peter's Asset Links Tool 并使用它来检查配置。请务必仔细检查快速入门指南上的section on App Signing,因为使用它时签名会发生变化,导致验证失败 从Play商店下载应用程序(您必须更新assetlinks.json才能使其工作)。

您似乎也在使用custom-tabs-client 库,该库已弃用,建议您移至android-browser-helper,这是受信任的网络活动的推荐库。如果您确实想使用较低级别的库,androidx.browser 将是可以使用的(我真的建议使用 android-browser-helper

android-browser-helper 包含一个LauncherActivity,这使得事情变得非常简单,因为大多数方面都可以从AndroidManifest.xml 配置,但它也希望从主屏幕启动受信任的 Web 活动。 twa-basic demo 展示了如何使用LauncherActivity

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

最后,如果目标是简单地从主屏幕启动 Progressive Web App,Bubblewrap 是一个 Node.js 命令行工具,可以自动执行该过程。

关于沉浸式模式,这里是how it's setup in the twa-basic demo。如果使用TwaLauncher,则要使用的LauncherActivity 代码为here

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

奖励:由于您提到想要使用 WebView 后备实现,您可能想知道 android-browser-helper 附带 WebView 后备(默认情况下禁用)。 twa-webview-fallback demo 显示 how to use it

【讨论】:

    猜你喜欢
    • 2021-05-31
    • 2016-08-25
    • 2014-03-06
    • 2018-05-10
    • 2014-03-07
    • 1970-01-01
    • 2013-12-23
    • 2016-10-28
    • 2018-12-30
    相关资源
    最近更新 更多