- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
我正在尝试在后台加载一些 URL,但 WebView 以相同的方式在 Activity 中加载它。
开发人员想要它的原因有很多(并要求它 here ),例如在没有 Activity 的情况下运行 JavaScript、缓存、监视网站更改、报废 ...
似乎在某些设备和 Android 版本(例如带有 Android P 的 Pixel 2)上,这在 Worker 上运行良好。 ,但在其他一些设备上(可能在较旧版本的 Android 上),我只能在使用 SYSTEM_ALERT_WINDOW 权限的具有顶层 View 的前台服务上安全地完成此操作。
问题是,我们需要在后台使用它,因为我们已经有一个用于其他用途的 Worker。我们不希望仅仅为此添加前台服务,因为这会使事情变得复杂,添加所需的权限,并会在需要完成工作时向用户发出通知。
在互联网上搜索,我发现很少有人提到这种情况(here 和 here)。主要的解决方案确实是拥有一个具有顶层 View 的前台服务。
为了检查网站是否正常加载,我在各种回调中添加了日志,包括 onProgressChanged 、 onConsoleMessage 、 onReceivedError 、 onPageFinished 、 shouldInterceptRequest 、 onPageStarted 。 WebViewClient的全部部分和 WebChromeClient类。
我已经在我知道应该写入控制台的网站上进行了测试,有点复杂并且需要一些时间来加载,例如 Reddit和 Imgur .
启用 JavaScript 很重要,因为我们可能需要使用它,并且网站在启用时会正常加载,因此我设置了 javaScriptEnabled=true
。我注意到还有 javaScriptCanOpenWindowsAutomatically
,但据我所知,通常不需要它,所以我并没有真正使用它。另外,似乎启用它会导致我的解决方案(在 Worker 上)失败更多,但也许这只是巧合。此外,了解应该在 UI 线程上使用 WebView 很重要,因此我将其处理放在与 UI 线程关联的 Handler 上。
我尝试在 WebSettings 中启用更多标志WebView 的类,我还尝试通过测量来模拟它在容器内。
尝试稍微延迟加载,并尝试先加载一个空 URL。在某些情况下,它似乎有所帮助,但并不一致。
似乎没有任何帮助,但在某些随机情况下,各种解决方案似乎仍然有效(但不一致)。
这是我当前的代码,其中还包括我尝试过的一些内容(可用项目 here):
Util.kt
object Util {
@SuppressLint("SetJavaScriptEnabled")
@UiThread
fun getNewWebView(context: Context): WebView {
val webView = WebView(context)
// val screenWidth = context.resources.displayMetrics.widthPixels
// val screenHeight = context.resources.displayMetrics.heightPixels
// webView.measure(screenWidth, screenHeight)
// webView.layout(0, 0, screenWidth, screenHeight)
// webView.measure(600, 400);
// webView.layout(0, 0, 600, 400);
val webSettings = webView.settings
webSettings.javaScriptEnabled = true
// webSettings.loadWithOverviewMode = true
// webSettings.useWideViewPort = true
// webSettings.javaScriptCanOpenWindowsAutomatically = true
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
// webSettings.allowFileAccessFromFileURLs = true
// webSettings.allowUniversalAccessFromFileURLs = true
// }
webView.webChromeClient = object : WebChromeClient() {
override fun onProgressChanged(view: WebView?, newProgress: Int) {
super.onProgressChanged(view, newProgress)
Log.d("appLog", "onProgressChanged:$newProgress " + view?.url)
}
override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean {
if (consoleMessage != null)
Log.d("appLog", "webViewConsole:" + consoleMessage.message())
return super.onConsoleMessage(consoleMessage)
}
}
webView.webViewClient = object : WebViewClient() {
override fun onReceivedError(view: WebView, request: WebResourceRequest, error: WebResourceError) {
Log.d("appLog", "error $request $error")
}
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
Log.d("appLog", "onPageFinished:$url")
}
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
Log.d("appLog", "shouldInterceptRequest:${request.url}")
else
Log.d("appLog", "shouldInterceptRequest")
return super.shouldInterceptRequest(view, request)
}
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
Log.d("appLog", "onPageStarted:$url hasFavIcon?${favicon != null}")
}
}
return webView
}
@TargetApi(Build.VERSION_CODES.M)
fun isSystemAlertPermissionGranted(@NonNull context: Context): Boolean {
return Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1 || Settings.canDrawOverlays(context)
}
fun requestSystemAlertPermission(context: Activity?, fragment: Fragment?, requestCode: Int) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1)
return
//http://developer.android.com/reference/android/Manifest.permission.html#SYSTEM_ALERT_WINDOW
val packageName = if (context == null) fragment!!.activity!!.packageName else context.packageName
var intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName"))
try {
if (fragment != null)
fragment.startActivityForResult(intent, requestCode)
else
context!!.startActivityForResult(intent, requestCode)
} catch (e: Exception) {
intent = Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS)
if (fragment != null)
fragment.startActivityForResult(intent, requestCode)
else
context!!.startActivityForResult(intent, requestCode)
}
}
/**
* requests (if needed) system alert permission. returns true iff requested.
* WARNING: You should always consider checking the result of this function
*/
fun requestSystemAlertPermissionIfNeeded(activity: Activity?, fragment: Fragment?, requestCode: Int): Boolean {
val context = activity ?: fragment!!.activity
if (isSystemAlertPermissionGranted(context!!))
return false
requestSystemAlertPermission(activity, fragment, requestCode)
return true
}
}
MyService.kt
class MyService : Service() {
override fun onBind(intent: Intent): IBinder? = null
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
run {
//general
val channel = NotificationChannel("channel_id__general", "channel_name__general", NotificationManager.IMPORTANCE_DEFAULT)
channel.enableLights(false)
channel.setSound(null, null)
notificationManager.createNotificationChannel(channel)
}
}
val builder = NotificationCompat.Builder(this, "channel_id__general")
builder.setSmallIcon(android.R.drawable.sym_def_app_icon).setContentTitle(getString(R.string.app_name))
startForeground(1, builder.build())
}
@SuppressLint("SetJavaScriptEnabled")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
val params = WindowManager.LayoutParams(
android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY else WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
PixelFormat.TRANSLUCENT
)
params.gravity = Gravity.TOP or Gravity.START
params.x = 0
params.y = 0
params.width = 0
params.height = 0
val webView = Util.getNewWebView(this)
// webView.loadUrl("https://www.google.com/")
// webView.loadUrl("https://www.google.com/")
// webView.loadUrl("")
// Handler().postDelayed( {
// webView.loadUrl("")
webView.loadUrl("https://imgur.com/a/GPlx4?desktop=1")
// },5000L)
// webView.loadUrl("https://imgur.com/a/GPlx4?desktop=1")
windowManager.addView(webView, params)
return super.onStartCommand(intent, flags, startId)
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startServiceButton.setOnClickListener {
if (!Util.requestSystemAlertPermissionIfNeeded(this, null, REQUEST_DRAW_ON_TOP))
ContextCompat.startForegroundService(this@MainActivity, Intent(this@MainActivity, MyService::class.java))
}
startWorkerButton.setOnClickListener {
val workManager = WorkManager.getInstance()
workManager.cancelAllWorkByTag(WORK_TAG)
val builder = OneTimeWorkRequest.Builder(BackgroundWorker::class.java).addTag(WORK_TAG)
builder.setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresCharging(false).build())
builder.setInitialDelay(5, TimeUnit.SECONDS)
workManager.enqueue(builder.build())
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_DRAW_ON_TOP && Util.isSystemAlertPermissionGranted(this))
ContextCompat.startForegroundService(this@MainActivity, Intent(this@MainActivity, MyService::class.java))
}
class BackgroundWorker : Worker() {
val handler = Handler(Looper.getMainLooper())
override fun doWork(): Result {
Log.d("appLog", "doWork started")
handler.post {
val webView = Util.getNewWebView(applicationContext)
// webView.loadUrl("https://www.google.com/")
webView.loadUrl("https://www.google.com/")
// webView.loadUrl("")
// Handler().postDelayed({
// // webView.loadUrl("")
//// webView.loadUrl("https://imgur.com/a/GPlx4?desktop=1")
// webView.loadUrl("https://www.reddit.com/")
//
// }, 1000L)
// webView.loadUrl("https://imgur.com/a/GPlx4?desktop=1")
}
Thread.sleep(20000L)
Log.d("appLog", "doWork finished")
return Worker.Result.SUCCESS
}
}
companion object {
const val REQUEST_DRAW_ON_TOP = 1
const val WORK_TAG = "WORK_TAG"
}
}
activity_main.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center"
android:orientation="vertical" tools:context=".MainActivity">
<Button
android:id="@+id/startServiceButton" android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="start service"/>
<Button
android:id="@+id/startWorkerButton" android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="start worker"/>
</LinearLayout>
gradle 文件
...
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.0.0-rc02'
implementation 'androidx.core:core-ktx:1.0.0-rc02'
implementation 'androidx.constraintlayout:constraintlayout:1.1.2'
def work_version = "1.0.0-alpha08"
implementation "android.arch.work:work-runtime-ktx:$work_version"
implementation "android.arch.work:work-firebase:$work_version"
}
list
<manifest package="com.example.webviewinbackgroundtest" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<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/AppTheme"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service
android:name=".MyService" android:enabled="true" android:exported="true"/>
</application>
</manifest>
主要问题:是否可以在 Worker 中使用 WebView?
为什么在 Worker 中它似乎在 Android P 上工作正常,但在其他人身上却不行?
为什么有时它对 Worker 有效?
是否有替代方案,要么在 Worker 中执行,要么有替代 WebView 的替代方案,能够执行加载网页并在其上运行 Javascripts 的相同操作?
最佳答案
我认为我们需要另一种工具来应对此类情况。我的诚实意见是,它是一个 WebView
,毕竟是一个 View ,旨在显示网页。我知道我们需要实现 hacky 解决方案来解决此类情况,但我相信这些也不是 webView 的问题。
我认为的解决方案是,与其观察网页和监听 javaScript 的变化,不如通过适当的消息(推送/套接字/网络服务)将变化传递给应用程序。
如果不能这样做,我认为请求 ( https://issuetracker.google.com/issues/113346931) 不应该是“能够在服务中运行 WebView”,而是对 SDK 的适当添加,它将执行您提到的操作。
关于android - 可以在Worker中使用WebView吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52162316/
我最近在/ drawable中添加了一些.gifs,以便可以将它们与按钮一起使用。这个工作正常(没有错误)。现在,当我重建/运行我的应用程序时,出现以下错误: Error: Gradle: Execu
Android 中有返回内部存储数据路径的方法吗? 我有 2 部 Android 智能手机(Samsung s2 和 s7 edge),我在其中安装了一个应用程序。我想使用位于这条路径中的 sqlit
这个问题在这里已经有了答案: What's the difference between "?android:" and "@android:" in an android layout xml f
我只想知道 android 开发手机、android 普通手机和 android root 手机之间的实际区别。 我们不能从实体店或除 android marketplace 以外的其他地方购买开发手
自Gradle更新以来,我正在努力使这个项目达到标准。这是一个团队项目,它使用的是android-apt插件。我已经进行了必要的语法更改(编译->实现和apt->注释处理器),但是编译器仍在告诉我存在
我是android和kotlin的新手,所以请原谅要解决的一个非常简单的问题! 我已经使用导航体系结构组件创建了一个基本应用程序,使用了底部的导航栏和三个导航选项。每个导航选项都指向一个专用片段,该片
我目前正在使用 Facebook official SDK for Android . 我现在正在使用高级示例应用程序,但我不知道如何让它获取应用程序墙/流/状态而不是登录的用户。 这可能吗?在那种情
我在下载文件时遇到问题, 我可以在模拟器中下载文件,但无法在手机上使用。我已经定义了上网和写入 SD 卡的权限。 我在服务器上有一个 doc 文件,如果用户单击下载。它下载文件。这在模拟器中工作正常但
这个问题在这里已经有了答案: What is the difference between gravity and layout_gravity in Android? (22 个答案) 关闭 9
任何人都可以告诉我什么是 android 缓存和应用程序缓存,因为当我们谈论缓存清理应用程序时,它的作用是,缓存清理概念是清理应用程序缓存还是像内存管理一样主存储、RAM、缓存是不同的并且据我所知,缓
假设应用程序 Foo 和 Eggs 在同一台 Android 设备上。任一应用程序都可以获取设备上所有应用程序的列表。一个应用程序是否有可能知道另一个应用程序是否已经运行以及运行了多长时间? 最佳答案
我有点困惑,我只看到了从 android 到 pc 或者从 android 到 pc 的例子。我需要制作一个从两部手机 (android) 连接的 android 应用程序进行视频聊天。我在想,我知道
用于使用 Android 以编程方式锁定屏幕。我从 Stackoverflow 之前关于此的问题中得到了一些好主意,并且我做得很好,但是当我运行该代码时,没有异常和错误。而且,屏幕没有锁定。请在这段代
文档说: android:layout_alignParentStart If true, makes the start edge of this view match the start edge
我不知道这两个属性和高度之间的区别。 以一个TextView为例,如果我将它的layout_width设置为wrap_content,并将它的width设置为50 dip,会发生什么情况? 最佳答案
这两个属性有什么关系?如果我有 android:noHistory="true",那么有 android:finishOnTaskLaunch="true" 有什么意义吗? 最佳答案 假设您的应用中有
我是新手,正在尝试理解以下 XML 代码: 查看 developer.android.com 上的文档,它说“starStyle”是 R.attr 中的常量, public static final
在下面的代码中,为什么当我设置时单选按钮的外观会发生变化 android:layout_width="fill_parent" 和 android:width="fill_parent" 我说的是
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 9
假设我有一个函数 fun myFunction(name:String, email:String){},当我调用这个函数时 myFunction('Ali', 'ali@test.com ') 如何
我是一名优秀的程序员,十分优秀!