Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android support #21

Closed
3 tasks done
wusyong opened this issue Feb 8, 2021 · 27 comments
Closed
3 tasks done

Android support #21

wusyong opened this issue Feb 8, 2021 · 27 comments
Labels

Comments

@wusyong
Copy link
Member

wusyong commented Feb 8, 2021

Bellow list is exploring list, not implementation. I'll start implementing once all are feasible.

  • Init scripts
  • IPC
  • custom protocol

Evaluate script is meaningless until we can provide some Android window (activity/fragment) library. See this comment for more info.

@marcomq
Copy link

marcomq commented Jan 2, 2022

Android has its own webview, available in java / kotlin.
Is this a feature request to have the same rust wry api available as jni? I once created a webview with an additional nim / c++ jni, based on android studio https://github.com/marcomq/nimview/tree/main/examples/android but I guess you are looking for something different here...

@wusyong
Copy link
Member Author

wusyong commented Jan 3, 2022

@marcomq We are actually looking for ways to build from its webview jni too! Because the inner webview library ABI is not exposed afaik.
I'm still busy on other stuff but I hope I can resume my research soon. Basically I would like to use android-ndk-rs
and combine with cargo-mobile. So it can make dev easier to setup the project and even run it.
Do you think this is the right way to do so?

@marcomq
Copy link

marcomq commented Jan 3, 2022

Do you think this is the right way to do so?

Actually no idea. My android knowledge is pretty basic. I was just interested on how this is planned.
But this sounds good. Great to know that there already is some library that can run rust on android and great to know that there is a plan :)

@Mohsen7s
Copy link

Mohsen7s commented Feb 4, 2022

It shouldn't be hard to support android, maybe should consider more priority on this issue

@FabianLars
Copy link
Member

@Mohsen7s
It literally can't get any higher on our priority list. Mobile support is our main feature for tauri v2!

Btw, we appreciate every helping hand we can get, especially for Android support.

@wusyong
Copy link
Member Author

wusyong commented Feb 16, 2022

Hey guys wanna give some update on current progress.
Here's my attempt to support Android so far: https://github.com/wusyong/android_webview.rs
Screen Shot 2022-02-16 at 3 11 38 PM

It doesn't contain lots of things because I'm focussing on how to make it more structural and ergonomic for users to do everythings they want. The sad thing is we can't create a NDK and use NativeActivity because it doesn't have Looper implemented by default and WebView will reject it upon creating. So the minimum we can offer is EmptyActivity and this also comes down to multi-windows support. We really can't use winit nor tao because they literally just some code implementation in NativeActivity. We will have to implement a new window management library if we want to offer richer window features on android.

This also affect after webview creation because we could only write FFI function to communicate with koltin code. We can't control the event loop after onCreate. That means the timing to call evaluate_script will be difficult.

So for the foreseeable future, the most reasonable todo list I could come up with is following:

  • Building wry android support with one Activity with one WebView tightly coupled. And you won't be able to configure anything after creating it.
    • Add init/eval_script method (though eval will be useless until window library is done)
    • Add IPC handler
    • Add custom protocol
  • Implement a some sort of Android window management library using Application.ActivityLifecycleCallbacks
    • ...I don't know what will be the blockers yet

Do you think this will be the reasonable progress? Any feedback is welcome!

@amrbashir
Copy link
Member

amrbashir commented Feb 16, 2022

Afaik, Activities on android has to be declared in the manifest and can't be created at runtime so I don't think multi-window or multi-activity WRY app is possible and probably shouldn't.

I guess we could make a macro that lets users define an entry point for an activity (it can be used multiple times for multiple activities) and we create these activities in the manifest at compile time.

I think Mobile support in general should be one window and use a client-side router or any kind of web navigation but that's just me, I don't think I would ever want multiple activities.

@wusyong
Copy link
Member Author

wusyong commented Feb 16, 2022

@amrbashir Yes, Activity has to be registered to manifest and cannot be created dynabmically during runtime.
But searching a bit, I still saw lots of issue about multiple webviews on android development. So I just want to take this into consideration just in case.

One possibility is running build script macro to edit the AndroidManifest.xml
Or if that's not desirable, perhaps we can narrow to one activity with multiple webviews.

@amrbashir
Copy link
Member

amrbashir commented Feb 16, 2022

Or if that's not desirable, perhaps we can narrow to one activity with multiple webviews.

We can use fragments, they can be dynamically created and I think multiple activities are more resource hungry than using multiple fragments in one activity. Jetpack Compose, which is the new modern toolkit to build native android app using kotlin, also uses fragments for multiple screens rather than activities.

@wusyong
Copy link
Member Author

wusyong commented Feb 18, 2022

Quick update for some great news. Init_script and IPC handling are possible!
wusyong/android_webview.rs@2b221c0
The last thing would be custom protocol. It should also be possible sine android has a guide explicit for it.
I'll give it a try next week.

@erlend-sh
Copy link

Continued in..

@Adhalianna
Copy link

What's the current status of the issue? Is anyone working on it? What would a complete newbie to the project need to know to help with the issue?

@lucasfernog
Copy link
Member

@Adhalianna we're actively working on it, see https://github.com/tauri-apps/cargo-mobile and the next branch in tauri. Also check out our mobile channel on Discord.

@dvc94ch
Copy link
Contributor

dvc94ch commented Oct 23, 2022

The sad thing is we can't create a NDK and use NativeActivity because it doesn't have Looper implemented by default and WebView will reject it upon creating.

Can you elaborate on this? I don't quite understand why you can't subclass NativeActivity and add a webview plus some additional methods to interface with the webview. Saddly the way it's currently handled there is no way to build without a complex build system like gradle, even after building a maven clone in rust and shelling out to kotlin/r8, since the AppCompatActivity requires support for res/drawables/etc.

This is how far I've got with trying to get xbuild to build a wry app for android:

 x run --device adb:16ee50bc
[1/3] Fetch precompiled artefacts
info: component 'rust-std' for target 'aarch64-linux-android' is up to date
[1/3] Fetch precompiled artefacts [1564ms]
[2/3] Build rust
    Finished dev [unoptimized + debuginfo] target(s) in 0.12s
[2/3] Build rust [159ms]
[3/3] Create apk [21007ms]
/home/dvc/rosetta-wallet/target/x/debug/android/dioxus-wall...le pushed, 0 skipped. 92.7 MB/s (114760019 bytes in 1.181s)
Success
Starting: Intent { act=android.intent.action.RUN cmp=com.example.dioxus_wallet/.TauriActivity }
pid of com.example.dioxus_wallet is 10822
--------- beginning of main

10822 10822 I e.dioxus_walle: Late-enabling -Xcheck:jni
10822 10822 I e.dioxus_walle: Unquickening 18 vdex files!
10822 10822 W e.dioxus_walle: Can't mmap dex file /data/app/~~Te3ceWAfiF2TYGsUCY8lRA==/com.example.dioxus_wallet-Dr1fKS2N2lPyWiRie5_ZFA==/base.apk!classes.dex directly; please zipalign to 4 bytes. Falling back to extracting file.
10822 10822 I Perf    : Connecting to perf service.
10822 10822 D NetworkSecurityConfig: No Network Security Config specified, using platform default
10822 10822 D NetworkSecurityConfig: No Network Security Config specified, using platform default
10822 10822 D AndroidRuntime: Shutting down VM
--------- beginning of crash

10822 10822 E AndroidRuntime: FATAL EXCEPTION: main
10822 10822 E AndroidRuntime: Process: com.example.dioxus_wallet, PID: 10822
10822 10822 E AndroidRuntime: java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/appcompat/R$drawable;
10822 10822 E AndroidRuntime: 	at androidx.appcompat.widget.f0.<init>(Unknown Source:6)
10822 10822 E AndroidRuntime: 	at androidx.appcompat.widget.g0.i(Unknown Source:26)
10822 10822 E AndroidRuntime: 	at androidx.appcompat.app.r0.<init>(Unknown Source:94)
10822 10822 E AndroidRuntime: 	at androidx.appcompat.app.r0.<init>(Unknown Source:1)
10822 10822 E AndroidRuntime: 	at androidx.appcompat.app.N.i(Unknown Source:2)
10822 10822 E AndroidRuntime: 	at androidx.appcompat.app.K.u0(Unknown Source:4)
10822 10822 E AndroidRuntime: 	at androidx.appcompat.app.K.attachBaseContext(Unknown Source:0)
10822 10822 E AndroidRuntime: 	at android.app.Activity.attach(Activity.java:7899)
10822 10822 E AndroidRuntime: 	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3384)
10822 10822 E AndroidRuntime: 	at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3596)
10822 10822 E AndroidRuntime: 	at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
10822 10822 E AndroidRuntime: 	at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
10822 10822 E AndroidRuntime: 	at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
10822 10822 E AndroidRuntime: 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2067)
10822 10822 E AndroidRuntime: 	at android.os.Handler.dispatchMessage(Handler.java:106)
10822 10822 E AndroidRuntime: 	at android.os.Looper.loop(Looper.java:223)
10822 10822 E AndroidRuntime: 	at android.app.ActivityThread.main(ActivityThread.java:7715)
10822 10822 E AndroidRuntime: 	at java.lang.reflect.Method.invoke(Native Method)
10822 10822 E AndroidRuntime: 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
10822 10822 E AndroidRuntime: 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:952)
10822 10822 E AndroidRuntime: Caused by: java.lang.ClassNotFoundException: Didn't find class "androidx.appcompat.R$drawable" on path: DexPathList[[zip file "/data/app/~~Te3ceWAfiF2TYGsUCY8lRA==/com.example.dioxus_wallet-Dr1fKS2N2lPyWiRie5_ZFA==/base.apk"],nativeLibraryDirectories=[/data/app/~~Te3ceWAfiF2TYGsUCY8lRA==/com.example.dioxus_wallet-Dr1fKS2N2lPyWiRie5_ZFA==/lib/arm64, /data/app/~~Te3ceWAfiF2TYGsUCY8lRA==/com.example.dioxus_wallet-Dr1fKS2N2lPyWiRie5_ZFA==/base.apk!/lib/arm64-v8a, /system/lib64, /system_ext/lib64]]
10822 10822 E AndroidRuntime: 	at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:207)
10822 10822 E AndroidRuntime: 	at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
10822 10822 E AndroidRuntime: 	at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
10822 10822 E AndroidRuntime: 	... 20 more
10822 10822 I Process : Sending signal. PID: 10822 SIG: 9

@dvc94ch
Copy link
Contributor

dvc94ch commented Oct 23, 2022

@dvc94ch
Copy link
Contributor

dvc94ch commented Oct 23, 2022

well, there are problems, but at least the one in the thread can be fixed by calling Looper.prepare() before creating the WebView. will see if I can get it working, if not I guess the proposed approach might be necessary.

// Copyright 2020-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use super::{WebContext, WebViewAttributes, RGBA};
use crate::{application::window::Window, Result};
use jni::objects::JObject;
use ndk::looper::ThreadLooper;
use std::rc::Rc;

pub(crate) struct InnerWebView {
  #[allow(unused)]
  pub window: Rc<Window>,
}

impl InnerWebView {
  pub fn new(
    window: Rc<Window>,
    attributes: WebViewAttributes,
    _pl_attrs: super::PlatformSpecificWebViewAttributes,
    _web_context: Option<&mut WebContext>,
  ) -> Result<Self> {
    let context = ndk_context::android_context();
    let vm = unsafe { jni::JavaVM::from_raw(context.vm().cast()) }?;
    let env = vm.attach_current_thread()?;
    let ctx = unsafe { JObject::from_raw(context.context().cast()) };

    let class = env.find_class("android/os/Looper")?;
    env.call_static_method(class, "prepare", "()V", &[])?.v()?;
    println!("Looper.prepare()");

    let class = env.find_class("android/webkit/WebView")?;
    let webview = env.new_object(class, "(Landroid/content/Context;)V", &[ctx.into()])?;
    println!("WebView webview = new WebView(ctx)");

    if let Some(url) = &attributes.url {
        let url = env.new_string(url)?;
        env.call_method(
            webview,
            "loadUrl",
            "(Ljava/lang/String;)V",
            &[url.into()]
        )?;
        println!("webview.loadUrl(url)");
    }

    /*env
      .call_method(
        ctx,
        "setContentView",
        "(Landroid/view/View;)V",
        &[webview.into()],
      )?
      .v()?;
    println!("setContentView(webview)");*/
    Ok(InnerWebView { window })
  }

  pub fn print(&self) {}

  pub fn eval(&self, _js: &str) -> Result<()> {
    Ok(())
  }

  #[cfg(any(debug_assertions, feature = "devtools"))]
  pub fn open_devtools(&self) {}

  #[cfg(any(debug_assertions, feature = "devtools"))]
  pub fn close_devtools(&self) {}

  #[cfg(any(debug_assertions, feature = "devtools"))]
  pub fn is_devtools_open(&self) -> bool {
    false
  }

  pub fn zoom(&self, _scale_factor: f64) {}

  pub fn set_background_color(&self, _background_color: RGBA) -> Result<()> {
    Ok(())
  }
}

pub fn platform_webview_version() -> Result<String> {
  Ok("1".into())
}

@wusyong
Copy link
Member Author

wusyong commented Oct 24, 2022

@dvc94ch I believe we have done this before. The result is Webview implementation doesn't work well with NDK.
What you see we do now is using regular Activity. Winit itself is also pushing an android-activity implementation that could work well with any kind of activity.

@dvc94ch
Copy link
Contributor

dvc94ch commented Oct 25, 2022

Got it working with gradle. Is the android-activity winit backend going to be adopted by tao? Currently tao does it's own weird thing and seems to be tightly coupled with and built specifically for wry. That would make experimenting with the wry android backend easier and tooling to not have to add special support for tao/wry.

@wusyong
Copy link
Member Author

wusyong commented Oct 27, 2022

Yes, itwill be the backend after it's merged in winit. The setup from tao is to connect to our project template from cargo-mobile fork.

@brandonros
Copy link

@dvc94ch got a repo/example anywhere of an Android Gradle project that uses wry?

@dvc94ch
Copy link
Contributor

dvc94ch commented Dec 4, 2022

not really, xbuild handles this all in the background. you can have a look at target/x/debug/android/gradle after running the following commands:

cargo install --git https://github.com/cloudpeers/xbuild
x new template
cd template
x build --platform android --arch arm64

@dvc94ch
Copy link
Contributor

dvc94ch commented Dec 8, 2022

is there a reason why on android doesn't support custom protocols? can they be supported?

@lucasfernog
Copy link
Member

It has been implemented @dvc94ch . Are you facing any issues?
Though it's not actually a custom protocol, it just listens to some specific URI like https://tauri.localhost like Windows.

@dvc94ch
Copy link
Contributor

dvc94ch commented Dec 8, 2022

I'm not quite following what you implemented and why. I registered a custom asset handler that works on linux, macos, ios but not android (we haven't tested windows). what I'd like to do is this:

use include_dir::{include_dir, Dir};

pub static ASSETS: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/assets");

pub fn asset_handler(request: &Request<Vec<u8>>) -> Result<Response<Vec<u8>>> {
    let path = request.uri().to_string().replace("asset://", "");
    let mime = match path.rsplit_once('.') {
        Some((_, "css")) => "text/css",
        Some((_, "png")) => "image/png",
        _ => "application/octet-stream",
    };
    let file = if let Some(file) = ASSETS.get_file(path) {
        file
    } else {
        return Response::builder()
            .status(StatusCode::NOT_FOUND)
            .body(b"Not Found".to_vec())
            .map_err(From::from);
    };
    Response::builder()
        .header("Content-Type", mime)
        .body(file.contents().to_vec())
        .map_err(From::from)
}

@dvc94ch
Copy link
Contributor

dvc94ch commented Dec 8, 2022

sorry for being thick. I managed to get it working like this:

macro_rules! img {
    ($file:literal) => {{
        #[cfg(target_family = "wasm")]
        let url = concat!("img/", $file);
        #[cfg(target_os = "android")]
        let url = concat!("https://asset.localhost/img/", $file);
        #[cfg(not(any(target_family = "wasm", target_os = "android")))]
        let url = concat!("asset://localhost/img/", $file);
        url
    }};
}

@lucasfernog
Copy link
Member

It should work. On tauri we do request.uri().strip_prefix("tauri://localhost").

@amrbashir
Copy link
Member

Closing as android support have been out for a while now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: 📬Proposal
Development

No branches or pull requests

10 participants