Commit fadee507 by 王一诺

webClient 使用jni 改写

parents
Showing with 8610 additions and 0 deletions
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
# Default ignored files
/shelf/
/workspace.xml
WebViewDemo
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="libwebview">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="sdk">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/libwebview" />
<option value="$PROJECT_DIR$/sdk" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>
\ No newline at end of file
/build
\ No newline at end of file
plugins {
alias(libs.plugins.android.application)
}
android {
namespace 'com.coolook.webview.demo'
compileSdk 35
defaultConfig {
applicationId "com.coolook.webview.demo"
minSdk 24
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation(project(':libwebview'))
implementation libs.appcompat
implementation libs.material
implementation libs.activity
implementation libs.constraintlayout
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
\ No newline at end of file
package com.coolook.webview.demo;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.coolook.webview.demo", appContext.getPackageName());
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.WebViewDemo"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
\ No newline at end of file
package com.coolook.webview.demo;
import android.os.Bundle;
import android.util.Log;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.coolook.view.ScrollHelper;
import com.coolook.view.listener.ScrollListener;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
WebView webView = findViewById(R.id.webView);
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true); // 启用JS(必须)
webSettings.setDomStorageEnabled(true); // 启用DOM存储(单页应用必需)
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); // 允许混合内容(http/https)
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
webView.loadUrl("https://www.baidu.com");
webView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
// webView.measure(0, 0);
// int measuredHeight = webView.getMeasuredHeight();
// int scrollY = webView.getScrollY();
// int webViewHeight = webView.getHeight();
// int webViewWidth = webView.getWidth();
// Log.i("yyyyyy","yScroolTo,measuredHeight: " + measuredHeight + ",scrollY: " + scrollY + ",webViewWidth: " + webViewWidth + ",webViewHeight: " + webViewHeight);
// ScrollHelper.yScrollTo(webView, 400, new ScrollListener() {
// @Override
// public void onScrollEnd() {
// Log.i("onScrollEnd", "onScrollEnd!!!!");
// }
//
// @Override
// public void onScrollError(String message) {
//
// }
// }, 100);
// ScrollHelper.yScrollRandom(webView, false, 3, 30, 30, 30, 30, new ScrollListener() {
//
// @Override
// public void onScrollEnd() {
// Log.i("onScrollEnd", "yScrollRandom!!!!");
// }
//
// @Override
// public void onScrollError(String message) {
// Log.i("onScrollError", "onScrollError!!!!");
// }
// });
ScrollHelper.handlerClickEvent(webView, 100, 100);
}
});
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:startY="49.59793"
android:startX="42.9492"
android:endY="92.4963"
android:endX="85.84757"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<WebView
android:layout_width="wrap_content"
android:layout_height="400dp"
android:id="@+id/webView"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
\ No newline at end of file
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.WebViewDemo" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your dark theme here. -->
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
</style>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
\ No newline at end of file
<resources>
<string name="app_name">WebViewDemo</string>
</resources>
\ No newline at end of file
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.WebViewDemo" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<style name="Theme.WebViewDemo" parent="Base.Theme.WebViewDemo" />
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>
\ No newline at end of file
package com.coolook.webview.demo;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}
\ No newline at end of file
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
}
\ No newline at end of file
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
\ No newline at end of file
[versions]
agp = "8.6.0"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
appcompat = "1.7.1"
material = "1.12.0"
activity = "1.10.1"
constraintlayout = "2.2.1"
[libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" }
ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
No preview for this file type
#Thu Jun 19 11:14:50 CST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
/build
\ No newline at end of file
plugins {
id 'com.android.library'
}
android {
namespace 'com.coolook.libwebview'
compileSdk 34
defaultConfig {
minSdk 24
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk {
//noinspection ChromeOsAbiSupport
abiFilters 'arm64-v8a', 'armeabi-v7a'//, 'arm64-v8a', 'x86'//, 'x86_64' // 确保包含设备所用的 ABI
}
externalNativeBuild {
cmake {
cppFlags "-std=c++17 -frtti -fexceptions" // 确保支持 RTTI 和异常
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.22.1'
}
}
}
dependencies {
implementation libs.appcompat
implementation libs.material
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
\ No newline at end of file
package com.coolook.view;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.coolook.libwebview", appContext.getPackageName());
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.WebViewDemo" />
</manifest>
\ No newline at end of file
cmake_minimum_required(VERSION 3.22.1)
add_subdirectory(scrollHelper)
cmake_minimum_required(VERSION 3.22.1)
project("scrollHelper")
add_library( # 库名称
scrollHelper
# 库类型:SHARED 表示动态库
SHARED
# 源文件路径(相对路径)
scrollHelper.c
common.c
listener.c
)
# 查找和链接 Android 特有的库
find_library( # 库变量名
log-lib
# 要查找的系统库名称
log )
find_library(android-lib android)
# 链接目标库需要的库文件
target_link_libraries( # 目标库名称
scrollHelper
# 链接 Android 核心库
${log-lib}
${android-lib} )
#ifndef _COMMON_H
#define _COMMON_H
#include "common.h"
#endif
//
// Created by 王一诺 on 2025/6/23.
//
// 初始化随机数种子
void init_random() {
static int initialized = 0;
if (!initialized) {
srand(time(NULL));
initialized = 1;
}
}
// 生成随机浮点数
float random_float(float min, float max) {
init_random();
return min + (max - min) * ((float) rand() / RAND_MAX);
}
// 处理触摸事件
void dispatch_touch_event(JNIEnv *env,
jobject view,
jlong downTime,
jlong eventTime,
jint action,
jobjectArray pointerProperties,
jobjectArray pointerCoords,
jint deviceId) {
//MotionEvent motionEventDown = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 1, pointPropsArr, pointerCoordsArr, 0, 0, 0, 0, deviceId, 0, 0x1001, 0);
jclass motionEventClass = (*env)->FindClass(env, "android/view/MotionEvent");
jmethodID obtainMethod = (*env)->GetStaticMethodID(env,
motionEventClass,
"obtain",
"(JJII[Landroid/view/MotionEvent$PointerProperties;[Landroid/view/MotionEvent$PointerCoords;IIFFIIII)Landroid/view/MotionEvent;");
if (obtainMethod == NULL) {
LOGI("Failed to find MotionEvent.obtain() method");
return;
}
jobject motionEvent = (*env)->CallStaticObjectMethod(env,
motionEventClass,
obtainMethod,
downTime,
eventTime,
action,
1,
pointerProperties,
pointerCoords,
0,
0,
0.0f,
0.0f,
deviceId,
0,
0x1001,
0);
if (motionEvent == NULL) {
LOGI("Failed to create MotionEvent object");
return;
}
//view.dispatchTouchEvent(motionEventDown);
jclass viewClass = (*env)->GetObjectClass(env, view);
jmethodID dispatchMethod = (*env)->GetMethodID(env, viewClass, "dispatchTouchEvent",
"(Landroid/view/MotionEvent;)Z");
if (dispatchMethod == NULL) {
LOGI("Failed to create viewClass object");
return;
}
(*env)->CallBooleanMethod(env, view, dispatchMethod, motionEvent);
//motionEventDown.recycle();
jmethodID recycleMethod = (*env)->GetMethodID(env, motionEventClass, "recycle", "()V");
(*env)->CallVoidMethod(env, motionEvent, recycleMethod);
(*env)->DeleteLocalRef(env, motionEvent);
}
// 创建PointerProperties对象
jobject create_pointer_properties(JNIEnv *env, jint id, jint toolType) {
jclass pointerPropsClass = (*env)->FindClass(env, "android/view/MotionEvent$PointerProperties");
jmethodID constructor = (*env)->GetMethodID(env, pointerPropsClass, "<init>", "()V");
if (constructor == NULL) {
return NULL;
}
jobject pointerProps = (*env)->NewObject(env, pointerPropsClass, constructor);
if (pointerProps == NULL) {
return NULL;
}
jfieldID idField = (*env)->GetFieldID(env, pointerPropsClass, "id", "I");
jfieldID toolTypeField = (*env)->GetFieldID(env, pointerPropsClass, "toolType", "I");
(*env)->SetIntField(env, pointerProps, idField, id);
(*env)->SetIntField(env, pointerProps, toolTypeField, toolType);
return pointerProps;
}
// 创建PointerCoords对象
jobject create_pointer_coords(JNIEnv *env, float x, float y, float pressure,
float touchMinor, float toolMinor,
float touchMajor, float toolMajor,
float orientation, float size) {
jclass pointerCoordsClass = (*env)->FindClass(env, "android/view/MotionEvent$PointerCoords");
jmethodID constructor = (*env)->GetMethodID(env, pointerCoordsClass, "<init>", "()V");
if (constructor == NULL) {
return NULL;
}
jobject pointerCoords = (*env)->NewObject(env, pointerCoordsClass, constructor);
if (pointerCoords == NULL) {
return NULL;
}
jfieldID xField = (*env)->GetFieldID(env, pointerCoordsClass, "x", "F");
jfieldID yField = (*env)->GetFieldID(env, pointerCoordsClass, "y", "F");
jfieldID pressureField = (*env)->GetFieldID(env, pointerCoordsClass, "pressure", "F");
jfieldID touchMinorField = (*env)->GetFieldID(env, pointerCoordsClass, "touchMinor", "F");
jfieldID toolMinorField = (*env)->GetFieldID(env, pointerCoordsClass, "toolMinor", "F");
jfieldID touchMajorField = (*env)->GetFieldID(env, pointerCoordsClass, "touchMajor", "F");
jfieldID toolMajorField = (*env)->GetFieldID(env, pointerCoordsClass, "toolMajor", "F");
jfieldID orientationField = (*env)->GetFieldID(env, pointerCoordsClass, "orientation", "F");
jfieldID sizeField = (*env)->GetFieldID(env, pointerCoordsClass, "size", "F");
(*env)->SetFloatField(env, pointerCoords, xField, x);
(*env)->SetFloatField(env, pointerCoords, yField, y);
(*env)->SetFloatField(env, pointerCoords, pressureField, pressure);
(*env)->SetFloatField(env, pointerCoords, touchMinorField, touchMinor);
(*env)->SetFloatField(env, pointerCoords, toolMinorField, toolMinor);
(*env)->SetFloatField(env, pointerCoords, touchMajorField, touchMajor);
(*env)->SetFloatField(env, pointerCoords, toolMajorField, toolMajor);
(*env)->SetFloatField(env, pointerCoords, orientationField, orientation);
(*env)->SetFloatField(env, pointerCoords, sizeField, size);
return pointerCoords;
}
jlong get_current_time_millis(JNIEnv *env) {
jclass systemClockClass = (*env)->FindClass(env, "android/os/SystemClock");
if (systemClockClass == NULL) {
return 0;
}
jmethodID uptimeMillisMethod = (*env)->GetStaticMethodID(env, systemClockClass, "uptimeMillis",
"()J");
if (uptimeMillisMethod == NULL) {
return 0;
}
return (*env)->CallStaticLongMethod(env, systemClockClass, uptimeMillisMethod);
}
// 实现scrollScreen函数
void scroll_screen(JNIEnv *env,
jint type,
jobject view,
jfloat x1,
jfloat x2,
jfloat y1,
jfloat y2,
jobject listener) {
//final long downTime = SystemClock.uptimeMillis();
jlong downTime = (jlong) get_current_time_millis(env);
/*
* MotionEvent.PointerProperties pointProp = new MotionEvent.PointerProperties();
pointProp.id = 0;
pointProp.toolType = MotionEvent.TOOL_TYPE_FINGER;
*/
jobject pointerProps = create_pointer_properties(env, 0, 1); // 1 = MotionEvent.TOOL_TYPE_FINGER
if (pointerProps == NULL) return;
//final MotionEvent.PointerProperties[] pointPropsArr = {pointProp};
jobjectArray propsArray = (*env)->NewObjectArray(env, 1, (*env)->FindClass(env,
"android/view/MotionEvent$PointerProperties"),
NULL);
(*env)->SetObjectArrayElement(env, propsArray, 0, pointerProps);
// 初始化PointerCoords
float x = x1;
float y = y1;
float pressure = 0.5f + random_float(0.0f, 0.5f) + random_float(0.0f, 0.5f);
float touchMinor = 80.0f + random_float(0.0f, 30.0f);
float toolMinor = touchMinor;
float touchMajor = touchMinor + random_float(0.0f, 30.0f);
float toolMajor = touchMajor;
float orientation = 0.3f + random_float(0.0f, 0.5f);
// 创建初始PointerCoords用于DOWN事件
jobject pointerCoords = create_pointer_coords(env,
x,
y,
pressure,
touchMinor,
toolMinor,
touchMajor,
toolMajor,
orientation,
0.0f);
if (pointerCoords == NULL) {
(*env)->DeleteLocalRef(env, pointerProps);
(*env)->DeleteLocalRef(env, propsArray);
return;
}
jobjectArray coordsArray = (*env)->NewObjectArray(env,
1,
(*env)->FindClass(env, "android/view/MotionEvent$PointerCoords"), NULL);
(*env)->SetObjectArrayElement(env, coordsArray, 0, pointerCoords);
// 发送DOWN事件
dispatch_touch_event(env, view, downTime, downTime, 0, propsArray, coordsArray, 1234567890);
// 计算动画持续时间
//ValueAnimator anim = ValueAnimator.ofFloat(y1, y2);
jclass valueAnimatorClass = (*env)->FindClass(env, "android/animation/ValueAnimator");
//public static ValueAnimator ofFloat(float... values)
jmethodID ofFloatMethod = (*env)->GetStaticMethodID(env, valueAnimatorClass, "ofFloat", "([F)Landroid/animation/ValueAnimator;");
jfloatArray floatArray = (*env)->NewFloatArray(env, 2);
(*env)->SetFloatArrayRegion(env, floatArray, 0, 2, (jfloat[]) {y1, y2});
jobject anim = (*env)->CallStaticObjectMethod(env, valueAnimatorClass, ofFloatMethod,
floatArray);
jlong duration;
if (type == 1) {
duration = 500 + (jlong) random_float(0.0f, 500.0f);
} else {
duration = 100 + (jlong) random_float(0.0f, 500.0f);
}
jmethodID setDurationMethod = (*env)->GetMethodID(env, valueAnimatorClass, "setDuration",
"(J)Landroid/animation/ValueAnimator;");
(*env)->CallObjectMethod(env, anim, setDurationMethod, duration);
//anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
jmethodID addUpdateListenerMethod = (*env)->GetMethodID(env,
valueAnimatorClass,
"addUpdateListener",
"(Landroid/animation/ValueAnimator$AnimatorUpdateListener;)V");
(*env)->CallVoidMethod(env, anim, addUpdateListenerMethod,
create_update_listener(env, y1, y2, x1, x2, propsArray, coordsArray,
view));
//anim.addListener(new AnimatorListenerAdapter()
jmethodID addListenerMethod = (*env)->GetMethodID(env,
valueAnimatorClass,
"addListener",
"(Landroid/animation/Animator$AnimatorListener;)V");
(*env)->CallVoidMethod(env, anim, addListenerMethod, create_animator_listener(env, y2, x2, propsArray, coordsArray, view, listener));
//anim.start();
jmethodID startMethod = (*env)->GetMethodID(env, valueAnimatorClass, "start", "()V");
(*env)->CallVoidMethod(env, anim, startMethod);
// 释放资源
(*env)->DeleteLocalRef(env, pointerProps);
(*env)->DeleteLocalRef(env, propsArray);
(*env)->DeleteLocalRef(env, pointerCoords);
(*env)->DeleteLocalRef(env, coordsArray);
}
// 实现YScrollListener的创建和回调
jobject
create_y_scroll_listener(JNIEnv *env, jobject webView, jfloat height, jobject originalListener,
jlong time) {
jclass yScrollListenerClass = (*env)->FindClass(env,
"com/coolook/view/listener/impl/YScrollListener");
if (yScrollListenerClass == NULL) {
return NULL;
}
jmethodID constructor = (*env)->GetMethodID(env,
yScrollListenerClass,
"<init>",
"(Landroid/webkit/WebView;FLcom/coolook/view/listener/ScrollListener;J)V");
if (constructor == NULL) {
return NULL;
}
jobject listener = (*env)->NewObject(env, yScrollListenerClass, constructor, webView, height,
originalListener, time);
return listener;
}
// 实现RandomScrollListener的创建和回调
jobject create_random_scroll_listener(JNIEnv *env, jobject webView, jboolean isUp,
jint slideNum, jint position1, jint position2,
jint distance1, jint distance2,
jobject originalListener) {
jclass randomScrollListenerClass = (*env)->FindClass(env,
"com/coolook/view/listener/impl/RandomScrollListener");
if (randomScrollListenerClass == NULL) {
return NULL;
}
jmethodID constructor = (*env)->GetMethodID(env, randomScrollListenerClass, "<init>",
"(Landroid/webkit/WebView;ZIIIIILcom/coolook/view/listener/ScrollListener;)V");
if (constructor == NULL) {
return NULL;
}
jobject listener = (*env)->NewObject(env, randomScrollListenerClass, constructor,
webView, isUp, slideNum, position1, position2,
distance1, distance2, originalListener);
return listener;
}
jobject create_update_listener(JNIEnv *env, jfloat y1, jfloat y2, jfloat x1,
jfloat x2, jobjectArray propsArray, jobjectArray coordsArray,
jobject view) {
jclass MyAnimatorUpdateListenerClass = (*env)->FindClass(env,
"com/coolook/view/listener/MyAnimatorUpdateListener");
jmethodID constructor = (*env)->GetMethodID(env,
MyAnimatorUpdateListenerClass,
"<init>",
"(FFFFLjava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V");
jobject updateListener = (*env)->NewObject(env, MyAnimatorUpdateListenerClass, constructor, y1,
y2, x1, x2, propsArray, coordsArray, view);
return updateListener;
}
jobject create_animator_listener(JNIEnv *env, jfloat y2, jfloat x2, jobjectArray propsArray,
jobjectArray coordsArray, jobject view, jobject listener) {
jclass MyAnimatorListenerClass = (*env)->FindClass(env,
"com/coolook/view/listener/MyAnimatorListenerAdapter");
jmethodID constructor = (*env)->GetMethodID(env, MyAnimatorListenerClass, "<init>",
"(FFLjava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lcom/coolook/view/listener/ScrollListener;)V");
jobject animatorListener = (*env)->NewObject(env, MyAnimatorListenerClass, constructor, y2, x2,
propsArray, coordsArray, view, listener);
return animatorListener;
}
\ No newline at end of file
//
// Created by 王一诺 on 2025/6/23.
//
#ifndef WEBVIEWDEMO_COMMON_H
#define WEBVIEWDEMO_COMMON_H
#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <android/log.h>
#include <unistd.h>
#define LOG_TAG "ScrollHelper"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
void init_random();
float random_float(float min, float max);
void dispatch_touch_event(JNIEnv *env,
jobject view,
jlong downTime,
jlong eventTime,
jint action,
jobjectArray pointerProperties,
jobjectArray pointerCoords,
jint deviceId);
jobject create_pointer_properties(JNIEnv *env, jint id, jint toolType);
jobject create_pointer_coords(JNIEnv *env,
float x,
float y,
float pressure,
float touchMinor,
float toolMinor,
float touchMajor,
float toolMajor,
float orientation,
float size);
jlong get_current_time_millis(JNIEnv *env);
void scroll_screen(JNIEnv *env,
jint type,
jobject view,
jfloat x1,
jfloat x2,
jfloat y1,
jfloat y2,
jobject listener);
jobject create_y_scroll_listener(JNIEnv *env,
jobject webView,
jfloat height,
jobject originalListener,
jlong time);
jobject create_random_scroll_listener(JNIEnv *env,
jobject webView,
jboolean isUp,
jint slideNum,
jint position1,
jint position2,
jint distance1,
jint distance2,
jobject originalListener);
jobject create_update_listener(JNIEnv *env,
jfloat y1,
jfloat y2,
jfloat x1,
jfloat x2,
jobjectArray propsArray,
jobjectArray coordsArray,
jobject view);
jobject create_animator_listener(JNIEnv *env,
jfloat y2,
jfloat x2,
jobjectArray propsArray,
jobjectArray coordsArray,
jobject view,
jobject listener);
#endif //WEBVIEWDEMO_COMMON_H
#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <android/log.h>
#include <unistd.h>
#include "common.h"
//
// Created by 王一诺 on 2025/6/23.
//
JNIEXPORT void JNICALL
Java_com_coolook_view_listener_MyAnimatorUpdateListener_onAnimationUpdate(JNIEnv *env,
jobject thiz, // `AnimatorUpdateListener` 实例
jobject animation // `ValueAnimator` 实例
) {
// 1. 获取 ValueAnimator 类的 getAnimatedValue() 方法
jclass animatorClass = (*env)->GetObjectClass(env, animation);
jmethodID getAnimatedValueMethod = (*env)->GetMethodID(env, animatorClass, "getAnimatedValue",
"()Ljava/lang/Object;");
// 2. 调用 getAnimatedValue()
jobject animatedValue = (*env)->CallObjectMethod(env, animation, getAnimatedValueMethod);
// 3. 检查返回值的类型(Float 或 Integer)并转换为 float
jfloat y = 0.0f;
if (animatedValue != NULL) {
if ((*env)->IsInstanceOf(env, animatedValue, (*env)->FindClass(env, "java/lang/Float"))) {
jclass floatClass = (*env)->GetObjectClass(env, animatedValue);
jmethodID floatValueMethod =(*env)->GetMethodID(env, floatClass, "floatValue", "()F");
// 如果返回值是 Float
jfloat floatValue = (*env)->CallFloatMethod(env, animatedValue,floatValueMethod);
y = floatValue;
} else if ((*env)->IsInstanceOf(env, animatedValue,
(*env)->FindClass(env, "java/lang/Integer"))) {
jclass intClass =(*env)->GetObjectClass( env, animatedValue);
jmethodID intValueMethod =(*env)->GetMethodID(env, intClass,"intValue", "()I");
// 如果返回值是 Integer
jint intValue = (*env)->CallIntMethod(env, animatedValue, intValueMethod);
y = (jfloat) intValue; // 转为 float
} else {
return;
}
}
jclass listenerClass = (*env)->GetObjectClass(env, thiz);
jfieldID fieldId_y1 = (*env)->GetFieldID(env, listenerClass, "y1", "F");
jfloat y1 = (*env)->GetFloatField(env, thiz, fieldId_y1);
jfieldID fieldId_y2 = (*env)->GetFieldID(env, listenerClass, "y2", "F");
jfloat y2 = (*env)->GetFloatField(env, thiz, fieldId_y2);
jfieldID fieldId_x1 = (*env)->GetFieldID(env, listenerClass, "x1", "F");
jfloat x1 = (*env)->GetFloatField(env, thiz, fieldId_x1);
jfieldID fieldId_x2 = (*env)->GetFieldID(env, listenerClass, "x2", "F");
jfloat x2 = (*env)->GetFloatField(env, thiz, fieldId_x2);
//float x = (y - y1) / (y2 - y1) * (x2 - x1) + x1;
jfloat x = (y - y1) / (y2 - y1) * (x2 - x1) + x1;
jfieldID pointPropsArrId = (*env)->GetFieldID(env, listenerClass, "pointPropsArr",
"Ljava/lang/Object;");
jobject pointPropsArr = (*env)->GetObjectField(env, thiz, pointPropsArrId);
jfieldID pointerCoordsArrId = (*env)->GetFieldID(env, listenerClass, "pointerCoordsArr",
"Ljava/lang/Object;");
jobject pointerCoordsArr = (*env)->GetObjectField(env, thiz, pointerCoordsArrId);
jclass objectArrayClass = (*env)->FindClass(env, "[Ljava/lang/Object;");
if (!(*env)->IsInstanceOf(env, pointerCoordsArr, objectArrayClass)) {
return;
}
// 5. 获取第0个元素 (PointerCoords)
jobject pointerCoords = (*env)->GetObjectArrayElement(env, (jobjectArray) pointerCoordsArr, 0);
if (pointerCoords == NULL) {
return;
}
jclass pointerCoordsClass = (*env)->FindClass(env, "android/view/MotionEvent$PointerCoords");
if (pointerCoordsClass == NULL) {
return;
}
jfieldID xField = (*env)->GetFieldID(env, pointerCoordsClass, "x", "F");
jfieldID yField = (*env)->GetFieldID(env, pointerCoordsClass, "y", "F");
if (xField == NULL || yField == NULL) {
return;
}
// 7. 修改 x 和 y
(*env)->SetFloatField(env, pointerCoords, xField, x);
(*env)->SetFloatField(env, pointerCoords, yField, y);
jfieldID viewId = (*env)->GetFieldID(env, listenerClass, "view", "Ljava/lang/Object;");
jobject view = (*env)->GetObjectField(env, thiz, viewId);
//MotionEvent.ACTION_MOVE
dispatch_touch_event(env, view, get_current_time_millis(env), get_current_time_millis(env), 2,
pointPropsArr, pointerCoordsArr, 1234567890);
}
JNIEXPORT void JNICALL
Java_com_coolook_view_listener_MyAnimatorListenerAdapter_onAnimationEnd(JNIEnv *env, jobject thiz,
jobject animation) {
jclass listenerClass = (*env)->GetObjectClass(env, thiz);
jfieldID fieldId_y2 = (*env)->GetFieldID(env, listenerClass, "y2", "F");
jfloat y2 = (*env)->GetFloatField(env, thiz, fieldId_y2);
jfieldID fieldId_x2 = (*env)->GetFieldID(env, listenerClass, "x2", "F");
jfloat x2 = (*env)->GetFloatField(env, thiz, fieldId_x2);
jfieldID pointPropsArrId = (*env)->GetFieldID(env, listenerClass, "pointPropsArr",
"Ljava/lang/Object;");
jobject pointPropsArr = (*env)->GetObjectField(env, thiz, pointPropsArrId);
jfieldID pointerCoordsArrId = (*env)->GetFieldID(env, listenerClass, "pointerCoordsArr",
"Ljava/lang/Object;");
jobject pointerCoordsArr = (*env)->GetObjectField(env, thiz, pointerCoordsArrId);
jclass objectArrayClass = (*env)->FindClass(env, "[Ljava/lang/Object;");
if (!(*env)->IsInstanceOf(env, pointerCoordsArr, objectArrayClass)) {
return;
}
// 5. 获取第0个元素 (PointerCoords)
jobject pointerCoords = (*env)->GetObjectArrayElement(env, (jobjectArray) pointerCoordsArr, 0);
if (pointerCoords == NULL) {
return;
}
jclass pointerCoordsClass = (*env)->FindClass(env, "android/view/MotionEvent$PointerCoords");
if (pointerCoordsClass == NULL) {
return;
}
jfieldID xField = (*env)->GetFieldID(env, pointerCoordsClass, "x", "F");
jfieldID yField = (*env)->GetFieldID(env, pointerCoordsClass, "y", "F");
if (xField == NULL || yField == NULL) {
return;
}
// 7. 修改 x 和 y
(*env)->SetFloatField(env, pointerCoords, xField, x2);
(*env)->SetFloatField(env, pointerCoords, yField, y2);
jfieldID viewId = (*env)->GetFieldID(env, listenerClass, "view", "Ljava/lang/Object;");
jobject view = (*env)->GetObjectField(env, thiz, viewId);
//MotionEvent.ACTION_UP
dispatch_touch_event(env, view, get_current_time_millis(env), get_current_time_millis(env), 1,
pointPropsArr, pointerCoordsArr, 1234567890);
jfieldID listenerId = (*env)->GetFieldID(env, listenerClass, "listerner",
"Lcom/coolook/view/listener/ScrollListener;");
jobject listenerObj = (*env)->GetObjectField(env, thiz, listenerId);
jclass scrollListenerClass = (*env)->FindClass(env, "com/coolook/view/listener/ScrollListener");
jmethodID onScrollEnd = (*env)->GetMethodID(env, scrollListenerClass, "onScrollEnd", "()V");
(*env)->CallVoidMethod(env, listenerObj, onScrollEnd);
}
\ No newline at end of file
#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <android/log.h>
#include <unistd.h>
#include "common.h"
// JNI实现 - 竖向滑动
JNIEXPORT void JNICALL
Java_com_coolook_view_ScrollHelper_nativeYScrollTo(JNIEnv *env,
jclass clazz,
jobject webView,
jfloat height,
jobject listener,
jlong time) {
// 获取WebView的高度和宽度
jclass viewClass = (*env)->GetObjectClass(env, webView);
//webView.measure(0, 0);
jmethodID measureMethod = (*env)->GetMethodID(env, viewClass, "measure", "(II)V");
(*env)->CallVoidMethod(env, webView, measureMethod, 0, 0);
//int measuredHeight = webView.getMeasuredHeight();
jmethodID getMeasuredHeightMethod = (*env)->GetMethodID(env, viewClass, "getMeasuredHeight",
"()I");
int measuredHeight = (*env)->CallIntMethod(env, webView, getMeasuredHeightMethod);
//int scrollY = webView.getScrollY();
jmethodID getScrollYMethod = (*env)->GetMethodID(env, viewClass, "getScrollY", "()I");
int scrollY = (*env)->CallIntMethod(env, webView, getScrollYMethod);
//int webViewHeight = webView.getHeight();
jmethodID getHeightMethod = (*env)->GetMethodID(env, viewClass, "getHeight", "()I");
int webViewHeight = (*env)->CallIntMethod(env, webView, getHeightMethod);
//int webViewWidth = webView.getWidth();
jmethodID getWidthMethod = (*env)->GetMethodID(env, viewClass, "getWidth", "()I");
int webViewWidth = (*env)->CallIntMethod(env, webView, getWidthMethod);
LOGI("measuredHeight = %d, scroll=%d, webViewWidth=%d, webViewHeight=%d", measuredHeight,
scrollY, webViewWidth, webViewHeight);
float height2;
if (height > measuredHeight - webViewHeight) {
height2 = measuredHeight - webViewHeight;
} else {
height2 = height;
}
float height3 = scrollY - height2;
if (fabs(height3) < 10) {
// 滑动距离小于10,不滑动直接返回
//listener.onScrollEnd();
jclass scrollListenerClass = (*env)->GetObjectClass(env, listener);
jmethodID onScrollEndMethod = (*env)->GetMethodID(env, scrollListenerClass, "onScrollEnd",
"()V");
(*env)->CallVoidMethod(env, listener, onScrollEndMethod);
return;
}
float x1 = webViewWidth * (0.4f + random_float(0.0f, 0.3f));
float x3 = random_float(0.0f, 50.0f);
float x2 = ((int) x3 % 2 == 0) ? x1 + x3 : x1 - x3;
if (fabs(height3) < webViewHeight * 0.8f) {
// 一次能滑到目标位置
float i = random_float(0.0f, webViewHeight - fabs(height3));
float y1 = height3 > 0 ? i : webViewHeight - i;
float y2 = y1 + height3;
scroll_screen(env, 1, webView, x1, x2, y1, y2, listener);
} else {
// 一次不能滑到目标位置,滑动随机距离
float y1 = webViewHeight * (0.1f + random_float(0.0f, 0.3f));
float y2 = webViewHeight * (0.6f + random_float(0.0f, 0.3f));
if (height3 < 0) {
float temp = y1;
y1 = y2;
y2 = temp;
}
jobject yListener = create_y_scroll_listener(env, webView, height2, listener, time);
if (yListener != NULL) {
scroll_screen(env, 2, webView, x1, x2, y1, y2, yListener);
(*env)->DeleteLocalRef(env, yListener);
}
}
}
// JNI实现 - 随机滑动
JNIEXPORT void JNICALL
Java_com_coolook_view_ScrollHelper_nativeYScrollRandom(JNIEnv *env, jclass clazz,
jobject webView,
jboolean isUp,
jint slideNum,
jint position1,
jint position2,
jint distance1,
jint distance2,
jobject listener) {
// 获取WebView的高度和宽度
jclass viewClass = (*env)->GetObjectClass(env, webView);
//webView.measure(0, 0);
jmethodID measureMethod = (*env)->GetMethodID(env, viewClass, "measure", "(II)V");
(*env)->CallVoidMethod(env, webView, measureMethod, 0, 0);
//int measuredHeight = webView.getMeasuredHeight();
jmethodID getMeasuredHeightMethod = (*env)->GetMethodID(env, viewClass, "getMeasuredHeight",
"()I");
int measuredHeight = (*env)->CallIntMethod(env, webView, getMeasuredHeightMethod);
//int scrollY = webView.getScrollY()
jmethodID getScrollYMethod = (*env)->GetMethodID(env, viewClass, "getScrollY", "()I");
int scrollY = (*env)->CallIntMethod(env, webView, getScrollYMethod);
//int webViewHeight = webView.getHeight();
jmethodID getHeightMethod = (*env)->GetMethodID(env, viewClass, "getHeight", "()I");
int webViewHeight = (*env)->CallIntMethod(env, webView, getHeightMethod);
//int webViewWidth = webView.getWidth();
jmethodID getWidthMethod = (*env)->GetMethodID(env, viewClass, "getWidth", "()I");
int webViewWidth = (*env)->CallIntMethod(env, webView, getWidthMethod);
float x1 = webViewWidth * (0.4f + random_float(0.0f, 0.3f));
float x3 = random_float(0.0f, 50.0f);
float x2 = ((int) x3 % 2 == 0) ? x1 + x3 : x1 - x3;
float y1 = webViewHeight * (position1 + random_float(0.0f, position2 - position1)) / 100.0f;
float y2 = webViewHeight * (distance1 + random_float(0.0f, distance2 - distance1)) / 100.0f;
y2 = isUp ? y1 + y2 : y1 - y2;
y2 = y2 < 0 ? 0 : (y2 > webViewHeight ? webViewHeight : y2);
if (slideNum < 1) {
jclass scrollListenerClass = (*env)->GetObjectClass(env, listener);
jmethodID onScrollEndMethod = (*env)->GetMethodID(env, scrollListenerClass, "onScrollEnd",
"()V");
(*env)->CallVoidMethod(env, listener, onScrollEndMethod);
} else if (slideNum == 1) {
scroll_screen(env, 1, webView, x1, x2, y1, y2, listener);
} else {
jobject randomListener = create_random_scroll_listener(env, webView, isUp, slideNum,
position1, position2, distance1,
distance2, listener);
if (randomListener != NULL) {
scroll_screen(env, 2, webView, x1, x2, y1, y2, randomListener);
(*env)->DeleteLocalRef(env, randomListener);
}
}
}
// JNI实现 - 处理点击事件
JNIEXPORT void JNICALL
Java_com_coolook_view_ScrollHelper_nativeHandlerClickEvent(JNIEnv *env,
jclass clazz,
jobject webView,
jfloat x,
jfloat y) {
//long downTime = SystemClock.uptimeMillis();
//long eventTime = SystemClock.uptimeMillis();
jlong downTime = get_current_time_millis(env);
jlong eventTime = get_current_time_millis(env);
// 创建PointerProperties
jobject pointerProps = create_pointer_properties(env, 0, 1); // 1 = MotionEvent.TOOL_TYPE_FINGER
if (pointerProps == NULL) return;
jobjectArray propsArray = (*env)->NewObjectArray(env, 1, (*env)->FindClass(env,
"android/view/MotionEvent$PointerProperties"),
NULL);
(*env)->SetObjectArrayElement(env, propsArray, 0, pointerProps);
// 创建PointerCoords
float pressure = 0.5f + random_float(0.0f, 0.5f) + random_float(0.0f, 0.5f);
float touchMinor = 80.0f + random_float(0.0f, 30.0f);
float toolMinor = touchMinor;
float touchMajor = touchMinor + random_float(0.0f, 30.0f);
float toolMajor = touchMajor;
float orientation = 0.3f + random_float(0.0f, 0.5f);
jobject pointerCoords = create_pointer_coords(env, x, y, pressure, touchMinor, toolMinor,
touchMajor, toolMajor, orientation, 0.0f);
if (pointerCoords == NULL) return;
jobjectArray coordsArray = (*env)->NewObjectArray(env, 1, (*env)->FindClass(env,
"android/view/MotionEvent$PointerCoords"),
NULL);
(*env)->SetObjectArrayElement(env, coordsArray, 0, pointerCoords);
// 发送DOWN事件
dispatch_touch_event(env, webView, downTime, downTime, 0, propsArray, coordsArray, 1234567890);
// 发送UP事件
jlong upTime = downTime + 100 + (jlong) (random_float(0.0f, 200.0f));
dispatch_touch_event(env, webView, downTime, upTime, 1, propsArray, coordsArray, 1234567890);
// 释放资源
(*env)->DeleteLocalRef(env, pointerProps);
(*env)->DeleteLocalRef(env, propsArray);
(*env)->DeleteLocalRef(env, pointerCoords);
(*env)->DeleteLocalRef(env, coordsArray);
}
\ No newline at end of file
package com.coolook.view;
import android.util.Log;
import android.webkit.WebView;
import com.coolook.view.listener.ScrollListener;
/**
* @author: WangYinuo
* @create: 2025-06-19:PM4:38
*/
public class ScrollHelper {
static {
System.loadLibrary("scrollHelper");
}
private static native void nativeYScrollTo(WebView webView, float height, ScrollListener listener, long time);
private static native void nativeYScrollRandom(WebView webView, boolean isUp, int slideNum,
int position1, int position2,
int distance1, int distance2,
ScrollListener listener);
private static native void nativeHandlerClickEvent(WebView webView, float x, float y);
// 修改原有的Java方法,调用对应的native方法
public static void yScrollTo(WebView webView, float height, ScrollListener listener, long time) {
nativeYScrollTo(webView, height, listener, time);
}
public static void yScrollRandom(WebView webView,
boolean isUp,
int slideNum,
int position1,
int position2,
int distance1,
int distance2,
ScrollListener listener) {
nativeYScrollRandom(webView, isUp, slideNum, position1, position2, distance1, distance2, listener);
}
public static void handlerClickEvent(WebView webView, float x, float y) {
nativeHandlerClickEvent(webView, x, y);
}
private static int getDeviceId() {
return 1234567890;
}
}
package com.coolook.view.listener;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
/**
* @author: WangYinuo
* @create: 2025-06-23:PM4:08
*/
public class MyAnimatorListenerAdapter extends AnimatorListenerAdapter {
private float y2;
private float x2;
private Object pointPropsArr;
private Object pointerCoordsArr;
private Object view;
private ScrollListener listerner;
public MyAnimatorListenerAdapter(float y2, float x2, Object pointPropsArr, Object pointerCoordsArr, Object view, ScrollListener listerner) {
this.y2 = y2;
this.x2 = x2;
this.pointPropsArr = pointPropsArr;
this.pointerCoordsArr = pointerCoordsArr;
this.view = view;
this.listerner = listerner;
}
@Override
public native void onAnimationEnd(Animator animation);
}
package com.coolook.view.listener;
import android.animation.ValueAnimator;
/**
* @author: WangYinuo
* @create: 2025-06-23:PM3:44
*/
public class MyAnimatorUpdateListener implements ValueAnimator.AnimatorUpdateListener {
private float y1;
private float y2;
private float x1;
private float x2;
private Object pointPropsArr;
private Object pointerCoordsArr;
private Object view;
public MyAnimatorUpdateListener(float y1,float y2, float x1,float x2, Object pointPropsArr, Object pointerCoordsArr, Object view) {
this.x1 = x1;
this.x2 = x2;
this.y1 = y1;
this.y2 = y2;
this.pointPropsArr = pointPropsArr;
this.pointerCoordsArr = pointerCoordsArr;
this.view = view;
}
@Override
public native void onAnimationUpdate(ValueAnimator animation);
}
package com.coolook.view.listener;
/**
* @author: WangYinuo
* @create: 2025-06-20:PM2:05
*/
public interface ScrollListener {
void onScrollEnd();
void onScrollError(String message);
}
package com.coolook.view.listener.impl;
import android.webkit.WebView;
import com.coolook.view.ScrollHelper;
import com.coolook.view.listener.ScrollListener;
import com.coolook.view.utils.ApplicationUtil;
import java.util.Random;
/**
* @author: WangYinuo
* @create: 2025-06-20:PM2:56
*/
public class RandomScrollListener implements ScrollListener {
private final WebView webView;
private final boolean isUp;
private final int slideNum;
private final int position1;
private final int position2;
private final int distance1;
private final int distance2;
private final ScrollListener listener;
public RandomScrollListener(WebView webView, boolean isUp, int slideNum,
int position1, int position2,
int distance1, int distance2,
ScrollListener listener) {
this.webView = webView;
this.isUp = isUp;
this.slideNum = slideNum;
this.position1 = position1;
this.position2 = position2;
this.distance1 = distance1;
this.distance2 = distance2;
this.listener = listener;
}
@Override
public void onScrollEnd() {
ApplicationUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
ScrollHelper.yScrollRandom(webView, isUp, slideNum - 1,
position1, position2,
distance1, distance2, listener);
} catch (Throwable throwable) {
listener.onScrollError(throwable.getMessage());
}
}
}, 1000 + new Random().nextInt(3000));
}
@Override
public void onScrollError(String message) {
listener.onScrollError(message);
}
}
package com.coolook.view.listener.impl;
import android.webkit.WebView;
import com.coolook.view.ScrollHelper;
import com.coolook.view.listener.ScrollListener;
import com.coolook.view.utils.ApplicationUtil;
import java.util.Random;
/**
* @author: WangYinuo
* @create: 2025-06-20:PM2:07
*/
public class YScrollListener implements ScrollListener {
private final WebView webView;
private final float height;
private final ScrollListener listener;
private final long time;
public YScrollListener(WebView webView, float height, ScrollListener listener, long time) {
this.webView = webView;
this.height = height;
this.listener = listener;
this.time = time;
}
@Override
public void onScrollEnd() {
ApplicationUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
if (System.currentTimeMillis() - time > 100 * 1000) {
listener.onScrollError("scroll time out");
return;
}
ScrollHelper.yScrollTo(webView, height, listener, time);
} catch (Throwable throwable) {
listener.onScrollError(throwable.getMessage());
}
}
}, 1000 + new Random().nextInt(3000));
}
@Override
public void onScrollError(String message) {
listener.onScrollError(message);
}
}
\ No newline at end of file
package com.coolook.view.utils;
import android.annotation.SuppressLint;
import android.app.Application;
import android.os.Handler;
import android.os.Looper;
/**
* @author: WangYinuo
* @create: 2025-06-19:PM4:54
*/
public class ApplicationUtil {
@SuppressLint("PrivateApi")
public static Application getApplication() {
try {
return (Application) Class.forName("android.app.ActivityThread").getMethod("currentApplication").invoke(null);
} catch (Exception ignored) {
}
try {
return (Application) Class.forName("android.app.AppGlobals").getMethod("getInitialApplication").invoke(null);
} catch (Exception ignored) {
}
return null;
}
public static void runOnUiThread(Runnable runnable, long delay) {
try {
if (runnable == null) {
return;
}
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(runnable, delay);
} catch (Exception ignored) {
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:startY="49.59793"
android:startX="42.9492"
android:endY="92.4963"
android:endX="85.84757"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
\ No newline at end of file
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.WebViewDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
\ No newline at end of file
<resources>
<string name="app_name">libwebview</string>
</resources>
\ No newline at end of file
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.WebViewDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>
\ No newline at end of file
package com.coolook.view;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}
\ No newline at end of file
/build
\ No newline at end of file
plugins {
alias(libs.plugins.android.application)
}
android {
namespace 'com.airmobyte.sdk.offer'
compileSdk 35
defaultConfig {
applicationId "com.airmobyte.sdk.offer"
minSdk 26
targetSdk 35
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk {
//noinspection ChromeOsAbiSupport
abiFilters 'arm64-v8a', 'armeabi-v7a'//, 'arm64-v8a', 'x86'//, 'x86_64' // 确保包含设备所用的 ABI
}
externalNativeBuild {
cmake {
cppFlags "-std=c++17 -frtti -fexceptions" // 确保支持 RTTI 和异常
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.22.1'
}
}
}
dependencies {
implementation libs.appcompat
implementation libs.material
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
\ No newline at end of file
package com.airmobyte.sdk.offer;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.airmobyte.sdk.offer", appContext.getPackageName());
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.WebViewDemo" />
</manifest>
\ No newline at end of file
cmake_minimum_required(VERSION 3.22.1)
add_subdirectory(scrollHelper)
add_subdirectory(webViewClient)
cmake_minimum_required(VERSION 3.22.1)
project("scrollHelper")
add_library( # 库名称
scrollHelper
# 库类型:SHARED 表示动态库
SHARED
# 源文件路径(相对路径)
scrollHelper.c
common.c
listener.c
)
# 查找和链接 Android 特有的库
find_library( # 库变量名
log-lib
# 要查找的系统库名称
log )
find_library(android-lib android)
# 链接目标库需要的库文件
target_link_libraries( # 目标库名称
scrollHelper
# 链接 Android 核心库
${log-lib}
${android-lib} )
#include "common.h"
//
// Created by 王一诺 on 2025/6/23.
//
// 初始化随机数种子
void init_random() {
static int initialized = 0;
if (!initialized) {
srand(time(NULL));
initialized = 1;
}
}
// 生成随机浮点数
float random_float(float min, float max) {
init_random();
return min + (max - min) * ((float) rand() / RAND_MAX);
}
// 处理触摸事件
void dispatch_touch_event(JNIEnv *env,
jobject view,
jlong downTime,
jlong eventTime,
jint action,
jobjectArray pointerProperties,
jobjectArray pointerCoords,
jint deviceId) {
//MotionEvent motionEventDown = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 1, pointPropsArr, pointerCoordsArr, 0, 0, 0, 0, deviceId, 0, 0x1001, 0);
jclass motionEventClass = (*env)->FindClass(env, "android/view/MotionEvent");
jmethodID obtainMethod = (*env)->GetStaticMethodID(env,
motionEventClass,
"obtain",
"(JJII[Landroid/view/MotionEvent$PointerProperties;[Landroid/view/MotionEvent$PointerCoords;IIFFIIII)Landroid/view/MotionEvent;");
if (obtainMethod == NULL) {
LOGI("Failed to find MotionEvent.obtain() method");
return;
}
jobject motionEvent = (*env)->CallStaticObjectMethod(env,
motionEventClass,
obtainMethod,
downTime,
eventTime,
action,
1,
pointerProperties,
pointerCoords,
0,
0,
0.0f,
0.0f,
deviceId,
0,
0x1001,
0);
if (motionEvent == NULL) {
LOGI("Failed to create MotionEvent object");
return;
}
//view.dispatchTouchEvent(motionEventDown);
jclass viewClass = (*env)->GetObjectClass(env, view);
jmethodID dispatchMethod = (*env)->GetMethodID(env, viewClass, "dispatchTouchEvent",
"(Landroid/view/MotionEvent;)Z");
if (dispatchMethod == NULL) {
LOGI("Failed to create viewClass object");
return;
}
(*env)->CallBooleanMethod(env, view, dispatchMethod, motionEvent);
//motionEventDown.recycle();
jmethodID recycleMethod = (*env)->GetMethodID(env, motionEventClass, "recycle", "()V");
(*env)->CallVoidMethod(env, motionEvent, recycleMethod);
(*env)->DeleteLocalRef(env, motionEvent);
}
// 创建PointerProperties对象
jobject create_pointer_properties(JNIEnv *env, jint id, jint toolType) {
jclass pointerPropsClass = (*env)->FindClass(env, "android/view/MotionEvent$PointerProperties");
jmethodID constructor = (*env)->GetMethodID(env, pointerPropsClass, "<init>", "()V");
if (constructor == NULL) {
return NULL;
}
jobject pointerProps = (*env)->NewObject(env, pointerPropsClass, constructor);
if (pointerProps == NULL) {
return NULL;
}
jfieldID idField = (*env)->GetFieldID(env, pointerPropsClass, "id", "I");
jfieldID toolTypeField = (*env)->GetFieldID(env, pointerPropsClass, "toolType", "I");
(*env)->SetIntField(env, pointerProps, idField, id);
(*env)->SetIntField(env, pointerProps, toolTypeField, toolType);
return pointerProps;
}
// 创建PointerCoords对象
jobject create_pointer_coords(JNIEnv *env, float x, float y, float pressure,
float touchMinor, float toolMinor,
float touchMajor, float toolMajor,
float orientation, float size) {
jclass pointerCoordsClass = (*env)->FindClass(env, "android/view/MotionEvent$PointerCoords");
jmethodID constructor = (*env)->GetMethodID(env, pointerCoordsClass, "<init>", "()V");
if (constructor == NULL) {
return NULL;
}
jobject pointerCoords = (*env)->NewObject(env, pointerCoordsClass, constructor);
if (pointerCoords == NULL) {
return NULL;
}
jfieldID xField = (*env)->GetFieldID(env, pointerCoordsClass, "x", "F");
jfieldID yField = (*env)->GetFieldID(env, pointerCoordsClass, "y", "F");
jfieldID pressureField = (*env)->GetFieldID(env, pointerCoordsClass, "pressure", "F");
jfieldID touchMinorField = (*env)->GetFieldID(env, pointerCoordsClass, "touchMinor", "F");
jfieldID toolMinorField = (*env)->GetFieldID(env, pointerCoordsClass, "toolMinor", "F");
jfieldID touchMajorField = (*env)->GetFieldID(env, pointerCoordsClass, "touchMajor", "F");
jfieldID toolMajorField = (*env)->GetFieldID(env, pointerCoordsClass, "toolMajor", "F");
jfieldID orientationField = (*env)->GetFieldID(env, pointerCoordsClass, "orientation", "F");
jfieldID sizeField = (*env)->GetFieldID(env, pointerCoordsClass, "size", "F");
(*env)->SetFloatField(env, pointerCoords, xField, x);
(*env)->SetFloatField(env, pointerCoords, yField, y);
(*env)->SetFloatField(env, pointerCoords, pressureField, pressure);
(*env)->SetFloatField(env, pointerCoords, touchMinorField, touchMinor);
(*env)->SetFloatField(env, pointerCoords, toolMinorField, toolMinor);
(*env)->SetFloatField(env, pointerCoords, touchMajorField, touchMajor);
(*env)->SetFloatField(env, pointerCoords, toolMajorField, toolMajor);
(*env)->SetFloatField(env, pointerCoords, orientationField, orientation);
(*env)->SetFloatField(env, pointerCoords, sizeField, size);
return pointerCoords;
}
jlong get_current_time_millis(JNIEnv *env) {
jclass systemClockClass = (*env)->FindClass(env, "android/os/SystemClock");
if (systemClockClass == NULL) {
return 0;
}
jmethodID uptimeMillisMethod = (*env)->GetStaticMethodID(env, systemClockClass, "uptimeMillis",
"()J");
if (uptimeMillisMethod == NULL) {
return 0;
}
return (*env)->CallStaticLongMethod(env, systemClockClass, uptimeMillisMethod);
}
// 实现scrollScreen函数
void scroll_screen(JNIEnv *env,
jint type,
jobject view,
jfloat x1,
jfloat x2,
jfloat y1,
jfloat y2,
jobject listener) {
//final long downTime = SystemClock.uptimeMillis();
jlong downTime = (jlong) get_current_time_millis(env);
/*
* MotionEvent.PointerProperties pointProp = new MotionEvent.PointerProperties();
pointProp.id = 0;
pointProp.toolType = MotionEvent.TOOL_TYPE_FINGER;
*/
jobject pointerProps = create_pointer_properties(env, 0, 1); // 1 = MotionEvent.TOOL_TYPE_FINGER
if (pointerProps == NULL) return;
//final MotionEvent.PointerProperties[] pointPropsArr = {pointProp};
jobjectArray propsArray = (*env)->NewObjectArray(env, 1, (*env)->FindClass(env,
"android/view/MotionEvent$PointerProperties"),
NULL);
(*env)->SetObjectArrayElement(env, propsArray, 0, pointerProps);
// 初始化PointerCoords
float x = x1;
float y = y1;
float pressure = 0.5f + random_float(0.0f, 0.5f) + random_float(0.0f, 0.5f);
float touchMinor = 80.0f + random_float(0.0f, 30.0f);
float toolMinor = touchMinor;
float touchMajor = touchMinor + random_float(0.0f, 30.0f);
float toolMajor = touchMajor;
float orientation = 0.3f + random_float(0.0f, 0.5f);
// 创建初始PointerCoords用于DOWN事件
jobject pointerCoords = create_pointer_coords(env,
x,
y,
pressure,
touchMinor,
toolMinor,
touchMajor,
toolMajor,
orientation,
0.0f);
if (pointerCoords == NULL) {
(*env)->DeleteLocalRef(env, pointerProps);
(*env)->DeleteLocalRef(env, propsArray);
return;
}
jobjectArray coordsArray = (*env)->NewObjectArray(env,
1,
(*env)->FindClass(env, "android/view/MotionEvent$PointerCoords"), NULL);
(*env)->SetObjectArrayElement(env, coordsArray, 0, pointerCoords);
// 发送DOWN事件
dispatch_touch_event(env, view, downTime, downTime, 0, propsArray, coordsArray, 1234567890);
// 计算动画持续时间
//ValueAnimator anim = ValueAnimator.ofFloat(y1, y2);
jclass valueAnimatorClass = (*env)->FindClass(env, "android/animation/ValueAnimator");
//public static ValueAnimator ofFloat(float... values)
jmethodID ofFloatMethod = (*env)->GetStaticMethodID(env, valueAnimatorClass, "ofFloat", "([F)Landroid/animation/ValueAnimator;");
jfloatArray floatArray = (*env)->NewFloatArray(env, 2);
(*env)->SetFloatArrayRegion(env, floatArray, 0, 2, (jfloat[]) {y1, y2});
jobject anim = (*env)->CallStaticObjectMethod(env, valueAnimatorClass, ofFloatMethod,
floatArray);
jlong duration;
if (type == 1) {
duration = 500 + (jlong) random_float(0.0f, 500.0f);
} else {
duration = 100 + (jlong) random_float(0.0f, 500.0f);
}
jmethodID setDurationMethod = (*env)->GetMethodID(env, valueAnimatorClass, "setDuration",
"(J)Landroid/animation/ValueAnimator;");
(*env)->CallObjectMethod(env, anim, setDurationMethod, duration);
//anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
jmethodID addUpdateListenerMethod = (*env)->GetMethodID(env,
valueAnimatorClass,
"addUpdateListener",
"(Landroid/animation/ValueAnimator$AnimatorUpdateListener;)V");
(*env)->CallVoidMethod(env, anim, addUpdateListenerMethod,
create_update_listener(env, y1, y2, x1, x2, propsArray, coordsArray,
view));
//anim.addListener(new AnimatorListenerAdapter()
jmethodID addListenerMethod = (*env)->GetMethodID(env,
valueAnimatorClass,
"addListener",
"(Landroid/animation/Animator$AnimatorListener;)V");
(*env)->CallVoidMethod(env, anim, addListenerMethod, create_animator_listener(env, y2, x2, propsArray, coordsArray, view, listener));
//anim.start();
jmethodID startMethod = (*env)->GetMethodID(env, valueAnimatorClass, "start", "()V");
(*env)->CallVoidMethod(env, anim, startMethod);
// 释放资源
(*env)->DeleteLocalRef(env, pointerProps);
(*env)->DeleteLocalRef(env, propsArray);
(*env)->DeleteLocalRef(env, pointerCoords);
(*env)->DeleteLocalRef(env, coordsArray);
}
// 实现YScrollListener的创建和回调
jobject
create_y_scroll_listener(JNIEnv *env, jobject webView, jfloat height, jobject originalListener,
jlong time) {
jclass yScrollListenerClass = (*env)->FindClass(env,
"com/coolook/view/listener/impl/YScrollListener");
if (yScrollListenerClass == NULL) {
return NULL;
}
jmethodID constructor = (*env)->GetMethodID(env,
yScrollListenerClass,
"<init>",
"(Landroid/webkit/WebView;FLcom/coolook/view/listener/ScrollListener;J)V");
if (constructor == NULL) {
return NULL;
}
jobject listener = (*env)->NewObject(env, yScrollListenerClass, constructor, webView, height,
originalListener, time);
return listener;
}
// 实现RandomScrollListener的创建和回调
jobject create_random_scroll_listener(JNIEnv *env, jobject webView, jboolean isUp,
jint slideNum, jint position1, jint position2,
jint distance1, jint distance2,
jobject originalListener) {
jclass randomScrollListenerClass = (*env)->FindClass(env,
"com/coolook/view/listener/impl/RandomScrollListener");
if (randomScrollListenerClass == NULL) {
return NULL;
}
jmethodID constructor = (*env)->GetMethodID(env, randomScrollListenerClass, "<init>",
"(Landroid/webkit/WebView;ZIIIIILcom/coolook/view/listener/ScrollListener;)V");
if (constructor == NULL) {
return NULL;
}
jobject listener = (*env)->NewObject(env, randomScrollListenerClass, constructor,
webView, isUp, slideNum, position1, position2,
distance1, distance2, originalListener);
return listener;
}
jobject create_update_listener(JNIEnv *env, jfloat y1, jfloat y2, jfloat x1,
jfloat x2, jobjectArray propsArray, jobjectArray coordsArray,
jobject view) {
jclass MyAnimatorUpdateListenerClass = (*env)->FindClass(env,
"com/coolook/view/listener/MyAnimatorUpdateListener");
jmethodID constructor = (*env)->GetMethodID(env,
MyAnimatorUpdateListenerClass,
"<init>",
"(FFFFLjava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V");
jobject updateListener = (*env)->NewObject(env, MyAnimatorUpdateListenerClass, constructor, y1,
y2, x1, x2, propsArray, coordsArray, view);
return updateListener;
}
jobject create_animator_listener(JNIEnv *env, jfloat y2, jfloat x2, jobjectArray propsArray,
jobjectArray coordsArray, jobject view, jobject listener) {
jclass MyAnimatorListenerClass = (*env)->FindClass(env,
"com/coolook/view/listener/MyAnimatorListenerAdapter");
jmethodID constructor = (*env)->GetMethodID(env, MyAnimatorListenerClass, "<init>",
"(FFLjava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lcom/coolook/view/listener/ScrollListener;)V");
jobject animatorListener = (*env)->NewObject(env, MyAnimatorListenerClass, constructor, y2, x2,
propsArray, coordsArray, view, listener);
return animatorListener;
}
\ No newline at end of file
//
// Created by 王一诺 on 2025/6/23.
//
#ifndef WEBVIEWDEMO_COMMON_H
#define WEBVIEWDEMO_COMMON_H
#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <android/log.h>
#include <unistd.h>
#define LOG_TAG "ScrollHelper"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
void init_random();
float random_float(float min, float max);
void dispatch_touch_event(JNIEnv *env,
jobject view,
jlong downTime,
jlong eventTime,
jint action,
jobjectArray pointerProperties,
jobjectArray pointerCoords,
jint deviceId);
jobject create_pointer_properties(JNIEnv *env, jint id, jint toolType);
jobject create_pointer_coords(JNIEnv *env,
float x,
float y,
float pressure,
float touchMinor,
float toolMinor,
float touchMajor,
float toolMajor,
float orientation,
float size);
jlong get_current_time_millis(JNIEnv *env);
void scroll_screen(JNIEnv *env,
jint type,
jobject view,
jfloat x1,
jfloat x2,
jfloat y1,
jfloat y2,
jobject listener);
jobject create_y_scroll_listener(JNIEnv *env,
jobject webView,
jfloat height,
jobject originalListener,
jlong time);
jobject create_random_scroll_listener(JNIEnv *env,
jobject webView,
jboolean isUp,
jint slideNum,
jint position1,
jint position2,
jint distance1,
jint distance2,
jobject originalListener);
jobject create_update_listener(JNIEnv *env,
jfloat y1,
jfloat y2,
jfloat x1,
jfloat x2,
jobjectArray propsArray,
jobjectArray coordsArray,
jobject view);
jobject create_animator_listener(JNIEnv *env,
jfloat y2,
jfloat x2,
jobjectArray propsArray,
jobjectArray coordsArray,
jobject view,
jobject listener);
#endif //WEBVIEWDEMO_COMMON_H
#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <android/log.h>
#include <unistd.h>
#include "common.h"
//
// Created by 王一诺 on 2025/6/23.
//
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_listener_MyAnimatorUpdateListener_onAnimationUpdate(JNIEnv *env,
jobject thiz, // `AnimatorUpdateListener` 实例
jobject animation // `ValueAnimator` 实例
) {
// 1. 获取 ValueAnimator 类的 getAnimatedValue() 方法
jclass animatorClass = (*env)->GetObjectClass(env, animation);
jmethodID getAnimatedValueMethod = (*env)->GetMethodID(env, animatorClass, "getAnimatedValue",
"()Ljava/lang/Object;");
// 2. 调用 getAnimatedValue()
jobject animatedValue = (*env)->CallObjectMethod(env, animation, getAnimatedValueMethod);
// 3. 检查返回值的类型(Float 或 Integer)并转换为 float
jfloat y = 0.0f;
if (animatedValue != NULL) {
if ((*env)->IsInstanceOf(env, animatedValue, (*env)->FindClass(env, "java/lang/Float"))) {
jclass floatClass = (*env)->GetObjectClass(env, animatedValue);
jmethodID floatValueMethod =(*env)->GetMethodID(env, floatClass, "floatValue", "()F");
// 如果返回值是 Float
jfloat floatValue = (*env)->CallFloatMethod(env, animatedValue,floatValueMethod);
y = floatValue;
} else if ((*env)->IsInstanceOf(env, animatedValue,
(*env)->FindClass(env, "java/lang/Integer"))) {
jclass intClass =(*env)->GetObjectClass( env, animatedValue);
jmethodID intValueMethod =(*env)->GetMethodID(env, intClass,"intValue", "()I");
// 如果返回值是 Integer
jint intValue = (*env)->CallIntMethod(env, animatedValue, intValueMethod);
y = (jfloat) intValue; // 转为 float
} else {
return;
}
}
jclass listenerClass = (*env)->GetObjectClass(env, thiz);
jfieldID fieldId_y1 = (*env)->GetFieldID(env, listenerClass, "y1", "F");
jfloat y1 = (*env)->GetFloatField(env, thiz, fieldId_y1);
jfieldID fieldId_y2 = (*env)->GetFieldID(env, listenerClass, "y2", "F");
jfloat y2 = (*env)->GetFloatField(env, thiz, fieldId_y2);
jfieldID fieldId_x1 = (*env)->GetFieldID(env, listenerClass, "x1", "F");
jfloat x1 = (*env)->GetFloatField(env, thiz, fieldId_x1);
jfieldID fieldId_x2 = (*env)->GetFieldID(env, listenerClass, "x2", "F");
jfloat x2 = (*env)->GetFloatField(env, thiz, fieldId_x2);
//float x = (y - y1) / (y2 - y1) * (x2 - x1) + x1;
jfloat x = (y - y1) / (y2 - y1) * (x2 - x1) + x1;
jfieldID pointPropsArrId = (*env)->GetFieldID(env, listenerClass, "pointPropsArr",
"Ljava/lang/Object;");
jobject pointPropsArr = (*env)->GetObjectField(env, thiz, pointPropsArrId);
jfieldID pointerCoordsArrId = (*env)->GetFieldID(env, listenerClass, "pointerCoordsArr",
"Ljava/lang/Object;");
jobject pointerCoordsArr = (*env)->GetObjectField(env, thiz, pointerCoordsArrId);
jclass objectArrayClass = (*env)->FindClass(env, "[Ljava/lang/Object;");
if (!(*env)->IsInstanceOf(env, pointerCoordsArr, objectArrayClass)) {
return;
}
// 5. 获取第0个元素 (PointerCoords)
jobject pointerCoords = (*env)->GetObjectArrayElement(env, (jobjectArray) pointerCoordsArr, 0);
if (pointerCoords == NULL) {
return;
}
jclass pointerCoordsClass = (*env)->FindClass(env, "android/view/MotionEvent$PointerCoords");
if (pointerCoordsClass == NULL) {
return;
}
jfieldID xField = (*env)->GetFieldID(env, pointerCoordsClass, "x", "F");
jfieldID yField = (*env)->GetFieldID(env, pointerCoordsClass, "y", "F");
if (xField == NULL || yField == NULL) {
return;
}
// 7. 修改 x 和 y
(*env)->SetFloatField(env, pointerCoords, xField, x);
(*env)->SetFloatField(env, pointerCoords, yField, y);
jfieldID viewId = (*env)->GetFieldID(env, listenerClass, "view", "Ljava/lang/Object;");
jobject view = (*env)->GetObjectField(env, thiz, viewId);
//MotionEvent.ACTION_MOVE
dispatch_touch_event(env, view, get_current_time_millis(env), get_current_time_millis(env), 2,
pointPropsArr, pointerCoordsArr, 1234567890);
}
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_listener_MyAnimatorListenerAdapter_onAnimationEnd(JNIEnv *env, jobject thiz,
jobject animation) {
jclass listenerClass = (*env)->GetObjectClass(env, thiz);
jfieldID fieldId_y2 = (*env)->GetFieldID(env, listenerClass, "y2", "F");
jfloat y2 = (*env)->GetFloatField(env, thiz, fieldId_y2);
jfieldID fieldId_x2 = (*env)->GetFieldID(env, listenerClass, "x2", "F");
jfloat x2 = (*env)->GetFloatField(env, thiz, fieldId_x2);
jfieldID pointPropsArrId = (*env)->GetFieldID(env, listenerClass, "pointPropsArr",
"Ljava/lang/Object;");
jobject pointPropsArr = (*env)->GetObjectField(env, thiz, pointPropsArrId);
jfieldID pointerCoordsArrId = (*env)->GetFieldID(env, listenerClass, "pointerCoordsArr",
"Ljava/lang/Object;");
jobject pointerCoordsArr = (*env)->GetObjectField(env, thiz, pointerCoordsArrId);
jclass objectArrayClass = (*env)->FindClass(env, "[Ljava/lang/Object;");
if (!(*env)->IsInstanceOf(env, pointerCoordsArr, objectArrayClass)) {
return;
}
// 5. 获取第0个元素 (PointerCoords)
jobject pointerCoords = (*env)->GetObjectArrayElement(env, (jobjectArray) pointerCoordsArr, 0);
if (pointerCoords == NULL) {
return;
}
jclass pointerCoordsClass = (*env)->FindClass(env, "android/view/MotionEvent$PointerCoords");
if (pointerCoordsClass == NULL) {
return;
}
jfieldID xField = (*env)->GetFieldID(env, pointerCoordsClass, "x", "F");
jfieldID yField = (*env)->GetFieldID(env, pointerCoordsClass, "y", "F");
if (xField == NULL || yField == NULL) {
return;
}
// 7. 修改 x 和 y
(*env)->SetFloatField(env, pointerCoords, xField, x2);
(*env)->SetFloatField(env, pointerCoords, yField, y2);
jfieldID viewId = (*env)->GetFieldID(env, listenerClass, "view", "Ljava/lang/Object;");
jobject view = (*env)->GetObjectField(env, thiz, viewId);
//MotionEvent.ACTION_UP
dispatch_touch_event(env, view, get_current_time_millis(env), get_current_time_millis(env), 1,
pointPropsArr, pointerCoordsArr, 1234567890);
jfieldID listenerId = (*env)->GetFieldID(env, listenerClass, "listerner",
"Lcom/airmobyte/sdk/offer/view/listener/ScrollListener;");
jobject listenerObj = (*env)->GetObjectField(env, thiz, listenerId);
jclass scrollListenerClass = (*env)->FindClass(env, "com/coolook/view/listener/ScrollListener");
jmethodID onScrollEnd = (*env)->GetMethodID(env, scrollListenerClass, "onScrollEnd", "()V");
(*env)->CallVoidMethod(env, listenerObj, onScrollEnd);
}
\ No newline at end of file
#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <android/log.h>
#include <unistd.h>
#include "common.h"
// JNI实现 - 竖向滑动
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_ScrollHelper_nativeYScrollTo(JNIEnv *env,
jclass clazz,
jobject webView,
jfloat height,
jobject listener,
jlong time) {
// 获取WebView的高度和宽度
jclass viewClass = (*env)->GetObjectClass(env, webView);
//webView.measure(0, 0);
jmethodID measureMethod = (*env)->GetMethodID(env, viewClass, "measure", "(II)V");
(*env)->CallVoidMethod(env, webView, measureMethod, 0, 0);
//int measuredHeight = webView.getMeasuredHeight();
jmethodID getMeasuredHeightMethod = (*env)->GetMethodID(env, viewClass, "getMeasuredHeight",
"()I");
int measuredHeight = (*env)->CallIntMethod(env, webView, getMeasuredHeightMethod);
//int scrollY = webView.getScrollY();
jmethodID getScrollYMethod = (*env)->GetMethodID(env, viewClass, "getScrollY", "()I");
int scrollY = (*env)->CallIntMethod(env, webView, getScrollYMethod);
//int webViewHeight = webView.getHeight();
jmethodID getHeightMethod = (*env)->GetMethodID(env, viewClass, "getHeight", "()I");
int webViewHeight = (*env)->CallIntMethod(env, webView, getHeightMethod);
//int webViewWidth = webView.getWidth();
jmethodID getWidthMethod = (*env)->GetMethodID(env, viewClass, "getWidth", "()I");
int webViewWidth = (*env)->CallIntMethod(env, webView, getWidthMethod);
LOGI("measuredHeight = %d, scroll=%d, webViewWidth=%d, webViewHeight=%d", measuredHeight,
scrollY, webViewWidth, webViewHeight);
float height2;
if (height > measuredHeight - webViewHeight) {
height2 = measuredHeight - webViewHeight;
} else {
height2 = height;
}
float height3 = scrollY - height2;
if (fabs(height3) < 10) {
// 滑动距离小于10,不滑动直接返回
//listener.onScrollEnd();
jclass scrollListenerClass = (*env)->GetObjectClass(env, listener);
jmethodID onScrollEndMethod = (*env)->GetMethodID(env, scrollListenerClass, "onScrollEnd",
"()V");
(*env)->CallVoidMethod(env, listener, onScrollEndMethod);
return;
}
float x1 = webViewWidth * (0.4f + random_float(0.0f, 0.3f));
float x3 = random_float(0.0f, 50.0f);
float x2 = ((int) x3 % 2 == 0) ? x1 + x3 : x1 - x3;
if (fabs(height3) < webViewHeight * 0.8f) {
// 一次能滑到目标位置
float i = random_float(0.0f, webViewHeight - fabs(height3));
float y1 = height3 > 0 ? i : webViewHeight - i;
float y2 = y1 + height3;
scroll_screen(env, 1, webView, x1, x2, y1, y2, listener);
} else {
// 一次不能滑到目标位置,滑动随机距离
float y1 = webViewHeight * (0.1f + random_float(0.0f, 0.3f));
float y2 = webViewHeight * (0.6f + random_float(0.0f, 0.3f));
if (height3 < 0) {
float temp = y1;
y1 = y2;
y2 = temp;
}
jobject yListener = create_y_scroll_listener(env, webView, height2, listener, time);
if (yListener != NULL) {
scroll_screen(env, 2, webView, x1, x2, y1, y2, yListener);
(*env)->DeleteLocalRef(env, yListener);
}
}
}
// JNI实现 - 随机滑动
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_ScrollHelper_nativeYScrollRandom(JNIEnv *env, jclass clazz,
jobject webView,
jboolean isUp,
jint slideNum,
jint position1,
jint position2,
jint distance1,
jint distance2,
jobject listener) {
// 获取WebView的高度和宽度
jclass viewClass = (*env)->GetObjectClass(env, webView);
//webView.measure(0, 0);
jmethodID measureMethod = (*env)->GetMethodID(env, viewClass, "measure", "(II)V");
(*env)->CallVoidMethod(env, webView, measureMethod, 0, 0);
//int measuredHeight = webView.getMeasuredHeight();
jmethodID getMeasuredHeightMethod = (*env)->GetMethodID(env, viewClass, "getMeasuredHeight",
"()I");
int measuredHeight = (*env)->CallIntMethod(env, webView, getMeasuredHeightMethod);
//int scrollY = webView.getScrollY()
jmethodID getScrollYMethod = (*env)->GetMethodID(env, viewClass, "getScrollY", "()I");
int scrollY = (*env)->CallIntMethod(env, webView, getScrollYMethod);
//int webViewHeight = webView.getHeight();
jmethodID getHeightMethod = (*env)->GetMethodID(env, viewClass, "getHeight", "()I");
int webViewHeight = (*env)->CallIntMethod(env, webView, getHeightMethod);
//int webViewWidth = webView.getWidth();
jmethodID getWidthMethod = (*env)->GetMethodID(env, viewClass, "getWidth", "()I");
int webViewWidth = (*env)->CallIntMethod(env, webView, getWidthMethod);
float x1 = webViewWidth * (0.4f + random_float(0.0f, 0.3f));
float x3 = random_float(0.0f, 50.0f);
float x2 = ((int) x3 % 2 == 0) ? x1 + x3 : x1 - x3;
float y1 = webViewHeight * (position1 + random_float(0.0f, position2 - position1)) / 100.0f;
float y2 = webViewHeight * (distance1 + random_float(0.0f, distance2 - distance1)) / 100.0f;
y2 = isUp ? y1 + y2 : y1 - y2;
y2 = y2 < 0 ? 0 : (y2 > webViewHeight ? webViewHeight : y2);
if (slideNum < 1) {
jclass scrollListenerClass = (*env)->GetObjectClass(env, listener);
jmethodID onScrollEndMethod = (*env)->GetMethodID(env, scrollListenerClass, "onScrollEnd",
"()V");
(*env)->CallVoidMethod(env, listener, onScrollEndMethod);
} else if (slideNum == 1) {
scroll_screen(env, 1, webView, x1, x2, y1, y2, listener);
} else {
jobject randomListener = create_random_scroll_listener(env, webView, isUp, slideNum,
position1, position2, distance1,
distance2, listener);
if (randomListener != NULL) {
scroll_screen(env, 2, webView, x1, x2, y1, y2, randomListener);
(*env)->DeleteLocalRef(env, randomListener);
}
}
}
// JNI实现 - 处理点击事件
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_ScrollHelper_nativeHandlerClickEvent(JNIEnv *env,
jclass clazz,
jobject webView,
jfloat x,
jfloat y) {
//long downTime = SystemClock.uptimeMillis();
//long eventTime = SystemClock.uptimeMillis();
jlong downTime = get_current_time_millis(env);
jlong eventTime = get_current_time_millis(env);
// 创建PointerProperties
jobject pointerProps = create_pointer_properties(env, 0, 1); // 1 = MotionEvent.TOOL_TYPE_FINGER
if (pointerProps == NULL) return;
jobjectArray propsArray = (*env)->NewObjectArray(env, 1, (*env)->FindClass(env,
"android/view/MotionEvent$PointerProperties"),
NULL);
(*env)->SetObjectArrayElement(env, propsArray, 0, pointerProps);
// 创建PointerCoords
float pressure = 0.5f + random_float(0.0f, 0.5f) + random_float(0.0f, 0.5f);
float touchMinor = 80.0f + random_float(0.0f, 30.0f);
float toolMinor = touchMinor;
float touchMajor = touchMinor + random_float(0.0f, 30.0f);
float toolMajor = touchMajor;
float orientation = 0.3f + random_float(0.0f, 0.5f);
jobject pointerCoords = create_pointer_coords(env, x, y, pressure, touchMinor, toolMinor,
touchMajor, toolMajor, orientation, 0.0f);
if (pointerCoords == NULL) return;
jobjectArray coordsArray = (*env)->NewObjectArray(env, 1, (*env)->FindClass(env,
"android/view/MotionEvent$PointerCoords"),
NULL);
(*env)->SetObjectArrayElement(env, coordsArray, 0, pointerCoords);
// 发送DOWN事件
dispatch_touch_event(env, webView, downTime, downTime, 0, propsArray, coordsArray, 1234567890);
// 发送UP事件
jlong upTime = downTime + 100 + (jlong) (random_float(0.0f, 200.0f));
dispatch_touch_event(env, webView, downTime, upTime, 1, propsArray, coordsArray, 1234567890);
// 释放资源
(*env)->DeleteLocalRef(env, pointerProps);
(*env)->DeleteLocalRef(env, propsArray);
(*env)->DeleteLocalRef(env, pointerCoords);
(*env)->DeleteLocalRef(env, coordsArray);
}
\ No newline at end of file
cmake_minimum_required(VERSION 3.22.1)
project("webViewClient")
add_library( # 库名称
webViewClient
# 库类型:SHARED 表示动态库
SHARED
# 源文件路径(相对路径)
webViewClient.c
listener.c
runnable.c
common.c
)
# 查找和链接 Android 特有的库
find_library( # 库变量名
log-lib
# 要查找的系统库名称
log )
find_library(android-lib android)
# 链接目标库需要的库文件
target_link_libraries( # 目标库名称
webViewClient
# 链接 Android 核心库
${log-lib}
${android-lib} )
//
// Created by 王一诺 on 2025/7/2.
//
#include "common.h"
jlong get_current_time_millis(JNIEnv *env) {
jclass systemClockClass = (*env)->FindClass(env, "android/os/SystemClock");
if (systemClockClass == NULL) {
return 0;
}
jmethodID uptimeMillisMethod = (*env)->GetStaticMethodID(env, systemClockClass, "uptimeMillis",
"()J");
if (uptimeMillisMethod == NULL) {
return 0;
}
return (*env)->CallStaticLongMethod(env, systemClockClass, uptimeMillisMethod);
}
void run_on_ui_thread(JNIEnv *env, jobject runnable, jlong delay) {
/*
* public static void runOnUiThread(Runnable runnable, long delay) {
if (runnable == null) {
return;
}
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(runnable, delay);
}
*/
if (runnable == NULL) {
return;
}
jclass handlerClass = (*env)->FindClass(env, "android/os/Handler");
if (handlerClass == NULL) {
return;
}
jmethodID handlerConstructor = (*env)->GetMethodID(env, handlerClass, "<init>",
"(Landroid/os/Looper;)V");
jclass looperClass = (*env)->FindClass(env, "android/os/Looper");
if (looperClass == NULL) {
return;
}
jmethodID getMainLooperMethod = (*env)->GetStaticMethodID(env, looperClass, "getMainLooper",
"()Landroid/os/Looper;");
jobject handler = (*env)->NewObject(env, handlerClass, handlerConstructor,
(*env)->CallStaticObjectMethod(env, looperClass,
getMainLooperMethod));
jmethodID postDelayedMethod = (*env)->GetMethodID(env, handlerClass, "postDelayed",
"(Ljava/lang/Runnable;J)Z");
(*env)->CallBooleanMethod(env, handler, postDelayedMethod, runnable, delay);
}
void init_random() {
static int initialized = 0;
if (!initialized) {
srand(time(NULL));
initialized = 1;
}
}
int random_next_int(int bound) {
if (bound <= 0) return 0; // 防止除零错误
// 初始化随机种子(只需调用一次,建议在程序启动时调用)
static int seeded = 0;
if (!seeded) {
srand(time(0)); // 使用当前时间作为种子
seeded = 1;
}
return rand() % bound;
}
int random_int(int num) {
init_random();
return rand() % num; // 取模限定范围
}
float random_float() {
init_random();
return (float) (rand() / RAND_MAX);
}
char* generate_str(const char *substr) {
if (!substr) return NULL;
// run('...') 总长度:
int totalLen = strlen("run('") + strlen(substr) + strlen("')") + 1;
char *result = (char *)malloc(totalLen);
if (!result) return NULL;
// 更高效的拼接方式(避免 strcat 多次遍历)
snprintf(result, totalLen, "run('%s')", substr);
return result; // 调用者需手动 free
}
jstring replace(JNIEnv *env, jstring source, char* target, char* replacement) {
if (source == NULL || target == NULL || replacement == NULL) {
return (*env)->NewStringUTF(env, "");
}
// 将 Java 字符串转换为 C 字符串
const char *srcStr = (*env)->GetStringUTFChars(env, source, NULL);
// const char *targetStr = (*env)->GetStringUTFChars(env, target, NULL);
// const char *replStr = (*env)->GetStringUTFChars(env, replacement, NULL);
char *result = NULL;
char *foundPos = strstr(srcStr, target);
if (foundPos != NULL) {
// 计算新字符串长度
size_t prefixLen = foundPos - srcStr;
size_t suffixLen = strlen(srcStr) - prefixLen - strlen(target);
size_t totalLen = prefixLen + strlen(replacement) + suffixLen + 1; // +1 for '\0'
// 分配内存并拼接结果
result = (char *)malloc(totalLen);
if (result != NULL) {
memcpy(result, srcStr, prefixLen); // 复制前半部分
memcpy(result + prefixLen, replacement, strlen(replacement)); // 复制替换内容
memcpy(result + prefixLen + strlen(replacement),
foundPos + strlen(target),
suffixLen); // 复制后半部分
result[totalLen - 1] = '\0'; // 终止符
}
}
// 释放 Java 字符串资源
(*env)->ReleaseStringUTFChars(env, source, srcStr);
// (*env)->ReleaseStringUTFChars(env, target, target);
// (*env)->ReleaseStringUTFChars(env, replacement, replacement);
// 返回结果(如果替换失败则返回原始字符串)
jstring jResult;
if (result != NULL) {
jResult = (*env)->NewStringUTF(env, result);
free(result); // 释放 C 层分配的内存
} else {
jResult = source; // 未找到替换目标时原样返回
}
return jResult;
}
//
// Created by 王一诺 on 2025/6/23.
//
#ifndef WEBVIEW_COMMON_H
#define WEBVIEW_COMMON_H
#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <android/log.h>
#include <unistd.h>
#define LOG_TAG "ScrollHelper"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
void init_random();
float random_float();
int random_int(int num);
int random_next_int(int bound);
jlong get_current_time_millis(JNIEnv *env);
void run_on_ui_thread(JNIEnv *env, jobject runnable, jlong delay);
char *generate_str(const char *substr);
jstring replace(JNIEnv *env, jstring source, char *target, char *replacement);
#endif //WEBVIEWDEMO_COMMON_H
//
// Created by 王一诺 on 2025/7/8.
//
#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <android/log.h>
#include <unistd.h>
#include "common.h"
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_listener_impl_DoClickScrollListener_onScrollEnd(JNIEnv *env,
jobject thiz) {
jclass clazz = (*env)->GetObjectClass(env, thiz);
// private WebViewClient client;
jfieldID webViewClientId = (*env)->GetFieldID(env, clazz, "client",
"Lcom/airmobyte/sdk/offer/view/WebViewClient;");
jobject webViewClient = (*env)->GetObjectField(env, thiz, webViewClientId);
// private WebView webView;
jfieldID webViewId = (*env)->GetFieldID(env, clazz, "webView", "Landroid/webkit/WebView;");
jobject webView = (*env)->GetObjectField(env, thiz, webViewId);
// private double x;
jfieldID xId = (*env)->GetFieldID(env, clazz, "x", "D");
jdouble x = (*env)->GetDoubleField(env, thiz, xId);
// private double y;
jfieldID yId = (*env)->GetFieldID(env, clazz, "y", "D");
jdouble y = (*env)->GetDoubleField(env, thiz, yId);
// private String type;
jfieldID typeId = (*env)->GetFieldID(env, clazz, "type", "Ljava/lang/String;");
jstring type = (*env)->GetObjectField(env, thiz, typeId);
jclass doClickRunnerClass = (*env)->FindClass(env,
"com/airmobyte/sdk/offer/view/runnable/DoClickRunnable");
jmethodID doClickRunnerConstructor = (*env)->GetMethodID(env, doClickRunnerClass, "<init>",
"(Lcom/airmobyte/sdk/offer/view/WebViewClient;Landroid/webkit/WebView;DDLjava/lang/String;)V");
jobject doClickRunner = (*env)->NewObject(env, doClickRunnerClass, doClickRunnerConstructor,
webViewClient, webView, x, y, type);
run_on_ui_thread(env, doClickRunner, (long) (3000 + random_float() * 4000));
}
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_listener_impl_DoClickScrollListener_onScrollError(JNIEnv *env,
jobject thiz,
jstring message) {
//client.stepsEnd(5);
jclass clazz = (*env)->GetObjectClass(env, thiz);
jfieldID webViewClientId = (*env)->GetFieldID(env, clazz, "client",
"Lcom/airmobyte/sdk/offer/view/WebViewClient;");
jclass webViewClientClass = (*env)->FindClass(env,
"com/airmobyte/sdk/offer/view/WebViewClient");
jmethodID stepsEndMethod = (*env)->GetMethodID(env, webViewClientClass, "stepsEnd", "(J)V");
(*env)->CallVoidMethod(env, (*env)->GetObjectField(env, thiz, webViewClientId), stepsEndMethod,
(jlong) 5);
}
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_listener_impl_RandomBehaviorScrollListener_onScrollEnd(
JNIEnv *env, jobject thiz) {
//ApplicationUtil.runOnUiThread(new RandomBehaviorRunnable(client, webView, js), (long) (3000 + Math.random() * 3000));
jclass clazz = (*env)->GetObjectClass(env, thiz);
jfieldID webViewClientId = (*env)->GetFieldID(env, clazz, "client",
"Lcom/airmobyte/sdk/offer/view/WebViewClient;");
jobject webViewClient = (*env)->GetObjectField(env, thiz, webViewClientId);
jfieldID webViewId = (*env)->GetFieldID(env, clazz, "webView", "Landroid/webkit/WebView;");
jobject webView = (*env)->GetObjectField(env, thiz, webViewId);
jfieldID jsId = (*env)->GetFieldID(env, clazz, "js", "Ljava/lang/String;");
jstring js = (*env)->GetObjectField(env, thiz, jsId);
jclass randomBehaviorRunnableClass = (*env)->FindClass(env,
"com/airmobyte/sdk/offer/view/runnable/RandomBehaviorRunnable");
jmethodID randomBehaviorRunnableConstructor = (*env)->GetMethodID(env,
randomBehaviorRunnableClass,
"<init>",
"(Lcom/airmobyte/sdk/offer/view/WebViewClient;Landroid/webkit/WebView;Ljava/lang/String;)V");
jobject randomBehaviorRunnable = (*env)->NewObject(env, randomBehaviorRunnableConstructor,
webViewClient, webView, js);
run_on_ui_thread(env, randomBehaviorRunnable, (long) (3000 + random_float() * 3000));
}
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_listener_impl_RandomBehaviorScrollListener_onScrollError(
JNIEnv *env, jobject thiz, jstring message) {
//client.stepsEnd(5);
jclass clazz = (*env)->GetObjectClass(env, thiz);
jfieldID webViewClientId = (*env)->GetFieldID(env, clazz, "client",
"Lcom/airmobyte/sdk/offer/view/WebViewClient;");
jclass webViewClientClass = (*env)->FindClass(env,
"com/airmobyte/sdk/offer/view/WebViewClient");
jmethodID stepsEndMethod = (*env)->GetMethodID(env, webViewClientClass, "stepsEnd", "(J)V");
(*env)->CallVoidMethod(env, (*env)->GetObjectField(env, thiz, webViewClientId), stepsEndMethod,
(jlong) 5);
}
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_listener_impl_RandomAndBackScrollListener_onScrollEnd(
JNIEnv *env, jobject thiz) {
// ApplicationUtil.runOnUiThread(new RandomAndBackRunnable(client, webView, type), (long) (3000 + Math.random() * 3000));
jclass clazz = (*env)->GetObjectClass(env, thiz);
jfieldID webViewClientId = (*env)->GetFieldID(env, clazz, "client",
"Lcom/airmobyte/sdk/offer/view/WebViewClient;");
jobject webViewClient = (*env)->GetObjectField(env, thiz, webViewClientId);
jfieldID webViewId = (*env)->GetFieldID(env, clazz, "webView", "Landroid/webkit/WebView;");
jobject webView = (*env)->GetObjectField(env, thiz, webViewId);
jfieldID typeId = (*env)->GetFieldID(env, clazz, "type", "Ljava/lang/String;");
jstring type = (*env)->GetObjectField(env, thiz, typeId);
jclass randomAndBackRunnableClass = (*env)->FindClass(env,
"com/airmobyte/sdk/offer/view/runnable/RandomAndBackRunnable");
jmethodID randomAndBackRunnableConstructor = (*env)->GetMethodID(env,
randomAndBackRunnableClass,
"<init>",
"(Lcom/airmobyte/sdk/offer/view/WebViewClient;Landroid/webkit/WebView;Ljava/lang/String;)V");
jobject randomAndBackRunnable = (*env)->NewObject(env, randomAndBackRunnableConstructor,
webViewClient, webView, type);
run_on_ui_thread(env, randomAndBackRunnable, (long) (3000 + random_float() * 3000));
}
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_listener_impl_RandomAndBackScrollListener_onScrollError(
JNIEnv *env, jobject thiz, jstring message) {
//client.stepsEnd(5);
jclass clazz = (*env)->GetObjectClass(env, thiz);
jfieldID webViewClientId = (*env)->GetFieldID(env, clazz, "client",
"Lcom/airmobyte/sdk/offer/view/WebViewClient;");
jobject webViewClient = (*env)->GetObjectField(env, thiz, webViewClientId);
jclass webViewClientClass = (*env)->FindClass(env,
"com/airmobyte/sdk/offer/view/WebViewClient");
jmethodID stepsEndMethod = (*env)->GetMethodID(env, webViewClientClass, "stepsEnd", "(J)V");
(*env)->CallVoidMethod(env, webViewClient, stepsEndMethod, (jlong) 5);
}
\ No newline at end of file
//
// Created by 王一诺 on 2025/7/3.
//
#include <jni.h>
#include "common.h"
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_runnable_CheckClickRunnable_run(JNIEnv *env, jobject thiz) {
jclass clazz = (*env)->GetObjectClass(env, thiz);
jfieldID client = (*env)->GetFieldID(env, clazz, "client",
"Lcom/airmobyte/sdk/offer/view/WebViewClient;");
jobject clientObj = (*env)->GetObjectField(env, thiz, client);
jclass webViewClientClazz = (*env)->GetObjectClass(env, clientObj);
jfieldID isClickingField = (*env)->GetFieldID(env, webViewClientClazz, "isClicking", "Z");
jboolean isClicking = (*env)->GetBooleanField(env, clientObj, isClickingField);
jfieldID fhnumField = (*env)->GetFieldID(env, webViewClientClazz, "fhnum", "I");
jint fhnum = (*env)->GetIntField(env, clientObj, fhnumField);
jfieldID numField = (*env)->GetFieldID(env, clazz, "num", "I");
jint num = (*env)->GetIntField(env, thiz, numField);
if (isClicking && fhnum == num) {
(*env)->SetBooleanField(env, clientObj, isClickingField, JNI_FALSE);
jfieldID webViewFidd = (*env)->GetFieldID(env, clazz, "webView",
"Landroid/webkit/WebView;");
jobject webView = (*env)->GetObjectField(env, thiz, webViewFidd);
jfieldID jsField = (*env)->GetFieldID(env, clazz, "js", "Ljava/lang/String;");
jstring js = (*env)->GetObjectField(env, thiz, jsField);
jmethodID evaluateJavascriptMethod = (*env)->GetMethodID(env, webViewClientClazz,
"evaluateJavascript",
"(Landroid/webkit/WebView;Ljava/lang/String;)V");
(*env)->CallVoidMethod(env, clientObj, evaluateJavascriptMethod, webView, js);
}
}
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_runnable_EvaluateJavascriptOnThreadRunnable_run(JNIEnv *env,
jobject thiz) {
jclass clazz = (*env)->GetObjectClass(env, thiz);
jfieldID webViewField = (*env)->GetFieldID(env, clazz, "webView", "Landroid/webkit/WebView;");
jobject webView = (*env)->GetObjectField(env, thiz, webViewField);
jfieldID jsField = (*env)->GetFieldID(env, clazz, "js", "Ljava/lang/String;");
jstring js = (*env)->GetObjectField(env, thiz, jsField);
jfieldID webViewClientField = (*env)->GetFieldID(env, clazz, "client",
"Lcom/airmobyte/sdk/offer/view/WebViewClient;");
jobject webViewClient = (*env)->GetObjectField(env, thiz, webViewClientField);
jclass webViewClientClazz = (*env)->GetObjectClass(env, webViewClient);
jmethodID evaluateJavascriptMethod = (*env)->GetMethodID(env, webViewClientClazz,
"evaluateJavascript",
"(Landroid/webkit/WebView;Ljava/lang/String;)V");
(*env)->CallVoidMethod(env, webViewClient, evaluateJavascriptMethod, webView, js);
if ((*env)->ExceptionCheck(env)) {
jmethodID stepsEnd = (*env)->GetMethodID(env, webViewClientClazz, "stepsEnd", "(J)V");
(*env)->CallVoidMethod(env, webViewClient, stepsEnd, (jlong) 5);
}
}
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_runnable_DoStepsRunnable_run(JNIEnv *env, jobject thiz) {
jclass clazz = (*env)->GetObjectClass(env, thiz);
jfieldID webViewClientField = (*env)->GetFieldID(env, clazz, "client",
"Lcom/airmobyte/sdk/offer/view/WebViewClient;");
jobject webViewClient = (*env)->GetObjectField(env, thiz, webViewClientField);
jclass webViewClientClazz = (*env)->GetObjectClass(env, webViewClient);
jfieldID stepsField = (*env)->GetFieldID(env, webViewClientClazz, "steps",
"Ljava/util/concurrent/atomic/AtomicInteger;");
jobject steps = (*env)->GetObjectField(env, webViewClient, stepsField);
jmethodID getSteps = (*env)->GetMethodID(env, webViewClientClazz, "doTrack",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
jmethodID stepsEnd = (*env)->GetMethodID(env, webViewClientClazz, "stepsEnd", "(J)V");
jfieldID ldnumField = (*env)->GetFieldID(env, webViewClientClazz, "ldnum",
"Ljava/util/concurrent/atomic/AtomicInteger;");
jobject ldnum = (*env)->GetObjectField(env, webViewClient, ldnumField);
jclass atomicIntegerClazz = (*env)->GetObjectClass(env, steps);
jmethodID getMethod = (*env)->GetMethodID(env, atomicIntegerClazz, "get", "()I");
jfieldID uaField = (*env)->GetFieldID(env, clazz, "ua", "Ljava/lang/String;");
jstring ua = (*env)->GetObjectField(env, thiz, uaField);
if ((*env)->CallIntMethod(env, steps, getMethod) == 1 &&
(*env)->CallIntMethod(env, ldnum, getMethod) < 5) {
jstring str_2 = (*env)->NewStringUTF(env, "-2");
jstring strNull = (*env)->NewStringUTF(env, "");
(*env)->CallVoidMethod(env, webViewClient, getSteps, str_2, ua, strNull);
(*env)->CallVoidMethod(env, webViewClient, stepsEnd, (jlong) 5);
}
}
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_runnable_DoClickRunnable_run(JNIEnv *env, jobject thiz) {
//try {
// DisplayMetrics displayMetrics = webView.getContext().getResources().getDisplayMetrics();
// float ratio = displayMetrics.density;
// final float clickx = (float) (x * ratio);
// final float clicky = (float) (y * ratio);
jclass clazz = (*env)->GetObjectClass(env, thiz);
jfieldID webViewClientField = (*env)->GetFieldID(env, clazz, "client",
"Lcom/airmobyte/sdk/offer/view/WebViewClient;");
jfieldID webViewId = (*env)->GetFieldID(env, clazz, "webView", "Landroid/webkit/WebView;");
jobject webView = (*env)->GetObjectField(env, thiz, webViewId);
jclass webViewClass = (*env)->GetObjectClass(env, webViewId);
jmethodID getContextId = (*env)->GetMethodID(env, webViewClass, "getContext",
"()Landroid/content/Context;");
jobject context = (*env)->CallObjectMethod(env, webView, getContextId);
jclass contextClass = (*env)->GetObjectClass(env, context);
jmethodID getResourcesId = (*env)->GetMethodID(env, contextClass, "getResources",
"()Landroid/content/res/Resources;");
jobject resources = (*env)->CallObjectMethod(env, context, getResourcesId);
jclass resourcesClass = (*env)->GetObjectClass(env, resources);
jmethodID getDisplayMetricsId = (*env)->GetMethodID(env, resourcesClass, "getDisplayMetrics",
"()Landroid/util/DisplayMetrics;");
jobject displayMetrics = (*env)->CallObjectMethod(env, resources, getDisplayMetricsId);
jclass displayMetricsClass = (*env)->GetObjectClass(env, displayMetrics);
jfieldID displayMetricsDensityField = (*env)->GetFieldID(env, displayMetricsClass, "density",
"F");
float ratio = (*env)->GetFloatField(env, resources, displayMetricsDensityField);
jfieldID xId = (*env)->GetFieldID(env, clazz, "x", "D");
jdouble x = (*env)->GetDoubleField(env, thiz, xId);
jfieldID yId = (*env)->GetFieldID(env, clazz, "y", "D");
jdouble y = (*env)->GetDoubleField(env, thiz, yId);
float clickx = (float) (x * ratio);
float clicky = (float) (y * ratio);
jfieldID typeId = (*env)->GetFieldID(env, clazz, "type", "Ljava/lang/String;");
jstring type = (*env)->GetObjectField(env, thiz, typeId);
const char *c_str = (*env)->GetStringUTFChars(env, type, NULL);
int result = atoi(c_str);
jobject webViewClient = (*env)->GetObjectField(env, thiz, webViewClientField);
jclass webViewClientClazz = (*env)->GetObjectClass(env, webViewClient);
jfieldID stepsId = (*env)->GetFieldID(env, webViewClientClazz, "steps",
"Ljava/util/concurrent/atomic/AtomicInteger;");
jobject steps = (*env)->GetObjectField(env, webViewClient, stepsId);
jclass atomicIntegerClazz = (*env)->GetObjectClass(env, steps);
jmethodID setMethod = (*env)->GetMethodID(env, atomicIntegerClazz, "set", "(I)V");
jmethodID doAdtrackId = (*env)->GetMethodID(env, webViewClientClazz, "doAdtrack",
"(Ljava/lang/String;)V");
jfieldID isClickingField = (*env)->GetFieldID(env, webViewClientClazz, "isClicking", "Z");
// jboolean isClicking = (*env)->GetBooleanField(env, webViewClient, isClickingField);
jfieldID adsTryNumId = (*env)->GetFieldID(env, webViewClientClazz, "adsTryNum", "I");
jint adsTryNum = (*env)->GetIntField(env, webViewClient, adsTryNumId);
jfieldID adsClickTryId = (*env)->GetFieldID(env, webViewClientClazz, "adsClickTry", "I");
jint adsClickTry = (*env)->GetIntField(env, webViewClient, adsClickTryId);
jmethodID checkClickId = (*env)->GetMethodID(env, webViewClientClazz, "checkClick",
"(ILandroid/webkit/WebView;Ljava/lang/String;Ljava/lang/String;)V");
jfieldID fhnumId = (*env)->GetFieldID(env, webViewClientClazz, "fhnum", "I");
jint fhnum = (*env)->GetIntField(env, webViewClient, fhnumId);
jfieldID relaSearchJsId = (*env)->GetFieldID(env, webViewClientClazz, "relaSearchJs",
"Ljava/lang/String;");
jstring relaSearchJs = (*env)->GetObjectField(env, webViewClient, relaSearchJsId);
jfieldID joinSearchRetryIntervalId = (*env)->GetFieldID(env, webViewClientClazz,
"joinSearchRetryInterval",
"Ljava/lang/String;");
jstring joinSearchRetryInterval = (*env)->GetObjectField(env, webViewClient,
joinSearchRetryIntervalId);
jmethodID stepsEndId = (*env)->GetMethodID(env, webViewClientClazz, "stepsEnd", "(J)V");
jfieldID nopfSsrId = (*env)->GetFieldID(env, webViewClientClazz, "nopfSsr", "Z");
jboolean nopfSsr = (*env)->GetBooleanField(env, webViewClient, nopfSsrId);
switch (result) {
case 1: {
//steps.set(4);
(*env)->CallVoidMethod(env, steps, setMethod, 4);
//client.doAdtrack("5");
jstring str5 = (*env)->NewStringUTF(env, "5");
(*env)->CallVoidMethod(env, webViewClient, doAdtrackId, str5);
//isClicking = true;
(*env)->SetBooleanField(env, webViewClient, isClickingField, JNI_TRUE);
//if (adsTryNum < adsClickTry)
if (adsTryNum < adsClickTry) {
//adsTryNum++;
(*env)->SetIntField(env, webViewClient, adsTryNumId, adsTryNum + 1);
// client.checkClick(fhnum, webView, adClickJs.replace("run()", "run('" + clickedAd + "')"), adsClickTryInterval);
jfieldID clickedAdId = (*env)->GetFieldID(env, webViewClientClazz, "clickedAd",
"Ljava/lang/String;");
jstring clickedAd = (*env)->GetObjectField(env, webViewClient, clickedAdId);
char *strClickedAd = (*env)->NewStringUTF(env, clickedAd);
char *strReplace = generate_str(strClickedAd);
char *strRun = (*env)->NewStringUTF(env, "run()");
jfieldID adClickJsId = (*env)->GetFieldID(env, webViewClientClazz, "adClickJs",
"Ljava/lang/String;");
jstring adClickJs = (*env)->GetObjectField(env, webViewClient, adClickJsId);
jstring replacedAdClickJs = replace(env, adClickJs, strRun, strReplace);
jfieldID adsClickTryIntervalId = (*env)->GetFieldID(env, webViewClientClazz,
"adsClickTryInterval",
"Ljava/lang/String;");
jstring adsClickTryInterval = (*env)->GetObjectField(env, webViewClient,
adsClickTryIntervalId);
(*env)->CallVoidMethod(env, webViewClient, checkClickId, fhnum, webView,
replacedAdClickJs, adsClickTryInterval);
}
break;
}
case 2: {
//steps.set(5);
(*env)->CallVoidMethod(env, steps, setMethod, 5);
///client.doTrack("43");
jstring str43 = (*env)->NewStringUTF(env, "43");
(*env)->CallVoidMethod(env, webViewClient, doAdtrackId, str43);
break;
}
case 3: {
//steps.set(6);
(*env)->CallVoidMethod(env, steps, setMethod, 6);
//client.doTrack("7");
jstring str7 = (*env)->NewStringUTF(env, "7");
(*env)->CallVoidMethod(env, webViewClient, doAdtrackId, str7);
if (nopfSsr) {
//client.doStepsDelay(webView, currentUrl, (long) (10000 + Math.random() * 10000));
jfieldID currentUrlId = (*env)->GetFieldID(env, webViewClientClazz, "currentUrl",
"Ljava/lang/String;");
jstring currentUrl = (*env)->GetObjectField(env, webViewClient, currentUrlId);
jmethodID doStepsDelayId = (*env)->GetMethodID(env, webViewClientClazz,
"doStepsDelay",
"(Landroid/webkit/WebView;Ljava/lang/String;J)V");
(*env)->CallVoidMethod(env, doStepsDelayId, webView, currentUrl,
(jlong) (10000 + random_float() * 10000));
}
break;
}
case 4: {
//steps.set(7);
(*env)->CallVoidMethod(env, steps, setMethod, 7);
jfieldID joinSearchTryNumId = (*env)->GetFieldID(env, webViewClientClazz,
"joinSearchTryNum", "I");
jint joinSearchTryNum = (*env)->GetIntField(env, webViewClient, joinSearchTryNumId);
if (joinSearchTryNum == 0) {
jstring str8 = (*env)->NewStringUTF(env, "8");
(*env)->CallVoidMethod(env, webViewClient, doAdtrackId, str8);
}
(*env)->SetBooleanField(env, webViewClient, isClickingField, JNI_TRUE);
jfieldID joinSearchRetryId = (*env)->GetFieldID(env, webViewClientClazz,
"joinSearchRetry", "I");
jint joinSearchRetry = (*env)->GetIntField(env, webViewClient, joinSearchRetryId);
if (joinSearchTryNum < joinSearchRetry) {
//joinSearchTryNum++;
(*env)->SetIntField(env, webViewClient, joinSearchTryNumId, joinSearchTryNum + 1);
// client.checkClick(fhnum, webView, relaSearchJs, joinSearchRetryInterval);
(*env)->CallVoidMethod(env, webViewClient, checkClickId, fhnum, webView,
relaSearchJs, joinSearchRetryInterval);
}
break;
}
case 5:
case 6: {
(*env)->SetBooleanField(env, webViewClient, nopfSsrId, JNI_TRUE);
jmethodID evaluateJavascriptOnThreadId = (*env)->GetMethodID(env, webViewClientClazz,
"evaluateJavascriptOnThread",
"(Landroid/webkit/WebView;Ljava/lang/String;J)V");
jfieldID secondSearchJsId = (*env)->GetFieldID(env, webViewClientClazz,
"secondSearchJs",
"Ljava/lang/String;");
jstring secondSearchJs = (*env)->GetObjectField(env, webViewClient, secondSearchJsId);
(*env)->CallVoidMethod(env, webViewClient, evaluateJavascriptOnThreadId, webView,
secondSearchJs,
(jlong) (1000 + random_float() * 3000));
break;
}
default:
(*env)->CallVoidMethod(env, webViewClient, stepsEndId, (jlong) 10);
}
// ScrollHelper.handlerClickEvent(webView, clickx, clicky - webView.getScrollY());
jclass scrollhelperClass = (*env)->FindClass(env, "com/airmobyte/sdk/offer/view/ScrollHelper");
jmethodID handlerClickEventId = (*env)->GetStaticMethodID(env, scrollhelperClass,
"handlerClickEvent",
"(Landroid/webkit/WebView;FF)V");
jmethodID getScrollYId = (*env)->GetMethodID(env, webViewClass, "getScrollY", "()I");
float scrollY = (float) (*env)->CallIntMethod(env, webViewClient, getScrollYId);
(*env)->CallStaticVoidMethod(env, scrollhelperClass, handlerClickEventId, webView, clickx,
clicky - scrollY);
if ((*env)->ExceptionCheck(env)) {
// 处理异常...
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
(*env)->CallVoidMethod(env, webViewClient, stepsEndId, (jlong) 5);
// 可以直接返回,让 Java 代码处理
return;
}
}
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_runnable_RandomBehaviorRunnable_run(JNIEnv *env, jobject thiz) {
jclass randomBehaviorRunnableClass = (*env)->GetObjectClass(env, thiz);
jfieldID webViewClientField = (*env)->GetFieldID(env, randomBehaviorRunnableClass, "client",
"Lcom/airmobyte/sdk/offer/view/WebViewClient;");
jobject webViewClient = (*env)->GetObjectField(env, thiz, webViewClientField);
jclass webViewClientClazz = (*env)->GetObjectClass(env, webViewClient);
jmethodID evaluateJavascriptId = (*env)->GetMethodID(env, webViewClientClazz,
"evaluateJavascript",
"(Landroid/webkit/WebView;Ljava/lang/String;)V");
jfieldID jsField = (*env)->GetFieldID(env, randomBehaviorRunnableClass, "js",
"Ljava/lang/String;");
jstring js = (*env)->GetObjectField(env, thiz, jsField);
jfieldID webViewField = (*env)->GetFieldID(env, randomBehaviorRunnableClass, "webView",
"Landroid/webkit/WebView;");
jobject webView = (*env)->GetObjectField(env, thiz, webViewField);
(*env)->CallVoidMethod(env, webViewClient, evaluateJavascriptId, webView, js);
if ((*env)->ExceptionCheck(env)) {
(*env)->ExceptionDescribe(env); // 打印异常
(*env)->ExceptionClear(env); // 清除异常
jmethodID stepsEndId = (*env)->GetMethodID(env, webViewClientClazz, "stepsEnd", "(J)V");
(*env)->CallVoidMethod(env, webViewClient, stepsEndId, (jlong) 5);
return;
}
}
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_runnable_RandomAndBackRunnable_run(JNIEnv *env, jobject thiz) {
//try {
// if (new Random().nextInt(100) < bnum * 10) {
jclass randomAndBackRunnableClass = (*env)->GetObjectClass(env, thiz);
jfieldID webViewClientField = (*env)->GetFieldID(env, randomAndBackRunnableClass,
"client",
"Lcom/airmobyte/sdk/offer/view/WebViewClient;");
jobject webViewClient = (*env)->GetObjectField(env, thiz, webViewClientField);
jclass webViewClientClazz = (*env)->GetObjectClass(env, webViewClient);
jfieldID bnumId = (*env)->GetFieldID(env, webViewClientClazz, "bnum", "D");
jdouble bnum = (*env)->GetDoubleField(env, webViewClient, bnumId);
int randomInt = random_next_int(100);
if (randomInt < bnum * 10) {
// fhnum++;
jfieldID fhnumId = (*env)->GetFieldID(env, webViewClientClazz, "fhnum", "I");
(*env)->SetIntField(env, webViewClient, fhnumId,
(*env)->GetIntField(env, webViewClient, fhnumId) + 1);
jfieldID typeId = (*env)->GetFieldID(env, randomAndBackRunnableClass, "type",
"Ljava/lang/String;");
jstring type = (*env)->GetObjectField(env, thiz, typeId);
const char *typeStr = (*env)->GetStringUTFChars(env, type, NULL);
jfieldID stepsID = (*env)->GetFieldID(env, webViewClientClazz, "steps",
"Ljava/util/concurrent/atomic/AtomicInteger;");
jobject steps = (*env)->GetObjectField(env, webViewClient, stepsID);
jclass atomicIntegerClass = (*env)->GetObjectClass(env, steps);
jmethodID setId = (*env)->GetMethodID(env, atomicIntegerClass, "set", "(I)V");
jmethodID doTrackId = (*env)->GetMethodID(env, webViewClientClazz, "doTrack",
"(Ljava/lang/String;)V");
if (strcmp(typeStr, "1") == 0) {
//steps.set(8);
(*env)->CallVoidMethod(env, steps, setId, 8);
// // doTrack("9");
jstring str9 = (*env)->NewStringUTF(env, "9");
(*env)->CallVoidMethod(env, webViewClient, doTrackId, str9);
} else {
//steps.set(9);
(*env)->CallVoidMethod(env, steps, setId, 9);
// client.doTrack("46");
jstring str46 = (*env)->NewStringUTF(env, "46");
(*env)->CallVoidMethod(env, webViewClient, doTrackId, str46);
}
// webView.goBack();
jfieldID webViewField = (*env)->GetFieldID(env, randomAndBackRunnableClass, "webView",
"Landroid/webkit/WebView;");
jobject webView = (*env)->GetObjectField(env, thiz, webViewField);
jclass webViewClazz = (*env)->GetObjectClass(env, webView);
jmethodID goBackId = (*env)->GetMethodID(env, webViewClazz, "goBack", "()V");
(*env)->CallVoidMethod(env, webView, goBackId);
} else {
// client.stepsEnd(5);
jmethodID stepsEndId = (*env)->GetMethodID(env, webViewClientClazz, "stepsEnd", "(J)V");
(*env)->CallVoidMethod(env, webViewClient, stepsEndId, (jlong) 5);
}
// } catch (Throwable ignored) {
// client.stepsEnd(5);
// }
if ((*env)->ExceptionCheck(env)) {
(*env)->ExceptionDescribe(env); // 打印异常
(*env)->ExceptionClear(env); // 清除异常
jmethodID stepsEndId = (*env)->GetMethodID(env, webViewClientClazz, "stepsEnd", "(J)V");
(*env)->CallVoidMethod(env, webViewClient, stepsEndId, (jlong) 5);
return;
}
}
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_runnable_ScrollScreenRunnable_run(JNIEnv *env, jobject thiz) {
//try {
jclass scrollScreenRunnableClass = (*env)->GetObjectClass(env, thiz);
jfieldID webViewClientField = (*env)->GetFieldID(env, scrollScreenRunnableClass, "client",
"Lcom/airmobyte/sdk/offer/view/WebViewClient;");
jobject webViewClient = (*env)->GetObjectField(env, thiz, webViewClientField);
jclass webViewClientClazz = (*env)->GetObjectClass(env, webViewClient);
jfieldID isEndId = (*env)->GetFieldID(env, webViewClientClazz, "isEnd",
"Ljava/util/concurrent/atomic/AtomicBoolean;");
jobject isEnd = (*env)->GetObjectField(env, webViewClient, isEndId);
jclass atomicBooleanClass = (*env)->GetObjectClass(env, isEnd);
jmethodID getId = (*env)->GetMethodID(env, atomicBooleanClass, "get", "()Z");
//if (isEnd.get()) {
if ((*env)->CallBooleanMethod(env, isEnd, getId)) {
return;
}
jclass scrollHelperClass = (*env)->FindClass(env, "com/airmobyte/sdk/offer/view/ScrollHelper");
jmethodID yScrollToId = (*env)->GetStaticMethodID(env, scrollHelperClass, "yScrollTo",
"(Landroid/webkit/WebView;FLcom/airmobyte/sdk/offer/view/listener/ScrollListener;J)V");
jfieldID webViewField = (*env)->GetFieldID(env, scrollScreenRunnableClass, "webView",
"Landroid/webkit/WebView;");
jobject webView = (*env)->GetObjectField(env, thiz, webViewField);
jfieldID heightId = (*env)->GetFieldID(env, scrollScreenRunnableClass, "height", "F");
jfloat height = (*env)->GetFloatField(env, thiz, heightId);
jfieldID listenerField = (*env)->GetFieldID(env, scrollScreenRunnableClass, "listener",
"Lcom/airmobyte/sdk/offer/view/listener/ScrollListener;");
jobject listener = (*env)->GetObjectField(env, thiz, listenerField);
(*env)->CallStaticVoidMethod(env, scrollHelperClass, yScrollToId,
webView,
height,
listener,
get_current_time_millis(env));
if ((*env)->ExceptionCheck(env)) {
(*env)->ExceptionDescribe(env); // 打印异常
(*env)->ExceptionClear(env); // 清除异常
jmethodID stepsEndId = (*env)->GetMethodID(env, webViewClientClazz, "stepsEnd", "(J)V");
(*env)->CallVoidMethod(env, webViewClient, stepsEndId, (jlong) 5);
return;
}
}
\ No newline at end of file
//
// Created by 王一诺 on 2025/7/2.
//
#include <jni.h>
#include "common.h"
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_WebViewClient_evaluateJavascript(JNIEnv *env, jobject thiz,
jobject webview, jstring js) {
/*
* Logger.d("js: " + js);
if (isEnd.get()) {
Logger.d("evaluateJavascript: isEnd");
return;
}
webView.evaluateJavascript("javascript:" + js, new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
}
});
*/
jclass webViewClientClass = (*env)->GetObjectClass(env, thiz);
jfieldID isEndFieldId = (*env)->GetFieldID(env, webViewClientClass, "isEnd",
"Ljava/util/concurrent/atomic/AtomicBoolean;");
jobject isEnd = (*env)->GetObjectField(env, thiz, isEndFieldId);
if ((*env)->CallBooleanMethod(env, isEnd,
(*env)->GetMethodID(env, (*env)->GetObjectClass(env, isEnd),
"get", "()Z"))) {
return;
}
jclass webViewClass = (*env)->GetObjectClass(env, webview);
jmethodID evaluateJavascriptMethodId = (*env)->GetMethodID(env, webViewClass,
"evaluateJavascript",
"(Ljava/lang/String;Landroid/webkit/ValueCallback;)V");
(*env)->CallVoidMethod(env, webview, evaluateJavascriptMethodId, js, NULL);
}
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_WebViewClient_evaluateJavascriptOnThread(JNIEnv *env,
jobject thiz,
jobject webview,
jstring js,
jlong delay) {
jclass EvaluateJavascriptOnThreadRunnableClass = (*env)->FindClass(env,
"com/airmobyte/sdk/offer/view/runnable/EvaluateJavascriptOnThreadRunnable");
jmethodID constructorMethodId = (*env)->GetMethodID(env,
EvaluateJavascriptOnThreadRunnableClass,
"<init>",
"(Landroid/webkit/WebView;Ljava/lang/String;Lcom/airmobyte/sdk/offer/view/WebViewClient;)V");
jobject runnable = (*env)->NewObject(env, EvaluateJavascriptOnThreadRunnableClass,
constructorMethodId, webview, js, thiz);
run_on_ui_thread(env, runnable, delay);
}
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_WebViewClient_stepsEnd(JNIEnv *env, jobject thiz, jlong delay) {
/*
* if (!isEnd.get()) {
isEnd.set(true);
Logger.d("stepsEnd delay: " + delay);
steps.set(0);
OfferWorker.instance().cleanAndNext(delay * 1000);
}
*/
jclass webViewClientClass = (*env)->GetObjectClass(env, thiz);
jfieldID isEndFieldId = (*env)->GetFieldID(env, webViewClientClass, "isEnd",
"Ljava/util/concurrent/atomic/AtomicBoolean;");
jobject isEnd = (*env)->GetObjectField(env, thiz, isEndFieldId);
if (!(*env)->CallBooleanMethod(env, isEnd,
(*env)->GetMethodID(env, (*env)->GetObjectClass(env, isEnd),
"get", "()Z"))) {
jmethodID setMethodId = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, isEnd), "set",
"(Z)V");
(*env)->CallVoidMethod(env, isEnd, setMethodId, JNI_TRUE);
jfieldID stepsFieldId = (*env)->GetFieldID(env, webViewClientClass, "steps",
"Ljava/util/concurrent/atomic/AtomicInteger;");
jobject steps = (*env)->GetObjectField(env, thiz, stepsFieldId);
(*env)->CallVoidMethod(env, steps,
(*env)->GetMethodID(env, (*env)->GetObjectClass(env, steps), "set",
"(I)V"), 0);
jclass offerWorkerClass = (*env)->FindClass(env,
"com/airmobyte/sdk/offer/work/OfferWorker");
jmethodID instanceMethodId = (*env)->GetStaticMethodID(env, offerWorkerClass, "instance",
"()Lcom/airmobyte/sdk/offer/work/OfferWorker;");
jmethodID cleanAndNextMethodId = (*env)->GetMethodID(env, offerWorkerClass, "cleanAndNext",
"(J)V");
jobject offerWorker = (*env)->CallStaticObjectMethod(env, offerWorkerClass,
instanceMethodId);
(*env)->CallVoidMethod(env, offerWorker, cleanAndNextMethodId, delay * 1000);
}
}
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_WebViewClient_doTrack(JNIEnv *env, jobject thiz, jstring logType,
jstring ua, jstring error) {
// TrackUtil.dotrack(logType, reportUrl, resultUrl, currentUrl, ua, error, String.valueOf(System.currentTimeMillis() - startTime), String.valueOf(currentPage), String.valueOf(fhnum), String.valueOf(adnum));
jclass webViewClientClass = (*env)->GetObjectClass(env, thiz);
jfieldID reportUrlFieldId = (*env)->GetFieldID(env, webViewClientClass, "reportUrl",
"Ljava/lang/String;");
jstring reportUrl = (*env)->GetObjectField(env, thiz, reportUrlFieldId);
jfieldID resultUrlFieldId = (*env)->GetFieldID(env, webViewClientClass, "resultUrl",
"Ljava/lang/String;");
jstring resultUrl = (*env)->GetObjectField(env, thiz, resultUrlFieldId);
jfieldID currentUrlFieldId = (*env)->GetFieldID(env, webViewClientClass, "currentUrl",
"Ljava/lang/String;");
jstring currentUrl = (*env)->GetObjectField(env, thiz, currentUrlFieldId);
jfieldID startTimeFieldId = (*env)->GetFieldID(env, webViewClientClass, "startTime", "J");
jlong startTime = (*env)->GetLongField(env, thiz, startTimeFieldId);
jfieldID currentPageFieldId = (*env)->GetFieldID(env, webViewClientClass, "currentPage", "I");
jint currentPage = (*env)->GetIntField(env, thiz, currentPageFieldId);
jfieldID fhnumFieldId = (*env)->GetFieldID(env, webViewClientClass, "fhnum", "I");
jint fhnum = (*env)->GetIntField(env, thiz, fhnumFieldId);
jfieldID adnumFieldId = (*env)->GetFieldID(env, webViewClientClass, "adnum", "I");
jint adnum = (*env)->GetIntField(env, thiz, adnumFieldId);
jclass trackUtilClass = (*env)->FindClass(env, "com/airmobyte/sdk/offer/util/TrackUtil");
//public static void dotrack(final String logType, final String logMessage, final String result_url, final String current_url, final String ua, final String error_str, final String time_consuming, final String page, final String fhnum, final String adnum) {
jmethodID dotrackMethodId = (*env)->GetStaticMethodID(env, trackUtilClass, "dotrack",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
//String.valueOf
jmethodID valueOfMethodLongId = (*env)->GetStaticMethodID(env, (*env)->FindClass(env,
"java/lang/String"),
"valueOf", "(J)Ljava/lang/String;");
//System.currentTimeMillis()
jlong currentTimeMillis = get_current_time_millis(env);
jstring currentTimeMillisString = (*env)->CallStaticObjectMethod(env, (*env)->FindClass(env,
"java/lang/String"),
valueOfMethodLongId,
currentTimeMillis - startTime);
jmethodID valueOfMethodIntId = (*env)->GetStaticMethodID(env, (*env)->FindClass(env,
"java/lang/String"),
"valueOf", "(I)Ljava/lang/String;");
jstring currentPageString = (*env)->CallStaticObjectMethod(env, (*env)->FindClass(env,
"java/lang/String"),
valueOfMethodIntId, currentPage);
jstring fhnumString = (*env)->CallStaticObjectMethod(env,
(*env)->FindClass(env, "java/lang/String"),
valueOfMethodIntId, fhnum);
jstring adnumString = (*env)->CallStaticObjectMethod(env,
(*env)->FindClass(env, "java/lang/String"),
valueOfMethodIntId, adnum);
(*env)->CallStaticVoidMethod(env, trackUtilClass, dotrackMethodId, logType, reportUrl,
resultUrl, currentUrl, ua, error, currentTimeMillisString,
currentPageString, fhnumString, adnumString);
}
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_WebViewClient_checkClick(JNIEnv *env, jobject thiz, jint num,
jobject webView, jstring js,
jstring delay) {
jclass checkClickRunnableClass = (*env)->FindClass(env,
"com/airmobyte/sdk/offer/view/runnable/CheckClickRunnable");
jmethodID checkClickRunnableConstructorId = (*env)->GetMethodID(env, checkClickRunnableClass,
"<init>",
"(Landroid/webkit/WebView;ILjava/lang/String;Lcom/airmobyte/sdk/offer/view/WebViewClient;)V");
jobject checkClickRunnable = (*env)->NewObject(env, checkClickRunnableClass,
checkClickRunnableConstructorId, webView, num,
js, thiz);
jclass webViewClientClass = (*env)->GetObjectClass(env, thiz);
jmethodID getDelayMethodId = (*env)->GetMethodID(env, webViewClientClass, "getDelay",
"(Ljava/lang/String;)J");
run_on_ui_thread(env, checkClickRunnable,
(*env)->CallLongMethod(env, thiz, getDelayMethodId, delay));
}
JNIEXPORT jboolean
JNICALL
Java_com_airmobyte_sdk_offer_view_WebViewClient_checkStatus(JNIEnv *env,
jobject thiz,
jobject web_view,
jstring type,
jdouble x,
jdouble y) {
jclass webViewClientClass = (*env)->GetObjectClass(env, thiz);
jmethodID doAdtrackMethodId = (*env)->GetMethodID(env, webViewClientClass, "doAdtrack",
"(Ljava/lang/String;)V");
jmethodID doStepsMethodId = (*env)->GetMethodID(env, webViewClientClass, "doSteps",
"(Landroid/webkit/WebView;Ljava/lang/String;)V");
jmethodID stepsEndMethodId = (*env)->GetMethodID(env, webViewClientClass, "stepsEnd", "(J)V");
jfieldID currentUrlId = (*env)->GetFieldID(env, webViewClientClass, "currentUrl",
"Ljava/lang/String;");
jstring currentUrl = (*env)->GetObjectField(env, thiz, currentUrlId);
jfieldID stepsId = (*env)->GetFieldID(env, webViewClientClass, "steps",
"Ljava/util/concurrent/atomic/AtomicInteger;");
jobject steps = (*env)->GetObjectField(env, thiz, stepsId);
jclass stepsClass = (*env)->GetObjectClass(env, steps);
jmethodID setMethodId = (*env)->GetMethodID(env, stepsClass, "set", "(I)V");
const char *typeString = (*env)->GetStringUTFChars(env, type, NULL);
if (x != 0 && y != 0) {
if (strcmp(typeString, "1") == 0) {
jstring str12 = (*env)->NewStringUTF(env, "12");
(*env)->CallVoidMethod(env, thiz, doAdtrackMethodId, str12);
int randomInt = random_int(100);
jfieldID reateId = (*env)->GetFieldID(env, webViewClientClass, "rate", "D");
double rate = (*env)->GetDoubleField(env, thiz, reateId);
if (randomInt >= rate * 10) {
//doAdtrack("6");
jstring str6 = (*env)->NewStringUTF(env, "6");
(*env)->CallVoidMethod(env, thiz, doAdtrackMethodId, str6);
//steps.set(3);
(*env)->CallVoidMethod(env, steps, setMethodId, 3);
//doSteps(6, currentUrl);
(*env)->CallVoidMethod(env, thiz, doStepsMethodId, web_view, currentUrl);
return JNI_FALSE;
}
} else if (strcmp(typeString, "2") == 0) {
//doTrack("41");
jstring str41 = (*env)->NewStringUTF(env, "41");
(*env)->CallVoidMethod(env, thiz, doAdtrackMethodId, str41);
int randomInt = random_int(100);
jfieldID ejymRateId = (*env)->GetFieldID(env, webViewClientClass, "ejymRate", "D");
double ejymRate = (*env)->GetDoubleField(env, thiz, ejymRateId);
//if (new Random().nextInt(100) >= ejymRate * 10)
if (randomInt >= ejymRate * 10) {
//doTrack("44");
jstring str44 = (*env)->NewStringUTF(env, "44");
(*env)->CallVoidMethod(env, thiz, doAdtrackMethodId, str44);
//stepsEnd(5);
(*env)->CallVoidMethod(env, thiz, stepsEndMethodId, (jlong) 3);
return JNI_FALSE;
}
}
} else {
if (strcmp(typeString, "1") == 0) {
jstring str4 = (*env)->NewStringUTF(env, "4");
(*env)->CallVoidMethod(env, thiz, doAdtrackMethodId, str4);
//steps.set(3);
(*env)->CallVoidMethod(env, steps, setMethodId, 3);
//doSteps(6, currentUrl);
(*env)->CallVoidMethod(env, thiz, doStepsMethodId, web_view, currentUrl);
} else if (strcmp(typeString, "2") == 0) {
jstring str42 = (*env)->NewStringUTF(env, "42");
(*env)->CallVoidMethod(env, thiz, doAdtrackMethodId, str42);
//steps.set(3);
(*env)->CallVoidMethod(env, thiz, stepsEndMethodId, (jlong) 5);
} else {
//stepsEnd(5);
(*env)->CallVoidMethod(env, thiz, stepsEndMethodId, (jlong) 5);
}
return JNI_FALSE;
}
return JNI_TRUE;
}
void JNICALL
Java_com_airmobyte_sdk_offer_view_WebViewClient_doSteps(JNIEnv *env, jobject thiz, jobject webView,
jstring url) {
jclass webViewClientClass = (*env)->GetObjectClass(env, thiz);
jfieldID currentUrlId = (*env)->GetFieldID(env, webViewClientClass, "currentUrl",
"Ljava/lang/String;");
jmethodID doSettingMethodId = (*env)->GetMethodID(env, webViewClientClass, "doSetting",
"(Landroid/webkit/WebView;)V");
// public void doSteps(final WebView webView, String url) {
// currentUrl = url;
(*env)->SetObjectField(env, thiz, currentUrlId, url);
// doSetting(webView);
(*env)->CallVoidMethod(env, thiz, doSettingMethodId, webView);
// Logger.d("doSteps: " + steps.get());
// if (isEnd.get()) {
jfieldID isEndId = (*env)->GetFieldID(env, webViewClientClass, "isEnd",
"Ljava/util/concurrent/atomic/AtomicBoolean;");
jobject isEnd = (*env)->GetObjectField(env, thiz, isEndId);
jclass isEndClass = (*env)->GetObjectClass(env, isEnd);
jmethodID getMethodId = (*env)->GetMethodID(env, isEndClass, "get", "()Z");
// Logger.d("doSteps: isEnd");
// return;
if ((*env)->CallBooleanMethod(env, isEnd, getMethodId)) {
return;
}
jfieldID stepsId = (*env)->GetFieldID(env, webViewClientClass, "steps",
"Ljava/util/concurrent/atomic/AtomicInteger;");
jobject steps = (*env)->GetObjectField(env, thiz, stepsId);
jclass stepsClass = (*env)->GetObjectClass(env, steps);
jmethodID stepsGetMethodId = (*env)->GetMethodID(env, stepsClass, "get", "()I");
jmethodID stepsSetMethodId = (*env)->GetMethodID(env, stepsClass, "set", "(I)V");
jint stepsGet = (*env)->CallIntMethod(env, steps, stepsGetMethodId);
jmethodID doTrackMethodId = (*env)->GetMethodID(env, webViewClientClass, "doTrack",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
jclass webViewClass = (*env)->FindClass(env, "android/webkit/WebView");
jmethodID getSettingsMethodId = (*env)->GetMethodID(env, webViewClass, "getSettings",
"()Landroid/webkit/WebSettings;");
jobject settings = (*env)->CallObjectMethod(env, webView, getSettingsMethodId);
jclass webSettingsClass = (*env)->FindClass(env, "android/webkit/WebSettings");
jmethodID getUserAgentStringMethodId = (*env)->GetMethodID(env, webSettingsClass,
"getUserAgentString",
"()Ljava/lang/String;");
jstring ua = (*env)->CallObjectMethod(env, settings, getUserAgentStringMethodId);
jmethodID doStepMethodId = (*env)->GetMethodID(env, webViewClientClass, "doSteps",
"(Landroid/webkit/WebView;Ljava/lang/String;)V");
jmethodID stepEndMethodId = (*env)->GetMethodID(env, webViewClientClass, "stepsEnd", "(J)V");
jmethodID randomAndBackMethodId = (*env)->GetMethodID(env, webViewClientClass, "randomAndBack",
"(Landroid/webkit/WebView;Ljava/lang/String;)V");
jmethodID doTrackMethodId1 = (*env)->GetMethodID(env, webViewClientClass, "doTrack",
"(Ljava/lang/String;)V");
jmethodID randomBehaviorId = (*env)->GetMethodID(env, webViewClientClass, "randomBehavior",
"(Landroid/webkit/WebView;Ljava/lang/String;)V");
jfieldID clickedAdId = (*env)->GetFieldID(env, webViewClientClass, "clickedAd",
"Ljava/lang/String;");
jstring clickedAd = (*env)->GetObjectField(env, thiz, clickedAdId);
char *strClickedAd = (*env)->NewStringUTF(env, clickedAd);
char *strReplace = generate_str(strClickedAd);
char *strRun = (*env)->NewStringUTF(env, "run()");
jfieldID adClickJsId = (*env)->GetFieldID(env, webViewClientClass, "adClickJs",
"Ljava/lang/String;");
jstring adClickJs = (*env)->GetObjectField(env, thiz, adClickJsId);
jstring replacedAdClickJs = replace(env, adClickJs, strRun, strReplace);
//adClickJs.replace("run()", "run('" + clickedAd + "')")
jclass textUtilsClass = (*env)->FindClass(env, "android/text/TextUtils");
jfieldID isClickId = (*env)->GetFieldID(env, webViewClientClass, "isClicking", "Z");
jmethodID isEmptyMethodId = (*env)->GetStaticMethodID(env, textUtilsClass, "isEmpty",
"(Ljava/lang/CharSequence;)Z");
// switch (steps.get()) {
switch (stepsGet) {
case 1: {
jfieldID ldurlFieldId;
jstring ldurl;
// if (url.contains(ldurl) || TextUtils.isEmpty(ldurl)) {
ldurlFieldId = (*env)->GetFieldID(env, webViewClientClass, "ldurl",
"Ljava/lang/String;");
ldurl = (*env)->GetObjectField(env, thiz, ldurlFieldId);
jmethodID containsMethodId = (*env)->GetMethodID(env, (*env)->FindClass(env,
"java/lang/String"),
"contains",
"(Ljava/lang/CharSequence;)Z");
if ((*env)->CallBooleanMethod(env, url, containsMethodId, ldurl) ||
(*env)->CallStaticBooleanMethod(env, textUtilsClass, isEmptyMethodId, url)) {
// steps.set(2);
(*env)->CallVoidMethod(env, steps, stepsSetMethodId, 2);
// resultUrl = url;
jfieldID resultUrlFieldId = (*env)->GetFieldID(env, webViewClientClass, "resultUrl",
"Ljava/lang/String;");
(*env)->SetObjectField(env, thiz, resultUrlFieldId, url);
// doTrack("2", webView.getSettings().getUserAgentString(), "");
jstring str2 = (*env)->NewStringUTF(env, "2");
jstring strNull = (*env)->NewStringUTF(env, "");
(*env)->CallVoidMethod(env, thiz, doTrackMethodId, str2, ua, strNull);
// doSteps(webView, url);
(*env)->CallVoidMethod(env, thiz, doStepMethodId, webView, url);
} else {
// int num = ldnum.incrementAndGet();
jfieldID ldumId = (*env)->GetFieldID(env, webViewClientClass, "ldnum",
"Ljava/util/concurrent/atomic/AtomicInteger;");
jobject ldum = (*env)->GetObjectField(env, thiz, ldumId);
jclass atomicIntegerClass = (*env)->GetObjectClass(env, ldum);
jmethodID incAndGetMethodId = (*env)->GetMethodID(env, atomicIntegerClass,
"incrementAndGet", "()I");
int num = (*env)->CallIntMethod(env, ldum, incAndGetMethodId);
//if (num >= 5) {
if (num >= 5) {
// doTrack("-2", webView.getSettings().getUserAgentString(), "");
jstring str_2 = (*env)->NewStringUTF(env, "-2");
jstring strNUll = (*env)->NewStringUTF(env, "");
(*env)->CallVoidMethod(env, thiz, doTrackMethodId, str_2, ua, strNUll);
// stepsEnd(5);
(*env)->CallVoidMethod(env, thiz, stepEndMethodId, (jlong) 5);
}
// if (num == 1) {
if (num == 1) {
jclass doStepRunnableClass = (*env)->FindClass(env,
"com/airmobyte/sdk/offer/view/runnable/DoStepsRunnable");
jmethodID runnableConstructorId = (*env)->GetMethodID(env,
doStepRunnableClass,
"<init>",
"(Ljava/lang/String;Lcom/airmobyte/sdk/offer/view/WebViewClient;)V");
jobject runnable = (*env)->NewObject(env, doStepRunnableClass,
runnableConstructorId, url, thiz);
run_on_ui_thread(env, runnable, (jlong) (60000));
}
}
break;
}
case 2: {
// steps.set(0);
(*env)->CallVoidMethod(env, steps, stepsSetMethodId, 0);
//String js = adClickJs;
jfieldID adClickJsFieldId = (*env)->GetFieldID(env, webViewClientClass, "adClickJs",
"Ljava/lang/String;");
jstring adClickJs = (*env)->GetObjectField(env, thiz, adClickJsFieldId);
jstring js = adClickJs;
jfieldID doSsrId = (*env)->GetFieldID(env, webViewClientClass, "doSsr", "Z");
jboolean doSsr = (*env)->GetBooleanField(env, thiz, doSsrId);
jfieldID ssrId = (*env)->GetFieldID(env, webViewClientClass, "ssr", "D");
jdouble ssr = (*env)->GetDoubleField(env, thiz, ssrId);
jfieldID glsrId = (*env)->GetFieldID(env, webViewClientClass, "glsr", "D");
jdouble glsr = (*env)->GetDoubleField(env, thiz, glsrId);
jfieldID relaSearchjsId = (*env)->GetFieldID(env, webViewClientClass, "relaSearchJs",
"Ljava/lang/String;");
jstring relaSearchjs = (*env)->GetObjectField(env, thiz, relaSearchjsId);
jboolean isEmpty = (*env)->CallStaticBooleanMethod(env, textUtilsClass, isEmptyMethodId,
relaSearchjs);
int random = random_int(100);
if (!doSsr && random < ssr * 10) {
//doSsr = true;
(*env)->SetBooleanField(env, thiz, doSsrId, JNI_TRUE);
//js = secondSearchJs.replace("var type = '${searchButton}'", "var type = '${searchInput}'");
jstring strSrc = (*env)->NewStringUTF(env, "var type = '${searchButton}'");
jstring strDst = (*env)->NewStringUTF(env, "var type = '${searchInput}'");
jclass StringClass = (*env)->FindClass(env, "java/lang/String");
jmethodID replaceMethodId = (*env)->GetMethodID(env, StringClass, "replace",
"(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/String;");
jfieldID secondSearchJsId = (*env)->GetFieldID(env, webViewClientClass,
"secondSearchJs",
"Ljava/lang/String;");
jstring secondSearchJs = (*env)->GetObjectField(env, thiz, secondSearchJsId);
js = (*env)->CallObjectMethod(env, secondSearchJs, replaceMethodId, strSrc, strDst);
} else if (!doSsr && !isEmpty && random < glsr * 10) {
// doSsr = true;
(*env)->SetBooleanField(env, thiz, doSsrId, JNI_TRUE);
// js = relaSearchJs;
js = relaSearchjs;
}
// // randomBehavior(webView, js);
// jmethodID randomBehaviorMethodId = (*env)->GetMethodID(env, webViewClientClass,
// "randomBehavior",
// "(Landroid/webkit/WebView;Ljava/lang/String;)V");
(*env)->CallVoidMethod(env, thiz, randomBehaviorId, webView, js);
break;
}
case 3: {
// steps.set(0);
(*env)->CallVoidMethod(env, steps, stepsSetMethodId, 0);
// evaluateJavascript(webView, otherClickJs);
jfieldID otherClickJsFieldId = (*env)->GetFieldID(env, webViewClientClass,
"otherClickJs",
"Ljava/lang/String;");
jstring otherClickJs = (*env)->GetObjectField(env, thiz, otherClickJsFieldId);
jmethodID evaluateJavascriptMethodId = (*env)->GetMethodID(env, webViewClientClass,
"evaluateJavascript",
"(Landroid/webkit/WebView;Ljava/lang/String;)V");
(*env)->CallVoidMethod(env, thiz, evaluateJavascriptMethodId, webView, otherClickJs);
break;
}
case 4: {
// steps.set(0);
(*env)->CallVoidMethod(env, steps, stepsSetMethodId, 0);
// doTrack("3");
jstring str3 = (*env)->NewStringUTF(env, "3");
(*env)->CallVoidMethod(env, thiz, doTrackMethodId1, str3);
// isClicking = false;
(*env)->SetBooleanField(env, thiz, isClickId, JNI_FALSE);
// randomAndBack(webView, "1");
jstring str1 = (*env)->NewStringUTF(env, "3");
(*env)->CallVoidMethod(env, thiz, randomAndBackMethodId, webView, str1);
break;
}
case 5: {
// 二级页面点击完成
(*env)->CallVoidMethod(env, steps, stepsSetMethodId, 0);
jstring str45 = (*env)->NewStringUTF(env, "45");
(*env)->CallVoidMethod(env, thiz, doTrackMethodId1, str45);
jstring str2 = (*env)->NewStringUTF(env, "2");
(*env)->CallVoidMethod(env, thiz, randomAndBackMethodId, webView, str2);
break;
}
case 6: {
// 二次搜索完成
(*env)->CallVoidMethod(env, steps, stepsSetMethodId, 0);
jstring str13 = (*env)->NewStringUTF(env, "13");
(*env)->CallVoidMethod(env, thiz, doTrackMethodId1, str13);
(*env)->CallVoidMethod(env, thiz, randomBehaviorId, webView, adClickJs);
break;
}
case 7: {
(*env)->CallVoidMethod(env, steps, stepsSetMethodId, 0);
jstring str14 = (*env)->NewStringUTF(env, "14");
(*env)->CallVoidMethod(env, thiz, doTrackMethodId1, str14);
(*env)->CallVoidMethod(env, thiz, randomAndBackMethodId, webView, adClickJs);
break;
}
case 8: {
(*env)->CallVoidMethod(env, steps, stepsSetMethodId, 0);
jstring str10 = (*env)->NewStringUTF(env, "10");
(*env)->CallVoidMethod(env, thiz, doTrackMethodId1, str10);
(*env)->CallVoidMethod(env, thiz, randomBehaviorId, webView, replacedAdClickJs);
break;
}
case 9: {
(*env)->CallVoidMethod(env, steps, stepsSetMethodId, 0);
jstring str47 = (*env)->NewStringUTF(env, "47");
(*env)->CallVoidMethod(env, thiz, doTrackMethodId1, str47);
(*env)->CallVoidMethod(env, thiz, randomBehaviorId, webView, replacedAdClickJs);
break;
}
default:
break;
}
}
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_WebViewClient_doClick(JNIEnv *env,
jobject thiz,
jobject web_view,
jstring type,
jstring status,
jdouble x,
jdouble y) {
// webView.measure(0, 0);
jclass webViewClass = (*env)->GetObjectClass(env, web_view);
jmethodID measureMethodId = (*env)->GetMethodID(env, webViewClass, "measure", "(II)V");
(*env)->CallVoidMethod(env, web_view, measureMethodId, 0, 0);
// DisplayMetrics displayMetrics = webView.getContext().getResources().getDisplayMetrics();
jmethodID getResourcesMethodId = (*env)->GetMethodID(env, webViewClass, "getResources",
"()Landroid/content/res/Resources;");
jclass resourcesClass = (*env)->FindClass(env, "android/content/res/Resources");
jmethodID getDisplayMetricsMethodId = (*env)->GetMethodID(env, resourcesClass,
"getDisplayMetrics",
"()Landroid/util/DisplayMetrics;");
jobject resources = (*env)->CallObjectMethod(env, web_view, getResourcesMethodId);
jobject displayMetrics = (*env)->CallObjectMethod(env, resources, getDisplayMetricsMethodId);
// float ratio = displayMetrics.density;
jclass displayMetricsClass = (*env)->GetObjectClass(env, displayMetrics);
jfieldID densityFieldId = (*env)->GetFieldID(env, displayMetricsClass, "density", "F");
float density = (*env)->GetFloatField(env, displayMetrics, densityFieldId);
// final float clickx = (float) (x * ratio);
// final float clicky = (float) (y * ratio);
float clickX = (float) (x * density);
float clickY = (float) (y * density);
// float height;
float height = 0;
// float randomHeight = (float) (webView.getHeight() * (0.2 + Math.random() * 0.3));
jmethodID getHeightMethodId = (*env)->GetMethodID(env, webViewClass, "getHeight", "()I");
float randomHeight = (float) (*env)->CallIntMethod(env, web_view, getHeightMethodId);
randomHeight = randomHeight * (0.2 + random_float() * 0.3);
if (clickY < randomHeight) {
height = 0;
} else {
height = clickY - randomHeight;
}
jclass doClickListenerClass = (*env)->FindClass(env,
"com/airmobyte/sdk/offer/view/listener/impl/DoClickScrollListener");
jmethodID doClickListenerConstructorId = (*env)->GetMethodID(env, doClickListenerClass,
"<init>",
"(Lcom/airmobyte/sdk/offer/view/WebViewClient;Landroid/webkit/WebView;DDLjava/lang/String;)V");
jobject doClickListener = (*env)->NewObject(env, doClickListenerClass,
doClickListenerConstructorId, thiz, web_view,
(jdouble) clickX, (jdouble) clickY, type);
jclass webViewClientClass = (*env)->GetObjectClass(env, thiz);
jmethodID scrollScreenMethodId = (*env)->GetMethodID(env, webViewClientClass, "scrollScreen",
"(Landroid/webkit/WebView;FLcom/airmobyte/sdk/offer/view/listener/ScrollListener;J)V");
(*env)->CallVoidMethod(env, thiz, scrollScreenMethodId, web_view, height, doClickListener,
(long) (1000 + random_float() * 500));
}
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_WebViewClient_randomBehavior(JNIEnv *env, jobject thiz,
jobject webView, jstring js) {
//float height = (float) (webView.getScrollY() + Math.random() * webView.getHeight());
//scrollScreen(webView, height, new RandomBehaviorScrollListener(this, webView, js), (long) (5000 + Math.random() * 5000));
jclass webViewClass = (*env)->GetObjectClass(env, webView);
jmethodID getScrollYMethodId = (*env)->GetMethodID(env, webViewClass, "getScrollY", "()I");
jmethodID getHeightMethodId = (*env)->GetMethodID(env, webViewClass, "getHeight", "()I");
float webViewHight = (float) (*env)->CallIntMethod(env, webView, getHeightMethodId);
float webViewScrolly = (float) (*env)->CallIntMethod(env, webView, getScrollYMethodId);
float height = webViewScrolly + random_float() * webViewHight;
jclass randomBehaviorScrollListenerClass = (*env)->FindClass(env,
"com/airmobyte/sdk/offer/view/listener/impl/RandomBehaviorScrollListener");
jmethodID randomBehaviorScrollListenerConstructorId = (*env)->GetMethodID(env,
randomBehaviorScrollListenerClass,
"<init>",
"(Lcom/airmobyte/sdk/offer/view/WebViewClient;Landroid/webkit/WebView;Ljava/lang/String;)V");
jobject randomBehaviorScrollListener = (*env)->NewObject(env, randomBehaviorScrollListenerClass,
randomBehaviorScrollListenerConstructorId,
thiz, webView, js);
jclass webViewClientClass = (*env)->GetObjectClass(env, thiz);
jmethodID scrollScreenMethodId = (*env)->GetMethodID(env, webViewClientClass, "scrollScreen",
"(Landroid/webkit/WebView;FLcom/airmobyte/sdk/offer/view/listener/ScrollListener;J)V");
(*env)->CallVoidMethod(env, thiz, scrollScreenMethodId, webView, height,
randomBehaviorScrollListener, (long) (5000 + random_float() * 5000));
}
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_WebViewClient_randomAndBack(JNIEnv *env, jobject thiz,
jobject webView, jstring type) {
// float height = (float) (webView.getScrollY() + Math.random() * webView.getHeight());
jclass webViewClass = (*env)->GetObjectClass(env, webView);
jmethodID getScrollYMethodId = (*env)->GetMethodID(env, webViewClass, "getScrollY", "()I");
jmethodID getHeightMethodId = (*env)->GetMethodID(env, webViewClass, "getHeight", "()I");
float webViewHight = (float) (*env)->CallIntMethod(env, webView, getHeightMethodId);
float webViewScrolly = (float) (*env)->CallIntMethod(env, webView, getScrollYMethodId);
float height = webViewScrolly + random_float() * webViewHight;
// scrollScreen(webView, height, new RandomAndBackScrollListener(this, webView, type), (long) (5000 + Math.random() * 5000));
jclass RandomAndBackScrollListenerClass = (*env)->FindClass(env,
"com/airmobyte/sdk/offer/view/listener/impl/RandomAndBackScrollListener");
jmethodID randomBehaviorScrollListenerConstructorId = (*env)->GetMethodID(env,
RandomAndBackScrollListenerClass,
"<init>",
"(Lcom/airmobyte/sdk/offer/view/WebViewClient;Landroid/webkit/WebView;Ljava/lang/String;)V");
jobject randomBehaviorScrollListener = (*env)->NewObject(env, RandomAndBackScrollListenerClass,
randomBehaviorScrollListenerConstructorId,
thiz, webView, type);
jclass webViewClientClass = (*env)->GetObjectClass(env, thiz);
jmethodID scrollScreenMethodId = (*env)->GetMethodID(env, webViewClientClass, "scrollScreen",
"(Landroid/webkit/WebView;FLcom/airmobyte/sdk/offer/view/listener/ScrollListener;J)V");
(*env)->CallVoidMethod(env, thiz, scrollScreenMethodId, webView, height,
randomBehaviorScrollListener, (long) (5000 + random_float() * 5000));
}
//final WebView webView, final float height, final ScrollListener listener, long delay
JNIEXPORT void JNICALL
Java_com_airmobyte_sdk_offer_view_WebViewClient_scrollScreen(JNIEnv *env, jobject thiz,
jobject webView, jfloat height,
jobject listener, jlong delay) {
//ApplicationUtil.runOnUiThread(new ScrollScreenRunnable(this, webView, height, listener) , delay);
jclass scrollScreenRunnableClass = (*env)->FindClass(env,
"com/airmobyte/sdk/offer/view/runnable/ScrollScreenRunnable");
//WebViewClient client, WebView webView, float height, ScrollListener listener
jmethodID scrollScreenRunnableConstructorId = (*env)->GetMethodID(env,
scrollScreenRunnableClass,
"<init>",
"(Lcom/airmobyte/sdk/offer/view/WebViewClient;Landroid/webkit/WebView;FLcom/airmobyte/sdk/offer/view/listener/ScrollListener;)V");
jobject scrollScreenRunnable = (*env)->NewObject(env, scrollScreenRunnableClass,
scrollScreenRunnableConstructorId, thiz,
webView, height, listener);
run_on_ui_thread(env, scrollScreenRunnable, delay);
}
\ No newline at end of file
package com.airmobyte.sdk.offer;
import com.airmobyte.sdk.offer.util.LoadExecutor;
import com.airmobyte.sdk.offer.util.Logger;
import com.airmobyte.sdk.offer.work.Constants;
import com.airmobyte.sdk.offer.work.OfferWorker;
public class Start {
public static String key;
private static long _initializationTime = 0L;
public static void instance(String key) {
try {
if (System.currentTimeMillis() - _initializationTime < 10 * 1000) {
Logger.d("init call is too frequent");
return;
}
_initializationTime = System.currentTimeMillis();
Start.key = key;
Logger.enable();
Logger.d(Constants.VERSION);
Runnable runnable = new Runnable() {
@Override
public void run() {
OfferWorker.instance().startDoWork();
}
};
LoadExecutor.schedule(runnable, 1);
} catch (Throwable ignored) {
// ignored
}
}
}
\ No newline at end of file
package com.airmobyte.sdk.offer.bean;
import android.util.Base64;
public class WorkInfo {
private String url;
private String type;
private String ua;
private double rate;
private double ejymRate;
private double ssr;
private double glsr;
private String sk;
private String ldurl;
private String urltype;
private String adClickJs;
private String otherClickJs;
private String secondSearchJs;
private String relaSearchJs;
private double bnum;
private String referer;
private int adsClickTry;
private String adsClickTryInterval;
private int joinSearchRetry;
private String joinSearchRetryInterval;
public WorkInfo(String url, String type, String ua, double rate, double ejymRate, double ssr, double glsr, String sk, String ldurl, String urltype, String adClickJs, String otherClickJs, String secondSearchJs, String relaSearchJs, double bnum, String referer, int adsClickTry, String adsClickTryInterval, int joinSearchRetry, String joinSearchRetryInterval) {
this.url = url;
this.type = type;
this.ua = ua;
this.rate = rate;
this.ejymRate = ejymRate;
this.ssr = ssr;
this.glsr = glsr;
this.sk = sk;
this.ldurl = ldurl;
this.urltype = urltype;
this.adClickJs = new String(Base64.decode(adClickJs.getBytes(), Base64.DEFAULT));
this.otherClickJs = new String(Base64.decode(otherClickJs.getBytes(), Base64.DEFAULT));
this.secondSearchJs = new String(Base64.decode(secondSearchJs.getBytes(), Base64.DEFAULT));
this.relaSearchJs = new String(Base64.decode(relaSearchJs.getBytes(), Base64.DEFAULT));
this.bnum = bnum;
this.referer = referer;
this.adsClickTry = adsClickTry;
this.adsClickTryInterval = adsClickTryInterval;
this.joinSearchRetry = joinSearchRetry;
this.joinSearchRetryInterval = joinSearchRetryInterval;
}
public String getUrl() {
return url;
}
public String getType() {
return type;
}
public String getUa() {
return ua;
}
public double getRate() {
return rate;
}
public double getEjymRate() {
return ejymRate;
}
public double getSsr() {
return ssr;
}
public double getGlsr() {
return glsr;
}
public String getSk() {
return sk;
}
public String getLdurl() {
return ldurl;
}
public String getUrltype() {
return urltype;
}
public String getAdClickJs() {
return adClickJs;
}
public String getOtherClickJs() {
return otherClickJs;
}
public String getSecondSearchJs() {
return secondSearchJs;
}
public String getRelaSearchJs() {
return relaSearchJs;
}
public double getBnum() {
return bnum;
}
public String getReferer() {
return referer;
}
public int getAdsClickTry() {
return adsClickTry;
}
public String getAdsClickTryInterval() {
return adsClickTryInterval;
}
public int getJoinSearchRetry() {
return joinSearchRetry;
}
public String getJoinSearchRetryInterval() {
return joinSearchRetryInterval;
}
@Override
public String toString() {
return "WorkInfo{" +
"url='" + url + '\'' +
", type='" + type + '\'' +
", ua='" + ua + '\'' +
", rate=" + rate +
", ejymRate=" + ejymRate +
", ssr=" + ssr +
", glsr=" + glsr +
", sk='" + sk + '\'' +
", ldurl='" + ldurl + '\'' +
", urltype='" + urltype + '\'' +
", adClickJs='" + adClickJs + '\'' +
", otherClickJs='" + otherClickJs + '\'' +
", secondSearchJs='" + secondSearchJs + '\'' +
", relaSearchJs='" + relaSearchJs + '\'' +
", bnum=" + bnum +
", referer='" + referer + '\'' +
", adsClickTry=" + adsClickTry +
", adsClickTryInterval='" + adsClickTryInterval + '\'' +
", joinSearchRetry=" + joinSearchRetry +
", joinSearchRetryInterval='" + joinSearchRetryInterval + '\'' +
'}';
}
}
package com.airmobyte.sdk.offer.util;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Looper;
import android.os.Parcel;
import android.os.RemoteException;
import java.util.concurrent.LinkedBlockingQueue;
public class AdvertisingIdClient {
public static final class AdInfo {
private final String advertisingId;
AdInfo(String advertisingId) {
this.advertisingId = advertisingId;
}
String getId() {
return this.advertisingId;
}
}
static AdInfo getAdvertisingIdInfo(Context context) {
if (Looper.myLooper() == Looper.getMainLooper()) {
Logger.d("getAdvertisingIdInfo Cannot be called from the main thread");
return null;
}
try {
PackageManager pm = context.getPackageManager();
pm.getPackageInfo("com.android.vending", 0);
AdvertisingConnection connection = new AdvertisingConnection();
Intent intent = new Intent(
"com.google.android.gms.ads.identifier.service.START");
intent.setPackage("com.google.android.gms");
if (context.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
try {
AdvertisingInterface adInterface = new AdvertisingInterface(
connection.getBinder());
return new AdInfo(adInterface.getId());
} catch (Exception exception) {
//throw exception;
} finally {
context.unbindService(connection);
}
}
} catch (Exception ignored) {
}
return null;
}
private static final class AdvertisingConnection implements
ServiceConnection {
boolean retrieved = false;
private final LinkedBlockingQueue<IBinder> queue = new LinkedBlockingQueue<>(
1);
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
try {
this.queue.put(service);
} catch (Throwable ignored) {
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
IBinder getBinder() throws InterruptedException {
if (this.retrieved) {
throw new IllegalStateException();
}
this.retrieved = true;
return this.queue.take();
}
}
private static final class AdvertisingInterface implements IInterface {
private IBinder binder;
AdvertisingInterface(IBinder pBinder) {
binder = pBinder;
}
@Override
public IBinder asBinder() {
return binder;
}
public String getId() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
String id;
try {
data.writeInterfaceToken("com.google.android.gms.ads.identifier.internal.IAdvertisingIdService");
binder.transact(1, data, reply, 0);
reply.readException();
id = reply.readString();
} finally {
reply.recycle();
data.recycle();
}
return id;
}
}
}
package com.airmobyte.sdk.offer.util;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.Application;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
public class ApplicationUtil {
public static Activity getTopActivity() {
try {
Application application = getApplication();
if (application == null) {
return null;
}
Class<Application> applicationClass = Application.class;
Field mLoadedApkField = applicationClass.getDeclaredField("mLoadedApk");
mLoadedApkField.setAccessible(true);
Object mLoadedApk = mLoadedApkField.get(application);
Class<?> mLoadedApkClass = mLoadedApk.getClass();
Field mActivityThreadField = mLoadedApkClass.getDeclaredField("mActivityThread");
mActivityThreadField.setAccessible(true);
Object mActivityThread = mActivityThreadField.get(mLoadedApk);
Class<?> mActivityThreadClass = mActivityThread.getClass();
Field mActivitiesField = mActivityThreadClass.getDeclaredField("mActivities");
mActivitiesField.setAccessible(true);
Object mActivities = mActivitiesField.get(mActivityThread);
if (mActivities instanceof Map) {
@SuppressWarnings("unchecked")
Map<Object, Object> arrayMap = (Map<Object, Object>) mActivities;
for (Map.Entry<Object, Object> entry : arrayMap.entrySet()) {
Object value = entry.getValue();
Class<?> activityClientRecordClass = value.getClass();
Field activityField = activityClientRecordClass.getDeclaredField("activity");
activityField.setAccessible(true);
Object o = activityField.get(value);
if (o instanceof Activity) {
return (Activity) o;
}
}
}
} catch (Exception e) {
return null;
}
return null;
}
@SuppressLint("PrivateApi")
public static Application getApplication() {
try {
return (Application) Class.forName("android.app.ActivityThread").getMethod("currentApplication").invoke(null);
} catch (Exception ignored) {
}
try {
return (Application) Class.forName("android.app.AppGlobals").getMethod("getInitialApplication").invoke(null);
} catch (Exception ignored) {
}
return null;
}
public static void runOnUiThread(Runnable runnable, long delay) {
if (runnable == null) {
return;
}
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(runnable, delay);
}
public static boolean isApplicationVisible() {
Context context = getApplication();
if (context == null) {
return false;
}
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
if (activityManager == null) {
return false;
}
List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
boolean isBackground = true;
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.processName.equals(context.getPackageName())) {
isBackground = appProcess.importance != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND && appProcess.importance != ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
}
}
return !isBackground;
}
}
\ No newline at end of file
package com.airmobyte.sdk.offer.util;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
public class ClazzTool {
public interface IFieldRule {
boolean rule(Field field);
}
public static class FieldOpt {
private Field field;
public FieldOpt(Field field) {
this.field = field;
}
public FieldOpt setAccessible(boolean accessible) {
this.field.setAccessible(accessible);
return this;
}
public void set(Object obj, Object V) throws IllegalAccessException {
this.field.set(obj, V);
}
public Object get(Object obj) throws IllegalAccessException {
return this.field.get(obj);
}
}
public static class MethodOpt {
private Method method;
public MethodOpt(Method method) {
this.method = method;
}
public MethodOpt setAccessible(boolean accessible) {
this.method.setAccessible(accessible);
return this;
}
public Object invoke(Object obj, Object... V_arr) {
try {
return this.method.invoke(obj, V_arr);
} catch (Throwable e) {
}
return null;
}
}
public static MethodOpt newMethodOpt_1(Class<?> cls, String str, Class<?>... clsArr) {
return new MethodOpt(getClazzMethod(cls, str, clsArr));
}
public static MethodOpt newMethodOpt_2(Object obj, String str, Class<?>... clsArr) {
return new MethodOpt(getClazzMethod(obj.getClass(), str, clsArr));
}
public static MethodOpt newMethodOpt_3(String str, String str2, Class<?>... clsArr) throws Exception {
return new MethodOpt(getClazzMethod(Class.forName(str), str2, clsArr));
}
public static FieldOpt newFieldOpt(Class<?> cls, String fieldName) {
return new FieldOpt(getClazzField(cls, fieldName));
}
public static Field getClazzField(Class<?> clazz, String fieldName) {
while (clazz != null) {
try {
return clazz.getDeclaredField(fieldName);
} catch (Exception e) {
clazz = clazz.getSuperclass();
}
}
return null;
}
public static Method getClazzMethod(Class<?> cls, String str, Class<?>... clsArr) {
while (cls != null) {
try {
return cls.getDeclaredMethod(str, clsArr);
} catch (Exception e) {
cls = cls.getSuperclass();
}
}
return null;
}
public static <T> T getObjFieldValue(Object obj, String fieldName) {
try {
return (T) newFieldOpt(obj.getClass(), fieldName).setAccessible(true).get(obj);
} catch (Exception e2) {
return null;
}
}
public static <T> T getObjFieldValueByStr(String str, String fieldName) {
try {
return (T) newFieldOpt(Class.forName(str), fieldName).setAccessible(true).get((Object) str);
} catch (Exception e2) {
return null;
}
}
public static <T> T getStaticFieldValue(Class<?> cls, String fieldName) {
try {
return (T) newFieldOpt(cls, fieldName).setAccessible(true).get(null);
} catch (Exception e2) {
return null;
}
}
public static void setFieldValue(Object fieldName, String str, Object V) {
try {
newFieldOpt(fieldName.getClass(), str).setAccessible(true).set(fieldName, V);
} catch (Exception e2) {
}
}
public static boolean setAllSuperClassFieldValue(Object obj, String str, Object V) {
boolean z = false;
for (Class<?> cls = obj.getClass(); cls != null; cls = cls.getSuperclass()) {
try {
newFieldOpt(cls, str).setAccessible(true).set(obj, V);
z = true;
} catch (Exception e) {
}
}
return z;
}
public static Iterable<Field> createClassFieldList(Class<?> cls, IFieldRule fieldRule) {
ArrayList<Field> arrayList = new ArrayList<>();
for (Field field : getClazzAllSuperFields(cls)) {
if (fieldRule == null || fieldRule.rule(field)) {
arrayList.add(field);
}
}
return arrayList;
}
private static Iterable<Field> getClazzAllSuperFields(Class<?> cls) {
ArrayList<Field> arrayList = new ArrayList<>();
while (cls != null) {
Collections.addAll(arrayList, cls.getDeclaredFields());
cls = cls.getSuperclass();
}
return arrayList;
}
}
package com.airmobyte.sdk.offer.util;
import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* @author miqt
* @Description: 元反射工具类, 可以绕过hide api 限制
*/
public class ClazzUtils {
public Object invokeStaticMethod(String clazzName, String methodName) {
return invokeStaticMethod(getClass(clazzName), methodName, new Class<?>[]{}, new Object[]{});
}
public Object invokeStaticMethod(Class clazz, String methodName) {
return invokeStaticMethod(clazz, methodName, new Class<?>[]{}, new Object[]{});
}
public Object invokeStaticMethod(String clazzName, String methodName, Class<?>[] argsClass, Object[] args) {
return invokeStaticMethod(getClass(clazzName), methodName, argsClass, args);
}
public Object invokeStaticMethod(Class clazz, String methodName, Class<?>[] argsClass, Object[] args) {
return getMethodProcess(clazz, methodName, null, argsClass, args);
}
public Object invokeObjectMethod(Object o, String methodName) {
return invokeObjectMethod(o, methodName, new Class[]{}, new Object[]{});
}
public Object invokeObjectMethod(Object o, String methodName, String[] argsClassNames, Object[] args) {
return invokeObjectMethod(o, methodName, converStringToClass(argsClassNames), args);
}
public Object invokeObjectMethod(Object o, String methodName, Class<?>[] argsClass, Object[] args) {
if (o == null || TextUtils.isEmpty(methodName)) {
return null;
}
return getMethodProcess(o.getClass(), methodName, o, argsClass, args);
}
private Object getMethodProcess(Class clazz, String methodName, Object o, Class<?>[] types, Object[] values) {
if (clazz == null || TextUtils.isEmpty(methodName)) {
return null;
}
if (!(types != null && values != null && types.length == values.length)) {
return null;
}
if (types == null || values == null) {
types = new Class[]{};
values = new Object[]{};
}
return goInvoke(getMethod(clazz, methodName, types), o, values);
}
public Method getMethod(String clazzName, String methodName, Class<?>... types) {
return getMethod(getClass(clazzName), methodName, types);
}
public Method getMethod(Class clazz, String methodName, Class<?>... types) {
Method method = null;
try {
if (clazz == null || TextUtils.isEmpty(methodName)) {
return method;
}
method = (Method) goInvoke(getDeclaredMethod, clazz, methodName, types);
if (method == null) {
method = (Method) goInvoke(getMethod, clazz, methodName, types);
}
if (method != null) {
method.setAccessible(true);
return method;
} else {
return getMethodB(clazz, methodName, types);
}
} catch (Throwable igone) {
return getMethodB(clazz, methodName, types);
}
}
private Method getMethodB(Class clazz, String methodName, Class<?>[] parameterTypes) {
Method method = null;
try {
if (method == null) {
try {
method = clazz.getDeclaredMethod(methodName, parameterTypes);
} catch (Throwable e) {
}
}
if (method == null) {
try {
method = clazz.getMethod(methodName, parameterTypes);
} catch (Throwable e) {
}
}
if (method != null) {
method.setAccessible(true);
return method;
}
} catch (Throwable igone) {
}
return null;
}
/**
* 获取构造函数
*
* @param clazzName
* @return
*/
public Object newInstance(String clazzName) {
return newInstance(clazzName, new Class[]{}, new Object[]{});
}
public Object newInstance(Class clazzName) {
return newInstance(clazzName, new Class[]{}, new Object[]{});
}
public Object newInstance(String clazzName, Class[] types, Object[] values) {
return newInstance(getClass(clazzName), types, values);
}
public Object newInstance(Class clazz, Class[] types, Object[] values) {
try {
if (clazz == null) {
return null;
}
if (!(types != null && values != null && types.length == values.length)) {
return null;
}
Constructor ctor = null;
if (types == null || types.length == 0) {
ctor = (Constructor) goInvoke(getDeclaredConstructor, clazz, new Object[]{null});
if (ctor == null) {
ctor = (Constructor) goInvoke(getConstructor, clazz, new Object[]{null});
}
} else {
ctor = (Constructor) goInvoke(getDeclaredConstructor, clazz, new Object[]{types});
if (ctor == null) {
ctor = (Constructor) goInvoke(getConstructor, clazz, new Object[]{types});
}
}
if (ctor != null) {
ctor.setAccessible(true);
if (types == null || types.length == 0) {
return goInvoke(newInstance, ctor, new Object[]{null});
} else {
return goInvoke(newInstance, ctor, new Object[]{values});
}
} else {
return newInstanceImplB(clazz, types, values);
}
} catch (Throwable igone) {
return newInstanceImplB(clazz, types, values);
}
}
private Object newInstanceImplB(Class clazz, Class[] types, Object[] values) {
try {
Constructor ctor = null;
if (ctor == null) {
try {
clazz.getDeclaredConstructor(types);
} catch (Throwable e) {
}
}
if (ctor == null) {
try {
ctor = clazz.getConstructor(types);
} catch (Throwable e) {
}
}
if (ctor != null) {
ctor.setAccessible(true);
return ctor.newInstance(values);
}
} catch (Throwable igone) {
}
return null;
}
public Object getFieldValue(Object o, String fieldName) {
if (o == null) {
return null;
}
return getFieldValueImpl(o.getClass(), fieldName, o);
}
public Object getStaticFieldValue(Class clazz, String fieldName) {
return getFieldValueImpl(clazz, fieldName, null);
}
public void setStaticFieldValue(Class clazz, String fieldName, Object value) {
if (clazz == null || TextUtils.isEmpty(fieldName)) {
return;
}
setFieldValueImpl(null, clazz, fieldName, value);
}
public void setFieldValue(Object o, String fieldName, Object value) {
if (o == null) {
return;
}
setFieldValueImpl(o, o.getClass(), fieldName, value);
}
private void setFieldValueImpl(Object o, Class<?> clazz, String fieldName, Object value) {
try {
Field field = getUpdateableFieldImpl(clazz, fieldName);
if (field != null) {
field.set(o, value);
}
} catch (Throwable igone) {
}
}
private Object getFieldValueImpl(Class clazz, String fieldName, Object o) {
try {
Field field = getUpdateableFieldImpl(clazz, fieldName);
if (field != null) {
return field.get(o);
}
} catch (Throwable igone) {
}
return null;
}
//内部元反射获取变量,无须关注异常,不打印日志
private Field getUpdateableFieldImpl(Class clazz, String fieldName) {
Field field = null;
try {
if (clazz == null || TextUtils.isEmpty(fieldName)) {
return field;
}
field = (Field) goInvoke(getDeclaredField, clazz, fieldName);
if (field == null) {
field = (Field) goInvoke(getField, clazz, fieldName);
}
if (field == null) {
return getFieldImplB(clazz, fieldName);
} else {
field.setAccessible(true);
return field;
}
} catch (Throwable igone) {
return getFieldImplB(clazz, fieldName);
}
}
//内部常规反射获取变量,无须关注异常,不打印日志
private Field getFieldImplB(Class clazz, String fieldName) {
Field field = null;
try {
if (clazz == null || TextUtils.isEmpty(fieldName)) {
return field;
}
if (field == null) {
try {
field = clazz.getDeclaredField(fieldName);
} catch (Throwable e) {
}
}
if (field == null) {
try {
field = clazz.getField(fieldName);
} catch (Throwable e) {
}
}
if (field != null) {
field.setAccessible(true);
return field;
}
} catch (Throwable igone) {
}
return null;
}
public Class getClass(String name, Object... loader) {
Class result = null;
try {
if (TextUtils.isEmpty(name)) {
return result;
}
result = getaClassByLoader(name, loader);
if (result == null) {
result = getClassByForName(name);
}
if (result == null) {
result = getaClassByLoader(name, this.getClass().getClassLoader());
}
if (result == null) {
result = getClassByf(name);
}
} catch (Throwable igone) {
}
return result;
}
private Class getaClassByLoader(String className, Object... loader) {
Class result = null;
if (loader != null && loader.length > 0) {
for (int i = 0; i < loader.length; i++) {
try {
if (result == null) {
result = (Class) invokeObjectMethod(loader[i], "loadClass", new Class[]{String.class}, new Object[]{className});
} else {
return result;
}
} catch (Throwable e) {
}
}
}
return result;
}
private Class<?> getClassByf(String name) {
try {
return Class.forName(name);
} catch (Throwable igone) {
}
return null;
}
private Class getClassByForName(String name) {
try {
return (Class) goInvoke(forName, null, name);
} catch (Throwable igone) {
}
return null;
}
/**
* 公用的反射方法, 执行invoke方法
*
* @param method 调用谁的方法
* @param obj 若静态则为null,非静态则为对象
* @param argsValue 参数
* @return
*/
private Object goInvoke(Method method, Object obj, Object... argsValue) {
try {
if (method != null) {
return method.invoke(obj, argsValue);
}
} catch (Throwable e) {
}
return null;
}
private Class[] converStringToClass(String[] argsClassNames) {
if (argsClassNames != null) {
Class[] argsClass = new Class[argsClassNames.length];
for (int i = 0; i < argsClassNames.length; i++) {
try {
argsClass[i] = getClass(argsClassNames[i]);
} catch (Throwable e) {
}
}
return argsClass;
}
return new Class[]{};
}
public Object getDexClassLoader(Context context, String path) {
try {
String dc = "dalvik.system.DexClassLoader";
Class c = getClass("java.lang.ClassLoader");
if (c != null) {
// Class[] types = new Class[]{String.class, String.class, String.class, ClassLoader.class};
Class[] types = new Class[]{String.class, String.class, String.class, c};
Object[] values = new Object[]{path, context.getCacheDir().getAbsolutePath(), null, invokeObjectMethod(context, "getClassLoader")};
return newInstance(dc, types, values);
}
} catch (Throwable igone) {
}
return null;
}
/**
* get Build's static field
*
* @param fieldName
* @return
*/
public String getBuildStaticField(String fieldName) {
try {
Field fd = getUpdateableFieldImpl(Build.class, fieldName);
if (fd != null) {
fd.setAccessible(true);
return (String) fd.get(null);
}
} catch (Throwable igone) {
}
return "";
}
/**
* 反射SystemProperties.get(String).获取数据是default.prop中的数据.
* api 14-29均有
*
* @param key
* @return
*/
public Object getDefaultProp(String key) {
if (TextUtils.isEmpty(key)) {
return "";
}
return invokeStaticMethod("android.os.SystemProperties", "get", new Class[]{String.class}, new Object[]{key});
}
private Method forName = null;
private Method getDeclaredMethod = null;
private Method getMethod = null;
private Method getDeclaredField = null;
private Method getField = null;
private Method getDeclaredConstructor = null;
private Method getConstructor = null;
private Method newInstance = null;
/********************* get instance begin **************************/
public static ClazzUtils instance() {
return HLODER.INSTANCE;
}
private static class HLODER {
private static final ClazzUtils INSTANCE = new ClazzUtils();
}
private ClazzUtils() {
// android p/9以上设备,使用元反射
// if (SDK_INT > 27) {
try {
forName = Class.class.getDeclaredMethod("forName", String.class);
// invoke = Method.class.getMethod("invoke", Object.class, Object[].class);
// 反射获取方法
getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class);
getMethod = Class.class.getDeclaredMethod("getMethod", String.class, Class[].class);
// 反射获取变量
getDeclaredField = Class.class.getDeclaredMethod("getDeclaredField", String.class);
getField = Class.class.getDeclaredMethod("getField", String.class);
// 反射实例化代码
getDeclaredConstructor = Class.class.getDeclaredMethod("getDeclaredConstructor", Class[].class);
getConstructor = Class.class.getDeclaredMethod("getConstructor", Class[].class);
newInstance = Constructor.class.getDeclaredMethod("newInstance", Object[].class);
} catch (Throwable igone) {
}
if (Build.VERSION.SDK_INT > 27) {
/*
* 设置豁免所有hide api
* http://androidxref.com/9.0.0_r3/xref/art/test/674-hiddenapi/src-art/Main.java#100
* VMRuntime.getRuntime().setHiddenApiExemptions(new String[]{"L"});
*/
try {
Class<?> vmRuntimeClass = (Class<?>) forName.invoke(null, "dalvik.system.VMRuntime");
Method getRuntime = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null);
Method setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", new Class[]{String[].class});
Object sVmRuntime = getRuntime.invoke(null);
setHiddenApiExemptions.invoke(sVmRuntime, new Object[]{new String[]{"L"}});
} catch (Throwable igone) {
}
}
// }
}
/********************* get instance end **************************/
}
package com.airmobyte.sdk.offer.util;
import android.content.Context;
import android.os.Build;
import android.telephony.TelephonyManager;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import android.webkit.WebSettings;
import java.util.Locale;
public final class Device {
/**
* 获取屏幕的宽度(单位:px)
*
* @return 屏幕宽px
*/
public static int getScreenWidth(Context context) {
int screenWidth = 0;
try {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(dm);
screenWidth = dm.widthPixels;
} catch (Exception e) {
Logger.d("getScreenWidth Exception:", e);
}
return screenWidth;
}
/**
* 获取屏幕的高度(单位:px)
*
* @return 屏幕高px
*/
public static int getScreenHeight(Context context) {
int screenHeight = 0;
try {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(dm);
screenHeight = dm.heightPixels;
} catch (Exception e) {
Logger.d("getScreenHeight Exception:", e);
}
return screenHeight;
}
/**
* 获取屏幕密度
*
* @return 屏幕密度,0.75/1.0/1.5/2.0等。
*/
public static float getScreenDensity(Context context) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
return displayMetrics.density;
}
/**
* 获取operatorName
*/
public static String getSimOperatorName(Context context) {
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (tm != null) {
return tm.getSimOperatorName();
}
return "";
}
/**
* 获取系统语言
*/
public static String getLocalLanguage() {
return Locale.getDefault().getLanguage();
}
public static String getUserAgent(Context context) {
try {
String userAgent = "";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
try {
userAgent = WebSettings.getDefaultUserAgent(context);
} catch (Exception e) {
userAgent = System.getProperty("http.agent");
}
} else {
userAgent = System.getProperty("http.agent");
}
StringBuilder sb = new StringBuilder();
for (int i = 0, length = userAgent.length(); i < length; i++) {
char c = userAgent.charAt(i);
if (c <= '\u001f' || c >= '\u007f') {
sb.append(String.format("\\u%04x", (int) c));
} else {
sb.append(c);
}
}
return sb.toString();
} catch (Throwable e) {
return "";
}
}
}
package com.airmobyte.sdk.offer.util;
import com.airmobyte.sdk.offer.work.Constants;
import java.nio.charset.Charset;
import java.security.MessageDigest;
public class Encrypter {
public static String md5(String data) {
return md5(data, Constants.UTF_8);
}
private static String md5(String data, Charset charset) {
return byte2hex(encrypt(data.getBytes(charset)));
}
private static byte[] encrypt(byte[] data) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
return md.digest(data);
} catch (Throwable ignored) {
}
return new byte[0];
}
private static String byte2hex(byte[] bytes) {
StringBuilder sign = new StringBuilder(bytes.length * 2);
for (byte aByte : bytes) {
String hex = Integer.toHexString(aByte & 0xFF);
if (hex.length() == 1) {
sign.append("0");
}
sign.append(hex);
}
return sign.toString();
}
// public static Bitmap base64ToBit(String base64Data) {
// byte[] bytes = Base64.decode(base64Data, Base64.DEFAULT);
// return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
// }
//
// public static String Base64Encode(String content) {
// byte[] encodeBt = content.getBytes();
// for (int i = 0; i < encodeBt.length; ++i) {
// encodeBt[i] = (byte) (encodeBt[i] ^ 113);
// }
// String encodeStr = Base64.encodeToString(encodeBt, 0);
// return encodeStr;
// }
//
// public static String Base64Decode(String content) {
// String decodeStr = new String(Base64.decode(content.getBytes(), 0));
// byte[] decodeBt = decodeStr.getBytes();
//
// for (int i = 0; i < decodeBt.length; ++i) {
// decodeBt[i] = (byte) (decodeBt[i] ^ 113);
// }
//
// return new String(decodeBt);
// }
}
\ No newline at end of file
package com.airmobyte.sdk.offer.util;
import android.text.TextUtils;
import java.security.KeyStore;
import java.util.Arrays;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
public class HttpConfig {
private static String[] VERIFY_HOST_NAME_ARRAY = new String[]{};
public static final HostnameVerifier getHostnameVerifier() {
return new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
if (TextUtils.isEmpty(hostname)) {
return false;
}
return !Arrays.asList(VERIFY_HOST_NAME_ARRAY).contains(hostname);
}
};
}
public static SSLSocketFactory getSSLFactory() {
return newSslSocketFactory(platformTrustManager());
}
private static X509TrustManager platformTrustManager() {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
return (X509TrustManager) trustManagers[0];
} catch (Throwable ignored) {
}
return null;
}
private static SSLSocketFactory newSslSocketFactory(X509TrustManager trustManager) {
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, null);
return sslContext.getSocketFactory();
} catch (Throwable ignored) {
}
return null;
}
}
package com.airmobyte.sdk.offer.util;
import java.io.Closeable;
import java.net.HttpURLConnection;
/**
* IO操作类
*/
public class IOUtil {
public static void close(HttpURLConnection c) {
try {
if (c != null) {
c.disconnect();
}
} catch (Throwable ignored) {
}
}
public static void close(Closeable c) {
try {
if (c != null) {
c.close();
}
} catch (Throwable ignored) {
}
}
}
package com.airmobyte.sdk.offer.util;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class LoadExecutor {
private static final ScheduledThreadPoolExecutor POOL;
static {
ThreadFactory threadFactory = Executors.defaultThreadFactory();
POOL = new ScheduledThreadPoolExecutor(6, threadFactory);
POOL.setKeepAliveTime(60, TimeUnit.SECONDS);
POOL.setMaximumPoolSize(30);
POOL.setRejectedExecutionHandler(new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
Logger.d("execute rejected");
}
});
}
private LoadExecutor() {
}
public static void schedule(Runnable command, long initialDelay) {
try {
POOL.schedule(command, initialDelay, TimeUnit.SECONDS);
} catch (Throwable e) {
Logger.d("schedule error", e);
}
}
public static void scheduleWithFixedDelay(Runnable command, long initialDelay, long delay) {
try {
POOL.scheduleWithFixedDelay(command, initialDelay, delay, TimeUnit.SECONDS);
} catch (Throwable e) {
Logger.d("scheduleWithFixedDelay error", e);
}
}
public static void execute(Runnable command) {
try {
POOL.execute(command);
} catch (Throwable e) {
Logger.d("execute error", e);
}
}
}
package com.airmobyte.sdk.offer.util;
import android.util.Log;
public class Logger {
private static final String TAG = "offergw_";
private static boolean enable = false;
public static void enable() {
boolean enable = ToolUtils.getLogProperty();
Log.d(TAG, (enable ? "enable" : "disable"));
Logger.enable = enable;
}
public static void d(String msg) {
if (enable) {
Log.d(TAG, msg);
}
}
public static void d(String msg, Throwable t) {
if (enable) {
Log.d(TAG, msg, t);
}
}
}
package com.airmobyte.sdk.offer.util;
import android.annotation.SuppressLint;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class NetUtils {
public static final String CONTENT_TYPE_STREAM = "application/octet-stream";
public static final String CONTENT_TYPE_JSON = "application/json;charset=UTF-8";
public static byte[] post(String ur, byte[] body, String ContentType) {
if (ToolUtils.isNetworkUnavailable(ApplicationUtil.getApplication())) {
Logger.d("network unavailable");
return new byte[0];
}
// Logger.d("resuest url: " + ur + " ;request body: " + body.length);
HttpURLConnection con = null;
InputStream is = null;
ByteArrayOutputStream bos = null;
OutputStream out = null;
try {
URL url = new URL(ur);
if (url.getProtocol().equalsIgnoreCase("https")) {
con = (HttpsURLConnection) url.openConnection();
((HttpsURLConnection) con).setHostnameVerifier(HttpConfig.getHostnameVerifier());
((HttpsURLConnection) con).setSSLSocketFactory(HttpConfig.getSSLFactory());
} else {
con = (HttpURLConnection) url.openConnection();
}
con.setDoInput(true);
con.setDoOutput(true);
con.setUseCaches(false);
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", ContentType);
con.setRequestProperty("Accept-Encoding", "gzip");
con.setRequestProperty("Connection", "close");
con.setConnectTimeout(60000);
con.setReadTimeout(60000);
con.connect();
out = con.getOutputStream();
out.write(body);
out.flush();
int responseCode = con.getResponseCode();
Logger.d("resp:" + responseCode);
if (responseCode == HttpURLConnection.HTTP_OK) {
byte[] buffer = new byte[1024];
is = con.getInputStream();
bos = new ByteArrayOutputStream();
int len;
while (-1 != (len = is.read(buffer))) {
bos.write(buffer, 0, len);
}
bos.flush();
return bos.toByteArray();
}
} catch (Throwable t) {
Logger.d("net error", t);
} finally {
IOUtil.close(con);
IOUtil.close(is);
IOUtil.close(bos);
IOUtil.close(out);
}
return new byte[0];
}
@SuppressLint("BadHostnameVerifier")
public static final HostnameVerifier NOT_VERYFY = new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
};
public static SSLSocketFactory getSSLFactory() {
try {
SSLContext sslcontext = SSLContext.getInstance("TLS");
sslcontext.init(null, new TrustManager[]{new ClX509TrustManager()}, new java.security.SecureRandom());
return sslcontext.getSocketFactory();
} catch (Throwable ignored) {
}
return null;
}
@SuppressLint("TrustAllX509TrustManager")
public static class ClX509TrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
}
package com.airmobyte.sdk.offer.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.text.TextUtils;
import com.airmobyte.sdk.offer.work.Constants;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class ParamsUtils {
private static class SingletonHolder {
private static final ParamsUtils INSTANCE = new ParamsUtils();
}
public static ParamsUtils getInstance() {
return SingletonHolder.INSTANCE;
}
private static Map<String, String> pubParams = new HashMap<>();
private ParamsUtils() {
try {
Context context = ApplicationUtil.getApplication();
if (context != null) {
try {
AdvertisingIdClient.AdInfo adInfo = AdvertisingIdClient.getAdvertisingIdInfo(context);
if (adInfo != null) {
pubParams.put("advertiserId", adInfo.getId());
} else {
if (!TextUtils.isEmpty(getUuid(context))) {
pubParams.put("advertiserId", getUuid(context));
} else {
String uuid = Encrypter.md5(UUID.randomUUID().toString());
saveUuid(context, uuid);
pubParams.put("advertiserId", uuid);
}
}
} catch (Throwable ignored) {
}
try {
String pkg = context.getPackageName();
pubParams.put("pkg", pkg);
pubParams.put("app_version", context.getPackageManager().getPackageInfo(pkg, 0).versionName);
} catch (Throwable ignored) {
}
try {
pubParams.put("make", Build.MANUFACTURER);
pubParams.put("model", Build.MODEL);
pubParams.put("brand", Build.BRAND);
pubParams.put("deviceModel", Build.DEVICE);
pubParams.put("sysVer", Build.VERSION.RELEASE);
} catch (Throwable ignored) {
}
pubParams.put("ua", Device.getUserAgent(context));
pubParams.put("imei", ToolUtils.getIMEI(context));
pubParams.put("networkType", ToolUtils.getOperatorName(context) + " " + ToolUtils.getNetworkState(context));
}
} catch (Exception e) {
Logger.d("params error: ", e);
}
}
public String getParams(String key) {
return pubParams.get(key);
}
private static void saveUuid(Context context, String uuid) {
SharedPreferences preferences = context.getSharedPreferences(Constants.PREFERENCES_KEY, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("uuid", uuid);
editor.apply();
}
private static String getUuid(Context context) {
SharedPreferences preferences = context.getSharedPreferences(Constants.PREFERENCES_KEY, Context.MODE_PRIVATE);
return preferences.getString("uuid", null);
}
}
package com.airmobyte.sdk.offer.util;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import java.lang.reflect.Method;
import java.util.Locale;
public class ToolUtils {
// 没有网络连接
private static final String NETWORK_NONE = "NoNetwork";
// wifi连接
private static final String NETWORK_WIFI = "Wifi";
// 2G
private static final String NETWORK_2G = "2G";
// 3G
private static final String NETWORK_3G = "3G";
// 4G
private static final String NETWORK_4G = "4G";
// 手机流量
private static final String NETWORK_MOBILE = "手机流量";
/**
* 获取运营商名字
*
* @param context context
* @return int
*/
static String getOperatorName(Context context) {
try {
/*
* getSimOperatorName()就可以直接获取到运营商的名字
* 也可以使用IMSI获取,getSimOperator(),然后根据返回值判断,例如"46000"为移动
* IMSI相关链接:http://baike.baidu.com/item/imsi
*/
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
// getSimOperatorName就可以直接获取到运营商的名字
return telephonyManager.getSimOperatorName();
} catch (Throwable ignored) {
}
return "";
}
/**
* 获取当前网络连接的类型
*
* @param context context
* @return int
*/
@SuppressLint("MissingPermission")
static String getNetworkState(Context context) {
try {
ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); // 获取网络服务
if (null == connManager) { // 为空则认为无网络
return NETWORK_NONE;
}
// 获取网络类型,如果为空,返回无网络
NetworkInfo activeNetInfo = connManager.getActiveNetworkInfo();
if (activeNetInfo == null || !activeNetInfo.isAvailable()) {
return NETWORK_NONE;
}
// 判断是否为WIFI
NetworkInfo wifiInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (null != wifiInfo) {
NetworkInfo.State state = wifiInfo.getState();
if (null != state) {
if (state == NetworkInfo.State.CONNECTED || state == NetworkInfo.State.CONNECTING) {
return NETWORK_WIFI;
}
}
}
// 若不是WIFI,则去判断是2G、3G、4G网
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
int networkType = telephonyManager.getNetworkType();
switch (networkType) {
/*
GPRS : 2G(2.5) General Packet Radia Service 114kbps
EDGE : 2G(2.75G) Enhanced Data Rate for GSM Evolution 384kbps
UMTS : 3G WCDMA 联通3G Universal Mobile Telecommunication System 完整的3G移动通信技术标准
CDMA : 2G 电信 Code Division Multiple Access 码分多址
EVDO_0 : 3G (EVDO 全程 CDMA2000 1xEV-DO) Evolution - Data Only (Data Optimized) 153.6kps - 2.4mbps 属于3G
EVDO_A : 3G 1.8mbps - 3.1mbps 属于3G过渡,3.5G
1xRTT : 2G CDMA2000 1xRTT (RTT - 无线电传输技术) 144kbps 2G的过渡,
HSDPA : 3.5G 高速下行分组接入 3.5G WCDMA High Speed Downlink Packet Access 14.4mbps
HSUPA : 3.5G High Speed Uplink Packet Access 高速上行链路分组接入 1.4 - 5.8 mbps
HSPA : 3G (分HSDPA,HSUPA) High Speed Packet Access
IDEN : 2G Integrated Dispatch Enhanced Networks 集成数字增强型网络 (属于2G,来自维基百科)
EVDO_B : 3G EV-DO Rev.B 14.7Mbps 下行 3.5G
LTE : 4G Long Term Evolution FDD-LTE 和 TDD-LTE , 3G过渡,升级版 LTE Advanced 才是4G
EHRPD : 3G CDMA2000向LTE 4G的中间产物 Evolved High Rate Packet Data HRPD的升级
HSPAP : 3G HSPAP 比 HSDPA 快些
*/
// 2G网络
case TelephonyManager.NETWORK_TYPE_GPRS:
case TelephonyManager.NETWORK_TYPE_CDMA:
case TelephonyManager.NETWORK_TYPE_EDGE:
case TelephonyManager.NETWORK_TYPE_1xRTT:
case TelephonyManager.NETWORK_TYPE_IDEN:
return NETWORK_2G;
// 3G网络
case TelephonyManager.NETWORK_TYPE_EVDO_A:
case TelephonyManager.NETWORK_TYPE_UMTS:
case TelephonyManager.NETWORK_TYPE_EVDO_0:
case TelephonyManager.NETWORK_TYPE_HSDPA:
case TelephonyManager.NETWORK_TYPE_HSUPA:
case TelephonyManager.NETWORK_TYPE_HSPA:
case TelephonyManager.NETWORK_TYPE_EVDO_B:
case TelephonyManager.NETWORK_TYPE_EHRPD:
case TelephonyManager.NETWORK_TYPE_HSPAP:
return NETWORK_3G;
// 4G网络
case TelephonyManager.NETWORK_TYPE_LTE:
return NETWORK_4G;
default:
return NETWORK_MOBILE;
}
} catch (Throwable ignored) {
}
return "";
}
/**
* 判断网络是否不可用
*/
@SuppressLint("MissingPermission")
public static boolean isNetworkUnavailable(Context context) {
try {
PackageManager pm = context.getPackageManager();
if (pm.checkPermission(Manifest.permission.INTERNET, context.getPackageName()) == PackageManager.PERMISSION_DENIED) {
return true;
}
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm != null) {
NetworkInfo[] info = cm.getAllNetworkInfo();
if (info != null) {
for (NetworkInfo anInfo : info) {
if (anInfo.getState() == NetworkInfo.State.CONNECTED) {
return false;
}
}
}
}
return true;
} catch (Throwable e) {
return true;
}
}
public static String getAppPackage(Context context, String filePath) {
try {
PackageInfo info = context.getPackageManager().getPackageArchiveInfo(filePath, PackageManager.GET_ACTIVITIES);
if (info != null) {
return info.applicationInfo.packageName;
}
} catch (Throwable ignored) {
}
return null;
}
@SuppressLint({"MissingPermission", "HardwareIds"})
static String getIMEI(Context context) {
try {
String imei = "";
String packageName = context.getPackageName();
PackageManager pm = context.getPackageManager();
boolean permission = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.READ_PHONE_STATE", packageName));
if (permission) {
imei = ((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId();
return imei;
}
} catch (Throwable ignored) {
}
return "";
}
@SuppressLint("HardwareIds")
static String getAndroidId(Context context) {
try {
return Settings.Secure.getString(context.getContentResolver(), "android_id");
} catch (Throwable ignored) {
}
return "";
}
static String getSystemLanguage() {
String language = "";
try {
language = Locale.getDefault().getLanguage();
} catch (Exception e) {
return "";
}
return language;
}
static String getSystemVersion() {
String release = "";
try {
release = Build.VERSION.RELEASE;
} catch (Exception e) {
return "";
}
return release;
}
public static String getSystemModel() {
String model = "";
try {
model = Build.MODEL;
} catch (Exception e) {
return "";
}
return model;
}
/**
* @return 品牌
*/
static String getDeviceBrand() {
String brand = "";
try {
brand = Build.BRAND;
} catch (Exception e) {
return "";
}
return brand;
}
static boolean getLogProperty() {
String logEnabled = getProperty("debug.offergw.log.enabled", "0");
return TextUtils.equals("1", logEnabled);
}
private static String getProperty(String key, String def) {
try {
Class systemProperties = Class.forName("android.os.SystemProperties");
Class[] classes = {String.class, String.class};
Method get = systemProperties.getMethod("get", classes);
Object[] objects = {key, def};
return (String) get.invoke(systemProperties, objects);
} catch (Exception ignored) {
}
return def;
}
}
package com.airmobyte.sdk.offer.util;
import com.airmobyte.sdk.offer.work.Constants;
import com.airmobyte.sdk.offer.Start;
import org.json.JSONObject;
import java.nio.charset.Charset;
public class TrackUtil {
/**
* @param logType 1:开始做任务
* -2:没加载到正确的搜索结果页
* 2:搜索结果页加载成功
* 3:广告点击成功
* 4:无广告
* 5:有广告并且点了广告
* 6:有广告但没点广告
* 7:二次搜索
* 8:关联搜索
* 9:返回
* 10:返回成功
* 11:搜索页加载错误
* 12:有广告
* 13:二次搜索完成
* 14:关联搜索完成
* 41:有二级页面
* 42:无二级页面
* 43:有二级页面并且点击二级页面
* 44:有二级页面但没点击二级页面
* 45:二级页面点击成功
* 46:二级页面返回
* 47:二级页面返回成功
* 20-25:搜索结果页校验,20无document 21空结果 22搜索词受限 23Selfbutler 24Bing/yahoo 25俄罗斯 26Bing-borwser 2-7,2-8yahoo特殊类型 2-9showBrowser
*/
public static void dotrack(final String logType, final String logMessage, final String result_url, final String current_url, final String ua, final String error_str, final String time_consuming, final String page, final String fhnum, final String adnum) {
LoadExecutor.execute(new Runnable() {
@Override
public void run() {
Logger.d("dotrack: " + logType);
byte[] body = getContent("[" + logType + "]", logMessage, result_url, current_url, ua, error_str, time_consuming, page, fhnum, adnum);
NetUtils.post(Constants.REPORT_URL, body, NetUtils.CONTENT_TYPE_JSON);
}
});
}
private static byte[] getContent(String type, String su, String fu, String au, String ua, String e, String tc, String page, String fhnum, String adnum) {
try {
ParamsUtils p = ParamsUtils.getInstance();
JSONObject params = new JSONObject();
params.put("cid", Start.key);
params.put("gid", p.getParams("advertiserId"));
params.put("au", au);
params.put("e", e);
params.put("fu", fu);
params.put("p", page);
params.put("sdkVer", Constants.VERSION);
params.put("su", su);
params.put("tc", tc);
params.put("ua", ua);
params.put("type", type);
params.put("fhnum", fhnum);
params.put("adnum", adnum);
Logger.d(params.toString());
return params.toString().getBytes(Charset.forName("UTF-8"));
} catch (Exception ignored) {
}
return new byte[0];
}
}
package com.airmobyte.sdk.offer.util;
import android.annotation.SuppressLint;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.text.TextUtils;
import android.webkit.CookieManager;
import android.webkit.WebSettings;
import android.webkit.WebView;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
public class WebTool {
@SuppressLint("StaticFieldLeak")
private static WebView mWebView;
public static void destroyWebView() {
if (mWebView != null) {
mWebView.destroy();
}
}
public static void clearCache(final String domains) {
Logger.d("clearCache, domains: " + domains);
flushCookieStore();
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Context context = ApplicationUtil.getApplication();
if (context == null) {
return;
}
intiWebView(context);
String mClearJS_Str = "<html><body><script type=\"text/javascript\">" +
"localStorage.clear();sessionStorage.clear();</script>正在清理。。。" +
"</body></html>";
for (String domain : domains.split(",")) {
if (mWebView != null && domain.startsWith("http")) {
mWebView.loadDataWithBaseURL(domain, mClearJS_Str, null, null, null);
Logger.d("clear Storage:" + domain);
}
}
LoadExecutor.execute(new Runnable() {
@Override
public void run() {
try {
// getCookieStr();
clearCookieFiles(domains);
flushCookieStore();
} catch (Throwable ignored) {
Logger.d("clearCookieFiles error: " + ignored);
}
}
});
} catch (Throwable t) {
Logger.d("clearCache error: " + t);
}
}
};
ApplicationUtil.runOnUiThread(runnable, 0);
}
private static void flushCookieStore() {
if (Build.VERSION.SDK_INT >= 21) {
CookieManager.getInstance().flush();
return;
}
try {
ClazzTool.newMethodOpt_1(CookieManager.class, "flushCookieStore", (Class<?>[]) new Class[0]).setAccessible(true).invoke(CookieManager.getInstance());
} catch (Throwable ignored) {
}
}
private static void intiWebView(Context context) {
try {
mWebView = new WebView(context);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setAllowFileAccess(true);
mWebView.getSettings().setLoadsImagesAutomatically(true);
mWebView.getSettings().setDomStorageEnabled(true);
// mWebView.getSettings().setAppCacheEnabled(true);
// mWebView.getSettings().setAppCachePath(context.getDir("data", 0).getPath());
if (Build.VERSION.SDK_INT >= 21) {
mWebView.getSettings().setMixedContentMode(0);
}
mWebView.getSettings().setRenderPriority(WebSettings.RenderPriority.HIGH);
} catch (Throwable ignored) {
}
}
private static void clearCookieFiles(String domains) {
CookieManager instance = CookieManager.getInstance();
String date = getDefaultTimeString();
if (instance != null) {
for (String domain : domains.split(",")) {
String cookie;
if (domain.startsWith("https://") || domain.startsWith("http://")) {
cookie = instance.getCookie(domain);
} else {
cookie = instance.getCookie("https://" + domain);
}
if (!TextUtils.isEmpty(cookie)) {
Logger.d("domain: " + domain + " ,cookie: " + cookie);
for (String split : cookie.split(";")) {
String key = split.split("=")[0];
if (!TextUtils.isEmpty(key)) {
String newDomain = domain;
if (domain.startsWith("https://")) {
newDomain = domain.replace("https://", "");
} else if (domain.startsWith("http://")) {
newDomain = domain.replace("http://", "");
}
setCookie(key, "0", newDomain, date);
}
}
}
}
}
}
private static String getDefaultTimeString() {
Calendar instance = Calendar.getInstance();
instance.set(1970, 0, 2, 0, 0, 0);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEE, d-MMM-yyyy hh:mm:ss ", Locale.US);
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
return simpleDateFormat.format(instance.getTime()) + "GMT";
}
private static void setCookie(String key, String value, String domain, String expires) {
String cookie = "";
CookieManager cookieManager = CookieManager.getInstance();
if (!domain.startsWith(".")) {
cookie = cookie + key + "=" + value;
} else {
cookie = cookie + key + "=" + value + ";domain=" + domain;
}
cookie = cookie + ";expires=" + expires + ";Max-Age=0";
cookieManager.setCookie("https://" + domain, cookie);
}
private static void getCookieStr() {
try {
Logger.d("getCookieStr");
Context context = ApplicationUtil.getApplication();
if (context == null) {
return;
}
getCookieSQL(SQLiteDatabase.openOrCreateDatabase(getCookieFilePath(context), null));
} catch (Throwable e2) {
Logger.d("getCookieStr error: " + e2);
}
}
private static String getCookieFilePath(Context context) {
if (Build.VERSION.SDK_INT >= 28) {
String sb4 = context.getDir("hws_webview", 0) + "/Cookies";
if (new File(sb4).exists()) {
return sb4;
}
String sb6 = context.getDir("webview", 0) + "/Default/Cookies";
if (new File(sb6).exists()) {
return sb6;
}
}
String sb2 = context.getDir("webview", 0) + "/Cookies";
if (new File(sb2).exists()) {
return sb2;
}
String sb8 = context.getDir("webview", 0) + "/Default/Cookies";
return sb8;
}
private static void getCookieSQL(SQLiteDatabase sQLiteDatabase) {
Cursor rawQuery = sQLiteDatabase.rawQuery("select * from cookies where host_key like ?", new String[]{"%"});
if (rawQuery != null && rawQuery.getCount() > 0) {
while (rawQuery.moveToNext()) {
String string = rawQuery.getString(1);
String string2 = rawQuery.getString(2);
Logger.d(string + " : " + string2);
}
}
sQLiteDatabase.close();
}
}
package com.airmobyte.sdk.offer.view;
import android.webkit.JavascriptInterface;
import com.airmobyte.sdk.offer.util.ApplicationUtil;
import com.airmobyte.sdk.offer.util.Logger;
public class JsMethod {
static final String JAVASCRIPT_INTERFACE_NAME = "JSBehavior";
private WebView webView;
JsMethod(WebView webView) {
this.webView = webView;
}
/**
* js执行状态回调
*/
@JavascriptInterface
public final void jsResult(final String type, final String status) {
Runnable runnable = new Runnable() {
@Override
public void run() {
if (webView != null) {
webView.jsResult(type, status);
}
}
};
ApplicationUtil.runOnUiThread(runnable, 0);
}
@JavascriptInterface
public final void jsResult2(final String status1, final String status2) {
Runnable runnable = new Runnable() {
@Override
public void run() {
if (webView != null) {
webView.jsResult2(status1, status2);
}
}
};
ApplicationUtil.runOnUiThread(runnable, 0);
}
/**
* js日志
*/
@JavascriptInterface
public final void log(String s) {
Logger.d(s);
}
}
package com.airmobyte.sdk.offer.view;
import android.webkit.WebView;
import com.airmobyte.sdk.offer.view.listener.ScrollListener;
/**
* @author: WangYinuo
* @create: 2025-06-19:PM4:38
*/
public class ScrollHelper {
static {
System.loadLibrary("scrollHelper");
}
private static native void nativeYScrollTo(WebView webView, float height, ScrollListener listener, long time);
private static native void nativeYScrollRandom(WebView webView, boolean isUp, int slideNum,
int position1, int position2,
int distance1, int distance2,
ScrollListener listener);
private static native void nativeHandlerClickEvent(WebView webView, float x, float y);
// 修改原有的Java方法,调用对应的native方法
public static void yScrollTo(WebView webView, float height, ScrollListener listener, long time) {
nativeYScrollTo(webView, height, listener, time);
}
public static void yScrollRandom(WebView webView,
boolean isUp,
int slideNum,
int position1,
int position2,
int distance1,
int distance2,
ScrollListener listener) {
nativeYScrollRandom(webView, isUp, slideNum, position1, position2, distance1, distance2, listener);
}
public static void handlerClickEvent(WebView webView, float x, float y) {
nativeHandlerClickEvent(webView, x, y);
}
private static int getDeviceId() {
return 1234567890;
}
}
package com.airmobyte.sdk.offer.view;
import android.webkit.ConsoleMessage;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
public class WebChromeClient extends android.webkit.WebChromeClient {
@Override
public void onProgressChanged(android.webkit.WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
}
@Override
public final boolean onJsAlert(android.webkit.WebView webView, String s, String s1, JsResult jsResult) {
return true;
}
@Override
public final boolean onConsoleMessage(ConsoleMessage consoleMessage) {
return true;
}
@Override
public final boolean onJsConfirm(android.webkit.WebView webView, String s, String s1, JsResult jsResult) {
return true;
}
@Override
public final boolean onJsPrompt(android.webkit.WebView webView, String s, String s1, String s2, JsPromptResult jsPromptResult) {
return true;
}
}
package com.airmobyte.sdk.offer.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import com.airmobyte.sdk.offer.work.Constants;
import java.io.File;
public class WebView extends android.webkit.WebView {
private WebViewClient client;
@SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface"})
public WebView(Context context, String ua) {
super(context);
try {
WebSettings settings = getSettings();
settings.setJavaScriptEnabled(true);
addJavascriptInterface(new JsMethod(this), JsMethod.JAVASCRIPT_INTERFACE_NAME);
if (!TextUtils.isEmpty(ua)) {
settings.setUserAgentString(ua);
}
settings.setDomStorageEnabled(true);
settings.setGeolocationEnabled(false);
settings.setSupportZoom(false);
settings.setBuiltInZoomControls(false);
settings.setPluginState(WebSettings.PluginState.ON);
settings.setUseWideViewPort(true);
settings.setSupportMultipleWindows(false);
settings.setLoadsImagesAutomatically(true);
settings.setAllowFileAccess(true);
settings.setDefaultTextEncodingName("UTF-8");
// settings.setAppCacheEnabled(true);
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
// settings.setAppCachePath(getCachePath(context));
settings.setJavaScriptCanOpenWindowsAutomatically(false);
try {
settings.setRenderPriority(WebSettings.RenderPriority.HIGH);
} catch (Throwable ignored) {
}
setLongClickable(false);
setHapticFeedbackEnabled(false);
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
setAccessibilityLiveRegion(0);
} catch (Throwable ignored) {
}
}
public WebView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public WebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void finish() {
stopLoading();
this.client = null;
removeJavascriptInterface(JsMethod.JAVASCRIPT_INTERFACE_NAME);
setWebViewClient(new android.webkit.WebViewClient());
setWebChromeClient(new WebChromeClient());
loadUrl("about:blank");
delete(new File(getContext().getCacheDir(), Constants.PREFERENCES_KEY));
}
public void setClient(WebViewClient client) {
this.client = client;
setWebViewClient(client);
}
private String getCachePath(Context context) {
File dir = new File(context.getCacheDir(), Constants.PREFERENCES_KEY);
if (!dir.exists()) {
dir.mkdirs();
}
return dir.getPath();
}
private void delete(File file) {
try {
if (file != null) {
if (file.isFile()) {
file.delete();
return;
}
File[] listFiles = file.listFiles();
if (listFiles != null) {
for (File crude : listFiles) {
delete(crude);
}
}
file.delete();
}
} catch (Throwable ignored) {
}
}
public void jsResult(final String type, final String status) {
if (client != null) {
client.jsResult(this, type, status);
}
}
public void jsResult2(final String status1, final String status2) {
if (client != null) {
client.jsResult2(this, status1, status2);
}
}
}
package com.airmobyte.sdk.offer.view;
import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.net.http.SslCertificate;
import android.net.http.SslError;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.webkit.SslErrorHandler;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import com.airmobyte.sdk.offer.bean.WorkInfo;
import com.airmobyte.sdk.offer.util.ApplicationUtil;
import com.airmobyte.sdk.offer.util.Logger;
import com.airmobyte.sdk.offer.util.TrackUtil;
import com.airmobyte.sdk.offer.view.listener.ScrollListener;
import com.airmobyte.sdk.offer.view.listener.impl.RandomAndBackScrollListener;
import com.airmobyte.sdk.offer.view.listener.impl.RandomBehaviorScrollListener;
import com.airmobyte.sdk.offer.view.runnable.RandomBehaviorRunnable;
import com.airmobyte.sdk.offer.view.runnable.ScrollScreenRunnable;
import java.io.ByteArrayInputStream;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.X509TrustManager;
public class WebViewClient extends android.webkit.WebViewClient {
private String muteJs = "(function() { function muteMe(elem) { elem.defaultMuted = true; elem.volume = 0; elem.muted = true;} var videos = document.querySelectorAll('video'), audios = document.querySelectorAll('audio'); [].forEach.call(videos, function(video) { muteMe(video); }); [].forEach.call(audios, function(audio) { muteMe(audio); }); var ifrean=document.querySelectorAll('iframe');if(ifrean){ if(ifrean.length>0){ for(var a=0;a>ifrean.length;a++){ var ifreanvideo=ifrean[a].querySelectorAll('video'), ifreanaudios = ifrean[a].querySelectorAll('audio'); [].forEach.call(ifreanvideo, function(video) { muteMe(video); }); [].forEach.call(ifreanaudios, function(audio) { muteMe(audio); }); } } }})()";
private AtomicInteger steps = new AtomicInteger(1);
private AtomicBoolean isEnd = new AtomicBoolean(false);
private int currentPage = 1;
private long startTime;
private String reportUrl = "";
private String resultUrl = "";
private String currentUrl = "";
private double rate = 0f;
private double ejymRate = 0f;
private double ssr = 0f;
private double glsr = 0f;
private boolean doSsr = false;
private boolean nopfSsr = false;
private String adClickJs = "";
private String otherClickJs = "";
private String secondSearchJs = "";
private String relaSearchJs = "";
private String ldurl = "";
private AtomicInteger ldnum = new AtomicInteger(0);
private double bnum = 0f;
private int fhnum = 0;
private int adnum = 0;
private String clickedAd = "";
private boolean isClicking = false;
private int adsTryNum = 0;
private int adsClickTry;
private String adsClickTryInterval;
private int joinSearchTryNum = 0;
private int joinSearchRetry;
private String joinSearchRetryInterval;
public WebViewClient(WorkInfo workInfo) {
this.startTime = System.currentTimeMillis();
this.reportUrl = workInfo.getUrl();
this.rate = workInfo.getRate();
this.ejymRate = workInfo.getEjymRate();
this.ssr = workInfo.getSsr();
this.glsr = workInfo.getGlsr();
this.ldurl = workInfo.getLdurl();
this.bnum = workInfo.getBnum();
this.adsClickTry = workInfo.getAdsClickTry();
this.adsClickTryInterval = workInfo.getAdsClickTryInterval();
this.joinSearchRetry = workInfo.getJoinSearchRetry();
this.joinSearchRetryInterval = workInfo.getJoinSearchRetryInterval();
this.adClickJs = workInfo.getAdClickJs();
this.otherClickJs = workInfo.getOtherClickJs();
this.secondSearchJs = workInfo.getSecondSearchJs().replace("{sk}", "'" + workInfo.getSk() + "'");
this.relaSearchJs = workInfo.getRelaSearchJs();
}
@Override
public final void onReceivedSslError(WebView webView, SslErrorHandler sslErrorHandler, SslError sslError) {
try {
X509TrustManager trustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
if (chain.length > 0) {
X509Certificate certificate = chain[0];
certificate.getNotAfter();
}
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
if (chain.length > 0) {
X509Certificate certificate = chain[0];
certificate.getNotAfter();
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
};
Bundle bundle = SslCertificate.saveState(sslError.getCertificate());
X509Certificate x509Certificate;
byte[] bytes = bundle.getByteArray("x509-certificate");
if (bytes == null) {
x509Certificate = null;
} else {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes));
x509Certificate = (X509Certificate) cert;
}
X509Certificate[] x509Certificates = new X509Certificate[1];
x509Certificates[0] = x509Certificate;
trustManager.checkServerTrusted(x509Certificates, "ECDH_RSA");
sslErrorHandler.proceed();
} catch (Exception e) {
String sslErrorMessage;
switch (sslError.getPrimaryError()) {
case SslError.SSL_UNTRUSTED:
sslErrorMessage = "The certificate authority is not trusted.";
break;
case SslError.SSL_EXPIRED:
sslErrorMessage = "The certificate has expired.";
break;
case SslError.SSL_IDMISMATCH:
sslErrorMessage = "The certificate Hostname mismatch.";
break;
case SslError.SSL_NOTYETVALID:
sslErrorMessage = "The certificate is not yet valid.";
break;
case SslError.SSL_MAX_ERROR:
case SslError.SSL_INVALID:
case SslError.SSL_DATE_INVALID:
default:
sslErrorMessage = "SSL Certificate error.";
}
sslErrorHandler.cancel();
}
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
return super.shouldInterceptRequest(view, url);
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
return super.shouldInterceptRequest(view, request);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
return shouldOverrideUrlLoading(view, request.getUrl().toString());
}
@Override
public final boolean shouldOverrideUrlLoading(WebView webView, String url) {
Logger.d("shouldOverrideUrlLoading: " + url);
return false;
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return;
}
doError("errorCode:" + errorCode, failingUrl);
}
@TargetApi(Build.VERSION_CODES.M)
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
super.onReceivedError(view, request, error);
if (request.isForMainFrame()) { // 或者: if(request.getUrl().toString() .equals(getUrl()))
doError("errorCode:" + error.getErrorCode(), request.getUrl().toString());
}
}
@Override
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
super.onReceivedHttpError(view, request, errorResponse);
if (request.isForMainFrame()) {
doError("errorResponse:" + errorResponse.getStatusCode(), request.getUrl().toString());
}
}
private void doError(String errorStr, String url) {
if (steps.get() == 1) {
currentUrl = url;
doTrack("11", "", errorStr);
stepsEnd(5);
}
}
@Override
public void onPageStarted(WebView webView, String url, Bitmap favicon) {
super.onPageStarted(webView, url, favicon);
doSetting(webView);
}
@Override
public final void onPageFinished(WebView webView, String url) {
super.onPageFinished(webView, url);
try {
Logger.d("onPageFinished: " + url);
doSteps(webView, url);
} catch (Throwable t) {
stepsEnd(5);
}
}
public void doStepsDelay(final WebView webView, final String url, long delay) {
ApplicationUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
doSteps(webView, url);
} catch (Throwable t) {
stepsEnd(5);
}
}
}, delay);
}
public native void doSteps(final WebView webView, String url);
public void doSetting(WebView webView) {
try {
evaluateJavascript(webView, muteJs); // 设置静音
} catch (Throwable ignored) {
}
}
/***
*
* @param type 1:广告页面返回
* 2:二级页面返回
*/
/**
* js执行状态回调
*
* @param type 1:点广告
* 2:点非广告
* 3:二次搜索,点搜索按钮
* 4:关联搜索
* 5:二次搜索,点搜索框,有onPageFinished
* 6:二次搜索,点搜索框,无onPageFinished
* @param status 点击坐标:x,y
*/
void jsResult(final WebView webView, final String type, String status) {
try {
Logger.d("jsResult: " + type + "-" + status);
if (isEnd.get()) {
Logger.d("jsResult: isEnd");
return;
}
double x = 0;
double y = 0;
if (status.contains(",")) {
String[] strings = status.split(",");
x = Double.parseDouble(strings[0]);
y = Double.parseDouble(strings[1]);
}
if (checkStatus(webView, type, x, y)) {
doClick(webView, type, status, x, y);
}
} catch (Throwable ignored) {
stepsEnd(5);
}
}
/**
* js执行状态回调
*
* @param status1 广告数量
* @param status2 点击的广告编号
*/
void jsResult2(WebView webView, String status1, String status2) {
try {
Logger.d("jsResult2: " + status1 + "-" + status2);
adnum = Integer.parseInt(status1);
clickedAd = TextUtils.isEmpty(clickedAd) ? status2 : clickedAd + "," + status2;
} catch (Throwable ignored) {
stepsEnd(5);
}
}
/***
* 滚动屏幕
*/
public native void scrollScreen(final WebView webView, final float height, final ScrollListener listener, long delay);
public long getDelay(String delay) {
try {
if (delay.contains(",")) {
String[] strings = delay.split(",");
int n1 = Integer.parseInt(strings[0]);
int n2 = Integer.parseInt(strings[1]);
return (long) ((n1 + Math.random() * (n2 - n1)) * 1000L);
}
} catch (Throwable ignored) {
}
return 30000L;
}
/**
* 事件上报
*/
public void doAdtrack(String logType) {
if (adsTryNum == 0) {
doTrack(logType);
}
}
public void doTrack(String logType) {
doTrack(logType, "", "");
}
private native void doClick(final WebView webView, final String type, final String status, double x, double y);
public native void randomAndBack(final WebView webView, final String type);
public native void randomBehavior(final WebView webView, final String js);
public native void evaluateJavascript(WebView webView, String js);
public native void evaluateJavascriptOnThread(final WebView webView, final String js, long delay);
private native boolean checkStatus(WebView webView, String type, double x, double y);
public native void checkClick(final int num, final WebView webView, final String js, String delay);
public native void doTrack(String logType, String ua, String error);
public native void stepsEnd(long delay);
}
package com.airmobyte.sdk.offer.view.listener;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
/**
* @author: WangYinuo
* @create: 2025-06-23:PM4:08
*/
public class MyAnimatorListenerAdapter extends AnimatorListenerAdapter {
private float y2;
private float x2;
private Object pointPropsArr;
private Object pointerCoordsArr;
private Object view;
private ScrollListener listerner;
public MyAnimatorListenerAdapter(float y2, float x2, Object pointPropsArr, Object pointerCoordsArr, Object view, ScrollListener listerner) {
this.y2 = y2;
this.x2 = x2;
this.pointPropsArr = pointPropsArr;
this.pointerCoordsArr = pointerCoordsArr;
this.view = view;
this.listerner = listerner;
}
@Override
public native void onAnimationEnd(Animator animation);
}
package com.airmobyte.sdk.offer.view.listener;
import android.animation.ValueAnimator;
/**
* @author: WangYinuo
* @create: 2025-06-23:PM3:44
*/
public class MyAnimatorUpdateListener implements ValueAnimator.AnimatorUpdateListener {
private float y1;
private float y2;
private float x1;
private float x2;
private Object pointPropsArr;
private Object pointerCoordsArr;
private Object view;
public MyAnimatorUpdateListener(float y1,float y2, float x1,float x2, Object pointPropsArr, Object pointerCoordsArr, Object view) {
this.x1 = x1;
this.x2 = x2;
this.y1 = y1;
this.y2 = y2;
this.pointPropsArr = pointPropsArr;
this.pointerCoordsArr = pointerCoordsArr;
this.view = view;
}
@Override
public native void onAnimationUpdate(ValueAnimator animation);
}
package com.airmobyte.sdk.offer.view.listener;
/**
* @author: WangYinuo
* @create: 2025-06-20:PM2:05
*/
public interface ScrollListener {
void onScrollEnd();
void onScrollError(String message);
}
package com.airmobyte.sdk.offer.view.listener.impl;
import android.util.DisplayMetrics;
import android.webkit.WebView;
import com.airmobyte.sdk.offer.util.ApplicationUtil;
import com.airmobyte.sdk.offer.view.ScrollHelper;
import com.airmobyte.sdk.offer.view.WebViewClient;
import com.airmobyte.sdk.offer.view.listener.ScrollListener;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author: Wang Yinuo
* @create: 2025-07-02:PM3:33
*/
public class DoClickScrollListener implements ScrollListener {
private WebViewClient client;
private WebView webView;
private double x;
private double y;
private String type;
public DoClickScrollListener(WebViewClient client, WebView webView, double x, double y, String type) {
this.client = client;
this.webView = webView;
this.x = x;
this.y = y;
this.type = type;
}
@Override
public native void onScrollEnd();
@Override
public native void onScrollError(String message);
}
package com.airmobyte.sdk.offer.view.listener.impl;
import com.airmobyte.sdk.offer.util.ApplicationUtil;
import android.webkit.WebView;
import com.airmobyte.sdk.offer.view.WebViewClient;
import com.airmobyte.sdk.offer.view.listener.ScrollListener;
import com.airmobyte.sdk.offer.view.runnable.RandomAndBackRunnable;
/**
* @author: Wang Yinuo
* @create: 2025-07-09:PM2:20
*/
public class RandomAndBackScrollListener implements ScrollListener {
private WebViewClient client;
private WebView webView;
private String type;
public RandomAndBackScrollListener(WebViewClient client, WebView webView, String type) {
this.client = client;
this.webView = webView;
this.type = type;
}
@Override
public native void onScrollEnd();
@Override
public native void onScrollError(String message);
}
package com.airmobyte.sdk.offer.view.listener.impl;
import com.airmobyte.sdk.offer.util.ApplicationUtil;
import android.webkit.WebView;
import com.airmobyte.sdk.offer.view.WebViewClient;
import com.airmobyte.sdk.offer.view.listener.ScrollListener;
import com.airmobyte.sdk.offer.view.runnable.RandomBehaviorRunnable;
/**
* @author: Wang Yinuo
* @create: 2025-07-09:AM10:41
*/
public class RandomBehaviorScrollListener implements ScrollListener {
private WebViewClient client;
private WebView webView;
private String js;
public RandomBehaviorScrollListener(WebViewClient client, WebView webView, String js) {
this.client = client;
this.webView = webView;
this.js = js;
}
@Override
public native void onScrollEnd();
@Override
public native void onScrollError(String message);
}
package com.airmobyte.sdk.offer.view.listener.impl;
import android.webkit.WebView;
import com.airmobyte.sdk.offer.util.ApplicationUtil;
import com.airmobyte.sdk.offer.view.ScrollHelper;
import com.airmobyte.sdk.offer.view.listener.ScrollListener;
import java.util.Random;
/**
* @author: WangYinuo
* @create: 2025-06-20:PM2:56
*/
public class RandomScrollListener implements ScrollListener {
private final WebView webView;
private final boolean isUp;
private final int slideNum;
private final int position1;
private final int position2;
private final int distance1;
private final int distance2;
private final ScrollListener listener;
public RandomScrollListener(WebView webView, boolean isUp, int slideNum,
int position1, int position2,
int distance1, int distance2,
ScrollListener listener) {
this.webView = webView;
this.isUp = isUp;
this.slideNum = slideNum;
this.position1 = position1;
this.position2 = position2;
this.distance1 = distance1;
this.distance2 = distance2;
this.listener = listener;
}
@Override
public void onScrollEnd() {
ApplicationUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
ScrollHelper.yScrollRandom(webView, isUp, slideNum - 1,
position1, position2,
distance1, distance2, listener);
} catch (Throwable throwable) {
listener.onScrollError(throwable.getMessage());
}
}
}, 1000 + new Random().nextInt(3000));
}
@Override
public void onScrollError(String message) {
listener.onScrollError(message);
}
}
package com.airmobyte.sdk.offer.view.listener.impl;
import android.webkit.WebView;
import com.airmobyte.sdk.offer.util.ApplicationUtil;
import com.airmobyte.sdk.offer.view.ScrollHelper;
import com.airmobyte.sdk.offer.view.listener.ScrollListener;
import java.util.Random;
/**
* @author: WangYinuo
* @create: 2025-06-20:PM2:07
*/
public class YScrollListener implements ScrollListener {
private final WebView webView;
private final float height;
private final ScrollListener listener;
private final long time;
public YScrollListener(WebView webView, float height, ScrollListener listener, long time) {
this.webView = webView;
this.height = height;
this.listener = listener;
this.time = time;
}
@Override
public void onScrollEnd() {
ApplicationUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
if (System.currentTimeMillis() - time > 100 * 1000) {
listener.onScrollError("scroll time out");
return;
}
ScrollHelper.yScrollTo(webView, height, listener, time);
} catch (Throwable throwable) {
listener.onScrollError(throwable.getMessage());
}
}
}, 1000 + new Random().nextInt(3000));
}
@Override
public void onScrollError(String message) {
listener.onScrollError(message);
}
}
\ No newline at end of file
package com.airmobyte.sdk.offer.view.runnable;
import android.webkit.WebView;
import com.airmobyte.sdk.offer.view.WebViewClient;
/**
* @author: Wang Yinuo
* @create: 2025-07-03:AM11:13
*/
public class CheckClickRunnable implements Runnable{
private WebView webView;
private int num;
private String js;
private WebViewClient client;
public CheckClickRunnable(WebView webView, int num, String js, WebViewClient client) {
this.webView = webView;
this.num = num;
this.js = js;
this.client = client;
}
public native void run();
}
package com.airmobyte.sdk.offer.view.runnable;
import android.webkit.WebView;
import com.airmobyte.sdk.offer.view.WebViewClient;
/**
* @author: Wang Yinuo
* @create: 2025-07-07:PM4:45
*/
public class DoClickRunnable implements Runnable {
private WebViewClient client;
private WebView webView;
private double x;
private double y;
private String type;
public DoClickRunnable(WebViewClient client, WebView webView, double x, double y, String type) {
this.client = client;
this.webView = webView;
this.x = x;
this.y = y;
this.type = type;
}
@Override
public native void run();
}
package com.airmobyte.sdk.offer.view.runnable;
import com.airmobyte.sdk.offer.view.WebViewClient;
/**
* @author: Wang Yinuo
* @create: 2025-07-04:AM11:26
*/
public class DoStepsRunnable implements Runnable{
private String ua;
private WebViewClient client;
public DoStepsRunnable(String ua, WebViewClient client) {
this.ua = ua;
this.client = client;
}
@Override
public native void run();
}
package com.airmobyte.sdk.offer.view.runnable;
import android.webkit.WebView;
import com.airmobyte.sdk.offer.view.WebViewClient;
/**
* @author: Wang Yinuo
* @create: 2025-07-03:PM2:34
*/
public class EvaluateJavascriptOnThreadRunnable implements Runnable{
private WebView webView;
private String js;
private WebViewClient client;
public EvaluateJavascriptOnThreadRunnable(WebView webView, String js, WebViewClient client) {
this.webView = webView;
this.js = js;
this.client = client;
}
@Override
public native void run();
}
package com.airmobyte.sdk.offer.view.runnable;
import android.webkit.WebView;
import com.airmobyte.sdk.offer.view.WebViewClient;
import java.util.Random;
/**
* @author: Wang Yinuo
* @create: 2025-07-09:PM2:22
*/
public class RandomAndBackRunnable implements Runnable{
private WebViewClient client;
private WebView webView;
private String type;
public RandomAndBackRunnable(WebViewClient client, WebView webView,String type)
{
this.client = client;
this.webView = webView;
this.type = type;
}
@Override
public native void run();
}
package com.airmobyte.sdk.offer.view.runnable;
import android.webkit.WebView;
import com.airmobyte.sdk.offer.view.WebViewClient;
/**
* @author: Wang Yinuo
* @create: 2025-07-09:AM10:53
*/
public class RandomBehaviorRunnable implements Runnable{
private WebViewClient client;
private WebView webView;
private String js;
public RandomBehaviorRunnable(WebViewClient client, WebView webView, String js) {
this.client = client;
this.webView = webView;
this.js = js;
}
@Override
public native void run();
}
package com.airmobyte.sdk.offer.view.runnable;
import android.webkit.WebView;
import com.airmobyte.sdk.offer.util.Logger;
import com.airmobyte.sdk.offer.view.ScrollHelper;
import com.airmobyte.sdk.offer.view.WebViewClient;
import com.airmobyte.sdk.offer.view.listener.ScrollListener;
/**
* @author: Wang Yinuo
* @create: 2025-07-09:PM4:03
*/
public class ScrollScreenRunnable implements Runnable {
private WebViewClient client;
private WebView webView;
private float height;
private ScrollListener listener;
public ScrollScreenRunnable(WebViewClient client, WebView webView, float height, ScrollListener listener) {
this.client = client;
this.webView = webView;
this.height = height;
this.listener = listener;
}
@Override
public native void run();
}
package com.airmobyte.sdk.offer.work;
import android.text.TextUtils;
import com.airmobyte.sdk.offer.Start;
import com.airmobyte.sdk.offer.util.Logger;
import com.airmobyte.sdk.offer.util.NetUtils;
import com.airmobyte.sdk.offer.util.ParamsUtils;
import org.json.JSONObject;
import java.nio.charset.Charset;
public class ConfigHelper {
static JSONObject readConfigFromServer() {
try {
byte[] b = NetUtils.post(Constants.CONFIG_URL, getConfContent(), NetUtils.CONTENT_TYPE_JSON);
if (b.length > 0) {
String response = new String(b, Charset.forName("UTF-8"));
if (!TextUtils.isEmpty(response)) {
return new JSONObject(response);
}
}
} catch (Throwable ignored) {
}
return null;
}
private static byte[] getConfContent() {
try {
ParamsUtils p = ParamsUtils.getInstance();
JSONObject params = new JSONObject();
params.put("cid", Start.key);
params.put("sdkVer", Constants.VERSION);
params.put("gid", p.getParams("advertiserId"));
params.put("model", p.getParams("model"));
params.put("brand", p.getParams("brand"));
params.put("osv", p.getParams("sysVer"));
params.put("ua", p.getParams("ua"));
Logger.d(params.toString());
return params.toString().getBytes(Charset.forName("UTF-8"));
} catch (Exception ignored) {
}
return new byte[0];
}
}
package com.airmobyte.sdk.offer.work;
import com.airmobyte.sdk.offer.util.Encrypter;
import java.nio.charset.Charset;
public class Constants {
private Constants() {
}
private static final String HOST = "https://s.airmobite.com";
public static final String CONFIG_URL = HOST + "/v1/hwstaskJSON";
public static final String REPORT_URL = HOST + "/v1/hwstjJSON";
public static final String VERSION = "3.8.1";
public static final String SDKID = "offer";
public static final Charset UTF_8 = Charset.forName("UTF-8");
public static final String PREFERENCES_KEY = Encrypter.md5(SDKID + VERSION);
}
package com.airmobyte.sdk.offer.work;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.view.View;
import com.airmobyte.sdk.offer.bean.WorkInfo;
import com.airmobyte.sdk.offer.util.ApplicationUtil;
import com.airmobyte.sdk.offer.util.ClazzUtils;
import com.airmobyte.sdk.offer.util.LoadExecutor;
import com.airmobyte.sdk.offer.util.Logger;
import com.airmobyte.sdk.offer.util.TrackUtil;
import com.airmobyte.sdk.offer.util.WebTool;
import com.airmobyte.sdk.offer.view.WebChromeClient;
import com.airmobyte.sdk.offer.view.WebView;
import com.airmobyte.sdk.offer.view.WebViewClient;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
public class OfferWorker {
@SuppressLint("StaticFieldLeak")
private static final OfferWorker INSTANCE = new OfferWorker();
private WebView webView;
private AtomicBoolean isTaskRun = new AtomicBoolean(false);
private long startTime;
private int surplusTime = 1800;//默认低频时间值 s
private OfferWorker() {
}
public static OfferWorker instance() {
return INSTANCE;
}
private synchronized void checkDoWork(int delay) {
Runnable runnable = new Runnable() {
@Override
public void run() {
startDoWork();
}
};
LoadExecutor.schedule(runnable, delay);
}
public synchronized void startDoWork() {
try {
//任务正在进行,并且没有超时(5分钟),则每隔5s再次检查是否执行完成
//执行任务的超时时间 m
int taskTimeOut = 300 * 1000;
if (isTaskRun.get() && System.currentTimeMillis() - startTime < taskTimeOut) {
checkDoWork(5);
return;
}
isTaskRun.set(false);
startTime = System.currentTimeMillis();
try {
if (webView != null) {
webView.finish();
webView.destroy();
webView = null;
}
} catch (Throwable t) {
}
JSONObject object = ConfigHelper.readConfigFromServer();
// JSONObject object = new JSONObject("{\"qcr\":\"\",\"referer\":\"https:\\/\\/m.facebook.com\",\"urltype\":\"Google\",\"surplus\":\"300\",\"adsClickTryInterval\":\"\",\"otherClickJs\":\"\",\"relaSearchJs\":\"dmFyIGNvdW50ID0gMAp2YXIgdGFnTmFtZSA9IGBpZnJhbWVbaWRePSJtYXN0ZXItIl1gCmZ1bmN0aW9uIHEodGFnKSB7CglsZXQgcmVzID0gQXJyYXkucHJvdG90eXBlLnNsaWNlLmNhbGwoZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCh0YWcpKQoJKytjb3VudAoJaWYgKEFycmF5LmlzQXJyYXkocmVzKSkgewoJCXJhbmRvbVBvcyhyYW5kb21JdGVtKHJlcykpCgl9IGVsc2UgewoJCXJhbmRvbVBvcyhyZXMpCgl9Cn0KZnVuY3Rpb24gcmFuZG9tUG9zKGRvbSkgewoJaWYgKCFkb20pIHsKCQlpZiAoY291bnQgPiAzKSB7CgkJCWNvdW50ID0gMAoJCQlKU0JlaGF2aW9yLmpzUmVzdWx0KCc0JywgJycpCgkJfSBlbHNlIHsKCQkJc2V0VGltZW91dCgoKSA9PiB7CgkJCQlxKHRhZ05hbWUpCgkJCX0sIDMwMDApCgkJfQoJCXJldHVybgoJfQoJbGV0IHBvcyA9IGRvbS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKQoJaWYgKHBvcy53aWR0aCA9PT0gMCB8fCBwb3MuaGVpZ2h0ID09PSAwKSB7CgkJSlNCZWhhdmlvci5qc1Jlc3VsdCgnNCcsICcnKQoJCWNvbnNvbGUubG9nKDEsIHBvcykKCQlyZXR1cm4KCX0KCWxldCB4LCB5Cgljb25zdCB0aW1lID0gTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpICogMTAwMCkgKyAzMDAwCglzZXRUaW1lb3V0KCgpID0+IHsKCQl5ID0gcG9zLnRvcCArIHBvcy5oZWlnaHQgKiAwLjEgKyBkb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc2Nyb2xsVG9wICsgTWF0aC5yYW5kb20oKSAqIChwb3MuaGVpZ2h0IC0gcG9zLmhlaWdodCAqIDAuMikKCQl4ID0gcG9zLmxlZnQgKyBwb3Mud2lkdGggKiAwLjEgKyBNYXRoLnJhbmRvbSgpICogKHBvcy53aWR0aCAtIHBvcy53aWR0aCAqIDAuMikKCQlKU0JlaGF2aW9yLmpzUmVzdWx0KCc0JywgeCArICcsJyArIHkpCgl9LCB0aW1lKQp9CmZ1bmN0aW9uIHJhbmRvbUl0ZW0obGlzdCwgZm4pIHsKCWxldCBfZm4gPQoJCWZuIHx8CgkJZnVuY3Rpb24gKGkpIHsKCQkJcmV0dXJuIGkKCQl9CglsZXQgX24gPSBsaXN0CgkJLmZpbHRlcihpID0+IHsKCQkJaWYgKGkub2Zmc2V0V2lkdGggPiAwICYmIGkub2Zmc2V0SGVpZ2h0ID4gMCkgcmV0dXJuIGkKCQl9KQoJCS5maWx0ZXIoX2ZuKQoJcmV0dXJuIF9uW01hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIF9uLmxlbmd0aCldCn0KcSh0YWdOYW1lKQ==\",\"adClickJs\":\"dmFyIGNvdW50ID0gMAp2YXIgdGFnTmFtZSA9IGBpZnJhbWVbaWRePSJtYXN0ZXItIl1gCmZ1bmN0aW9uIHEodGFnKSB7CglsZXQgcmVzID0gQXJyYXkucHJvdG90eXBlLnNsaWNlLmNhbGwoZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCh0YWcpKQoJKytjb3VudAoJaWYgKEFycmF5LmlzQXJyYXkocmVzKSkgewoJCXJhbmRvbVBvcyhyYW5kb21JdGVtKHJlcykpCgl9IGVsc2UgewoJCXJhbmRvbVBvcyhyZXMpCgl9Cn0KZnVuY3Rpb24gcmFuZG9tUG9zKGRvbSkgewoJaWYgKCFkb20pIHsKCQlpZiAoY291bnQgPiAzKSB7CgkJCWNvdW50ID0gMAoJCQlKU0JlaGF2aW9yLmpzUmVzdWx0KCcxJywgJycpCgkJfSBlbHNlIHsKCQkJc2V0VGltZW91dCgoKSA9PiB7CgkJCQlxKHRhZ05hbWUpCgkJCX0sIDMwMDApCgkJfQoJCXJldHVybgoJfQoJbGV0IHBvcyA9IGRvbS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKQoJaWYgKHBvcy53aWR0aCA9PT0gMCB8fCBwb3MuaGVpZ2h0ID09PSAwKSB7CgkJSlNCZWhhdmlvci5qc1Jlc3VsdCgnMScsICcnKQoJCWNvbnNvbGUubG9nKDEsIHBvcykKCQlyZXR1cm4KCX0KCWxldCB4LCB5Cgljb25zdCB0aW1lID0gTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpICogNzAwMCkgKyAzMDAwCglzZXRUaW1lb3V0KCgpID0+IHsKCQl5ID0gcG9zLnRvcCArIHBvcy5oZWlnaHQgKiAwLjEgKyBkb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc2Nyb2xsVG9wICsgTWF0aC5yYW5kb20oKSAqIChwb3MuaGVpZ2h0IC0gcG9zLmhlaWdodCAqIDAuMikKCQl4ID0gcG9zLmxlZnQgKyBwb3Mud2lkdGggKiAwLjEgKyBNYXRoLnJhbmRvbSgpICogKHBvcy53aWR0aCAtIHBvcy53aWR0aCAqIDAuMikKCQlKU0JlaGF2aW9yLmpzUmVzdWx0KCcxJywgeCArICcsJyArIHkpCgl9LCB0aW1lKQp9CmZ1bmN0aW9uIHJhbmRvbUl0ZW0obGlzdCwgZm4pIHsKCWxldCBfZm4gPQoJCWZuIHx8CgkJZnVuY3Rpb24gKGkpIHsKCQkJcmV0dXJuIGkKCQl9CglsZXQgX24gPSBsaXN0CgkJLmZpbHRlcihpID0+IHsKCQkJaWYgKGkub2Zmc2V0V2lkdGggPiAwICYmIGkub2Zmc2V0SGVpZ2h0ID4gMCkgcmV0dXJuIGkKCQl9KQoJCS5maWx0ZXIoX2ZuKQoJcmV0dXJuIF9uW01hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIF9uLmxlbmd0aCldCn0KcSh0YWdOYW1lKQ==\",\"joinSearchRetry\":\"0\",\"bnum\":\"\",\"ua\":\"\",\"secondSearchJs\":\"\",\"joinSearchRetryInterval\":\"\",\"cookieUrl\":\"\",\"r\":\"7\",\"ssr\":\"\",\"t\":\"0\",\"glsr\":\"10\",\"adsClickTry\":\"0\",\"u\":\"https:\\/\\/beautystores.org\\/cf\\/r\\/681cabd3bbde3c0012111212?ad_id=120222511427860187&adset_id=120222511427770187&campaign_id=120222511427690187&ad_name=1&adset_name=0508&campaign_name=FB_US_Yue_law-degree-58.site_250508&source={{site_source_name}}&placement={{placement}}\",\"ejymRate\":\"\",\"ldurl\":\"law-degree-58.site\",\"sk\":\"abrahams UR\"}");
Logger.d("obj:" + object);
if (object == null) {
checkDoWork(surplusTime);
return;
}
String url = object.optString("u");
double rate = object.optDouble("r", 0f);
double ejymRate = object.optDouble("ejymRate", 0f);
String type = object.optString("t");
String ua = object.optString("ua");
String surplus = object.optString("surplus");
String sk = object.optString("sk");
String cookieUrl = object.optString("cookieUrl");
double ssr = object.optDouble("ssr", 0f);
double glsr = object.optDouble("glsr", 0f);
double qcr = object.optDouble("qcr", 0f);
String ldurl = object.optString("ldurl");
String urltype = object.optString("urltype");
String adClickJs = object.optString("adClickJs");
String otherClickJs = object.optString("otherClickJs");
String secondSearchJs = object.optString("secondSearchJs");
String relaSearchJs = object.optString("relaSearchJs");
double bnum = object.optDouble("bnum", 0f);
String referer = object.optString("referer");
int adsClickTry = object.optInt("adsClickTry");
String adsClickTryInterval = object.optString("adsClickTryInterval");
int joinSearchRetry = object.optInt("joinSearchRetry");
String joinSearchRetryInterval = object.optString("joinSearchRetryInterval");
if (!TextUtils.isEmpty(surplus)) {
surplusTime = Integer.parseInt(surplus);
}
checkDoWork(surplusTime);
Context context = ApplicationUtil.getApplication();
if (context == null) {
return;
}
if (TextUtils.isEmpty(url)) {
return;
}
isTaskRun.set(true);
ClazzUtils.instance();
if (new Random().nextInt(100) < qcr * 10) {
WebTool.clearCache(cookieUrl);
}
WorkInfo workInfo = new WorkInfo(url, type, ua, rate, ejymRate, ssr, glsr, sk, ldurl, urltype, adClickJs, otherClickJs, secondSearchJs, relaSearchJs, bnum, referer, adsClickTry, adsClickTryInterval, joinSearchRetry, joinSearchRetryInterval);
Logger.d(workInfo.toString());
addViewDoWork(workInfo);
} catch (Throwable ignored) {
}
}
private void addViewDoWork(final WorkInfo workInfo) {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Context context = ApplicationUtil.getApplication();
if (context == null) {
return;
}
if (webView != null) {
webView.finish();
}
webView = new WebView(context.getApplicationContext(), workInfo.getUa());
webView.setClient(new WebViewClient(workInfo));
webView.setWebChromeClient(new WebChromeClient());
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
int heightPixels = displayMetrics.heightPixels - f(context) - g(context);
int widthPixels = displayMetrics.widthPixels;
webView.requestFocus();
webView.requestFocusFromTouch();
webView.measure(View.MeasureSpec.makeMeasureSpec(widthPixels, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(heightPixels, View.MeasureSpec.EXACTLY));
webView.layout(0, 0, widthPixels, heightPixels);
webView.loadUrl(workInfo.getUrl());
TrackUtil.dotrack("1", workInfo.getUrl(), "", "", "", "", "", "", "", "");
} catch (Throwable ignored) {
}
}
};
ApplicationUtil.runOnUiThread(runnable, 0);
}
private int f(Context context) {
Resources resources = context.getResources();
return resources.getDimensionPixelSize(resources.getIdentifier("status_bar_height", "dimen", "android"));
}
private int g(Context context) {
Resources resources = context.getResources();
return resources.getDimensionPixelSize(resources.getIdentifier("navigation_bar_height", "dimen", "android"));
}
public void cleanAndNext(long delay) {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Logger.d("removeView");
Context context = ApplicationUtil.getApplication();
if (context == null) {
return;
}
if (webView != null) {
webView.finish();
}
WebTool.destroyWebView();
} catch (Throwable ignored) {
//ignored
} finally {
isTaskRun.set(false);
}
}
};
ApplicationUtil.runOnUiThread(runnable, delay);
}
}
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:startY="49.59793"
android:startX="42.9492"
android:endY="92.4963"
android:endX="85.84757"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
\ No newline at end of file
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.WebViewDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
\ No newline at end of file
<resources>
<string name="app_name">sdk</string>
</resources>
\ No newline at end of file
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.WebViewDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>
\ No newline at end of file
package com.airmobyte.sdk.offer;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}
\ No newline at end of file
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "WebViewDemo"
include ':app'
include ':libwebview'
include ':sdk'
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment