繁体   English   中英

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

[英]How to make Android app using TWA full screen (immersive)

我们制作了一个 Android 应用程序,当前使用 WebView 来全屏显示 web 内容。

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

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

URL/地址栏:据我们所知,要使 TWA 不显示 URL/地址栏,TWA 中显示的域必须有一个 /.well-known/assetlinks.json 文件“匹配” Android 应用程序。 包含有关此信息和有用链接的两页是https://developers.google.com/web/android/trusted-web-activity/integration-guidehttps://developer.ZC31B32364CE19linksEC58FCD150A47/-app.验证站点关联 The assetlinks.json was created using https://developers.google.com/digital-asset-links/tools/generator and successfully checked with https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.站点=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/地址栏和底部导航栏,基本上我们如何使 web 内容在 TWA 全屏显示?

我们查看了以下示例应用程序,以了解我们需要做什么才能使 TWA 正常工作,但没有发现任何工作(尽管我们错过了一些重要的东西并非不可想象):

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

清单.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"]
                }
  }
]

关于数字资产链接验证,我建议安装彼得的资产链接工具并使用它来检查配置。 请务必仔细检查快速入门指南上的应用签名部分,因为签名在使用时会发生变化,导致验证失败 从 Play 商店下载应用程序(您必须更新assetlinks.json以使其工作) .

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

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

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

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

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

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

奖励:由于您提到想要使用 WebView 回退实现,您可能有兴趣知道android-browser-helper附带 WebView 回退(默认禁用)。 twa-webview-fallback演示展示了如何使用它

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM