Browse Source

initial

master
steve.gao 6 months ago
commit
72bcc09bd6
  1. 24
      build.gradle
  2. 23
      gradle.properties
  3. 185
      gradlew
  4. 89
      gradlew.bat
  5. 2
      settings.gradle
  6. 1
      wechatvideo/.gitignore
  7. 46
      wechatvideo/build.gradle
  8. 21
      wechatvideo/proguard-rules.pro
  9. 26
      wechatvideo/src/androidTest/java/com/example/wechatvideo/ExampleInstrumentedTest.java
  10. 44
      wechatvideo/src/main/AndroidManifest.xml
  11. 1
      wechatvideo/src/main/assets/xposed_init
  12. 386
      wechatvideo/src/main/java/com/example/wechatvideo/HookVideoData825Entity.java
  13. 225
      wechatvideo/src/main/java/com/example/wechatvideo/HookVideoDataEntity.java
  14. 102
      wechatvideo/src/main/java/com/example/wechatvideo/MainActivity.java
  15. 864
      wechatvideo/src/main/java/com/example/wechatvideo/content/MultiprocessSharedPreferences.java
  16. 166
      wechatvideo/src/main/java/com/example/wechatvideo/handler/CommentListHandler.java
  17. 103
      wechatvideo/src/main/java/com/example/wechatvideo/handler/SearchUser825Handler.java
  18. 104
      wechatvideo/src/main/java/com/example/wechatvideo/handler/SearchUserHandler.java
  19. 125
      wechatvideo/src/main/java/com/example/wechatvideo/handler/UserVideo825Handler.java
  20. 129
      wechatvideo/src/main/java/com/example/wechatvideo/handler/UserVideoHandler.java
  21. 77
      wechatvideo/src/main/java/com/example/wechatvideo/model/StoredObject.java
  22. 146
      wechatvideo/src/main/java/com/example/wechatvideo/utils/SearchClassUtils.java
  23. 179
      wechatvideo/src/main/java/com/example/wechatvideo/utils/SpUtil.java
  24. 30
      wechatvideo/src/main/res/drawable-v24/ic_launcher_foreground.xml
  25. 170
      wechatvideo/src/main/res/drawable/ic_launcher_background.xml
  26. 30
      wechatvideo/src/main/res/drawable/ic_launcher_foreground.xml
  27. 74
      wechatvideo/src/main/res/drawable/new_icon_background.xml
  28. 118
      wechatvideo/src/main/res/layout/activity_main.xml
  29. 6
      wechatvideo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  30. 6
      wechatvideo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  31. 5
      wechatvideo/src/main/res/mipmap-anydpi-v26/new_icon.xml
  32. 5
      wechatvideo/src/main/res/mipmap-anydpi-v26/new_icon_round.xml
  33. BIN
      wechatvideo/src/main/res/mipmap-hdpi/ic_launcher.webp
  34. BIN
      wechatvideo/src/main/res/mipmap-hdpi/ic_launcher_round.webp
  35. BIN
      wechatvideo/src/main/res/mipmap-hdpi/new_icon.webp
  36. BIN
      wechatvideo/src/main/res/mipmap-hdpi/new_icon_foreground.webp
  37. BIN
      wechatvideo/src/main/res/mipmap-hdpi/new_icon_round.webp
  38. BIN
      wechatvideo/src/main/res/mipmap-mdpi/ic_launcher.webp
  39. BIN
      wechatvideo/src/main/res/mipmap-mdpi/ic_launcher_round.webp
  40. BIN
      wechatvideo/src/main/res/mipmap-mdpi/new_icon.webp
  41. BIN
      wechatvideo/src/main/res/mipmap-mdpi/new_icon_foreground.webp
  42. BIN
      wechatvideo/src/main/res/mipmap-mdpi/new_icon_round.webp
  43. BIN
      wechatvideo/src/main/res/mipmap-xhdpi/ic_launcher.webp
  44. BIN
      wechatvideo/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
  45. BIN
      wechatvideo/src/main/res/mipmap-xhdpi/new_icon.webp
  46. BIN
      wechatvideo/src/main/res/mipmap-xhdpi/new_icon_foreground.webp
  47. BIN
      wechatvideo/src/main/res/mipmap-xhdpi/new_icon_round.webp
  48. BIN
      wechatvideo/src/main/res/mipmap-xxhdpi/ic_launcher.webp
  49. BIN
      wechatvideo/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
  50. BIN
      wechatvideo/src/main/res/mipmap-xxhdpi/new_icon.webp
  51. BIN
      wechatvideo/src/main/res/mipmap-xxhdpi/new_icon_foreground.webp
  52. BIN
      wechatvideo/src/main/res/mipmap-xxhdpi/new_icon_round.webp
  53. BIN
      wechatvideo/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
  54. BIN
      wechatvideo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
  55. BIN
      wechatvideo/src/main/res/mipmap-xxxhdpi/new_icon.webp
  56. BIN
      wechatvideo/src/main/res/mipmap-xxxhdpi/new_icon_foreground.webp
  57. BIN
      wechatvideo/src/main/res/mipmap-xxxhdpi/new_icon_round.webp
  58. 16
      wechatvideo/src/main/res/values-night/themes.xml
  59. 10
      wechatvideo/src/main/res/values/colors.xml
  60. 4
      wechatvideo/src/main/res/values/strings.xml
  61. 16
      wechatvideo/src/main/res/values/themes.xml
  62. 17
      wechatvideo/src/test/java/com/example/wechatvideo/ExampleUnitTest.java

24
build.gradle

@ -0,0 +1,24 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:4.1.2"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

23
gradle.properties

@ -0,0 +1,23 @@
# 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. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec: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
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# 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

185
gradlew

@ -0,0 +1,185 @@
#!/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" "$@"

89
gradlew.bat

@ -0,0 +1,89 @@
@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

2
settings.gradle

@ -0,0 +1,2 @@
rootProject.name = "xp"
include ':wechatvideo'

1
wechatvideo/.gitignore

@ -0,0 +1 @@
/build

46
wechatvideo/build.gradle

@ -0,0 +1,46 @@
plugins {
id 'com.android.application'
}
android {
compileSdk 31
defaultConfig {
applicationId "com.example.wechatvideo"
minSdk 24
targetSdk 31
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
}
repositories {
maven { url "https://nexus.iinti.cn/repository/maven-public/" }
}
}
dependencies {
implementation "com.alibaba:fastjson:1.2.78" // fastjson sekiro也有
implementation 'com.google.guava:guava:30.1.1-jre'
implementation('cn.iinti.sekiro3.business:sekiro-business-api:1.1')
compileOnly 'de.robv.android.xposed:api:82'
compileOnly 'de.robv.android.xposed:api:82:sources'
implementation 'androidx.appcompat:appcompat:1.3.0-alpha02'
implementation 'com.google.android.material:material:1.1.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

21
wechatvideo/proguard-rules.pro

@ -0,0 +1,21 @@
# 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

26
wechatvideo/src/androidTest/java/com/example/wechatvideo/ExampleInstrumentedTest.java

@ -0,0 +1,26 @@
package com.example.wechatvideo;
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.example.wechatvideo", appContext.getPackageName());
}
}

44
wechatvideo/src/main/AndroidManifest.xml

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.wechatvideo">
<application
android:allowBackup="true"
android:icon="@drawable/new_icon_background"
android:label="@string/app_name"
android:roundIcon="@mipmap/new_icon"
android:supportsRtl="true"
android:theme="@style/Theme.Xp_module" >
<meta-data android:name="xposedmodule" android:value="true"/>
<meta-data android:name="xposeddescription" android:value="@string/xposed_description" />
<meta-data android:name="xposedminversion" android:value="53" />
<!--
name:MultiprocessSharedPreferences文件路径
authorities:provider标志,provider数据存储路径,一般以包名开头+.provider
exported:必须为true,否则不能跨进程通信-->
<provider
android:name="com.example.wechatvideo.content.MultiprocessSharedPreferences"
android:authorities="com.example.wechatvideo.provider"
android:exported="true"
android:enabled="true"
/>
<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>

1
wechatvideo/src/main/assets/xposed_init

@ -0,0 +1 @@
com.example.wechatvideo.HookVideoData825Entity

386
wechatvideo/src/main/java/com/example/wechatvideo/HookVideoData825Entity.java

@ -0,0 +1,386 @@
package com.example.wechatvideo;
import static com.example.wechatvideo.model.StoredObject.map1;
import android.app.Application;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import com.example.wechatvideo.handler.CommentListHandler;
import com.example.wechatvideo.handler.SearchUser825Handler;
import com.example.wechatvideo.handler.UserVideo825Handler;
import com.example.wechatvideo.model.StoredObject;
import com.example.wechatvideo.utils.SpUtil;
import java.util.Base64;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import cn.iinti.sekiro3.business.api.SekiroClient;
import cn.iinti.sekiro3.business.api.interfaze.HandlerRegistry;
import cn.iinti.sekiro3.business.api.interfaze.SekiroRequest;
import cn.iinti.sekiro3.business.api.interfaze.SekiroRequestInitializer;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
/**
* 8.0.25 微信视频号
* 支持搜索用户评论
*/
public class HookVideoData825Entity implements IXposedHookLoadPackage {
public static final String packageName = "com.tencent.mm";
public static final String TAG = "8.0.2 vxin -> ";
public static Context CONTEXT;
public static ClassLoader classLoader;
public static Object FEED_PROFILE; // 枚举类型获得 *视频主页
public static Object FinderMixSearchPresenter; // 搜索目标对象 一般只有一个 *视频搜索
public static Object i; // 视频载体model 很多个选其中之一即可 *视频评论
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if (packageName.equals(lpparam.packageName)){
Log.d(TAG, "handleLoadPackage: ?" + lpparam.packageName);
InitialObject(lpparam);
}
}
/**
* 对于翻页和二级评论对于参数的修改
* @param awClazz
* @param bClazz
* @param param
* @param map
* @return
*/
public void setLevelComments(Class<?> awClazz, Class<?> bClazz, XC_MethodHook.MethodHookParam param, Map<String, Object> map){
try {
Map<String, Object> paramsMap = (Map<String, Object>) map.get("params");
long commentId = (long) paramsMap.get("commentId"); // 获得评论id
long displayId = (long) paramsMap.get("displayId");
String lastBuffer = (String) paramsMap.get("lastBuffer"); // 获得翻页内容
if(lastBuffer != null){ // 有lastbuffer 就代表翻页
param.args[10] = true; // 修改true 这个影响二级评论翻页
Log.d(TAG, "setLevelComments: 需要处理参数 翻页");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
byte[] _lastBuffer = Base64.getDecoder().decode(lastBuffer);
param.args[2] = 6;
param.args[3] = 2;
param.args[6] = XposedHelpers.newInstance(bClazz, _lastBuffer); // 翻页需要构造一个对象用于翻页
param.args[14] = 1;
param.args[16] = 40864; // 翻页需要修改的值
}
if(commentId != 0 && displayId != 0){ // 是否调用二级评论
Log.d(TAG, "setLevelComments: 需要处理参数 获取二级评论");
Object aw = XposedHelpers.newInstance(awClazz); // 构造对象 给对象内部赋值
Object ajeB = XposedHelpers.getObjectField(XposedHelpers.getObjectField(aw, "field_actionInfo"), "ajeB"); // 获得一个commentinfo对象
XposedHelpers.callMethod(ajeB, "setCommentId", commentId); // 赋值commentId 当前二级评论的id
XposedHelpers.callMethod(ajeB, "setDisplayid", displayId); // 赋值displayId 当前二级评论id
param.args[7] = aw;
param.args[16] = 39712; // 二级评论需要
}
Log.d(TAG, "setLevelComments: 处理参数完毕");
}
}catch (Exception e){
e.printStackTrace();
Log.d(TAG, "setLevelComments: 处理参数过程中 异常");
}
}
/**
* 过滤一些评论需要的数据
* 拼装需要的数据
* @param object
* @param fcr
* @param map
*/
public void setCommentLists(Object object, Object fcr, Map<String, Object> map){
Object feedObject = XposedHelpers.getObjectField(fcr, "feedObject"); // 对象
LinkedList<?> linkedList = (LinkedList<?>) XposedHelpers.getObjectField(fcr, "ajlo"); // 评论列表
Object b = XposedHelpers.getObjectField(fcr, "lastBuffer"); // b 对象控制翻页内容 二级评论这个也会刷新 第一次是 目标评论的lastbuffer
// LinkedList<?> linkedList1 = (LinkedList<?>) XposedHelpers.callMethod(object, "ah", linkedList); // 晒一下评论信息
int commentCount = XposedHelpers.getIntField(fcr, "commentCount"); // 评论数量
int continueFlag = XposedHelpers.getIntField(fcr, "ajlp"); // 是有有数据
Map<String, Object> cache = new HashMap<>();
cache.put("cursor", b);
cache.put("commentTotalCount", commentCount);
cache.put("continueFlag", continueFlag);
cache.put("commentList", linkedList);
cache.put("videoDetail", feedObject);
map.put("data", cache);
}
/**
* 拦截处理评论信息
*/
public void hookComments(XC_LoadPackage.LoadPackageParam loadPackageParam){
// 评论调用类
Class<?> commentClazz = XposedHelpers.findClass("com.tencent.mm.plugin.finder.cgi.bj", loadPackageParam.classLoader);
// 翻页对象 不翻为null二级评论也用得上
Class<?> b = XposedHelpers.findClass("com.tencent.mm.cc.b", loadPackageParam.classLoader);
// 二级评论对象 需要构造
Class<?> aw = XposedHelpers.findClass("com.tencent.mm.plugin.finder.storage.aw", loadPackageParam.classLoader);
// ?
Class<?> ccz = XposedHelpers.findClass("com.tencent.mm.protocal.protobuf.ccz", loadPackageParam.classLoader);
// 为null 没啥用处
Class<?> brj = XposedHelpers.findClass("com.tencent.mm.protocal.protobuf.brj", loadPackageParam.classLoader);
// null 没啥用
Class<?> k = XposedHelpers.findClass("kotlin.g.b.k", loadPackageParam.classLoader);
// *** hook 构造函数
XposedHelpers.findAndHookConstructor(commentClazz, long.class, String.class, int.class, int.class, String.class,
boolean.class, b, aw, long.class, String.class,
boolean.class, boolean.class, String.class, ccz, int.class,
brj, int.class, k, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
String uuid = StoredObject.getUuidKey();
Log.d(TAG, "beforeHookedMethod: args 0 => " + param.args[0]);
Log.d(TAG, "beforeHookedMethod: args 1 => " + param.args[1]);
Log.d(TAG, "beforeHookedMethod: args 2 => " + param.args[2]);
Log.d(TAG, "beforeHookedMethod: args 3 => " + param.args[3]);
if(uuid !=null && uuid.contains(CommentListHandler.api)){ // 确定为评论的map
Map<String, Object> map = map1.get(uuid);
setLevelComments(aw, b, param, map); // 修改参数
}
Log.d(TAG, "beforeHookedMethod: 触发到构造函数");
super.beforeHookedMethod(param);
}
}
);
// *** hook 回调函数
XposedHelpers.findAndHookMethod(commentClazz, "b", int.class, int.class, String.class,
XposedHelpers.findClass("com.tencent.mm.protocal.protobuf.fcr", loadPackageParam.classLoader),
XposedHelpers.findClass("com.tencent.mm.av.p", loadPackageParam.classLoader),
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Object object = param.thisObject;
Object response = param.args[3]; // 最终的结果 但需要处理一下
String uuid = StoredObject.getUuidKey();
Log.d(TAG, "触发到回调函数 afterHookedMethod: uuidk is " + uuid);
if (uuid != null && uuid.contains(CommentListHandler.api)){ // 确定为评论的map
Map<String, Object> map = map1.get(uuid);
setCommentLists(object, response, map);
synchronized (uuid){
uuid.notify(); // 通知消息
Log.d(TAG, "beforeHookedMethod: 封装对象成功");
}
}
}
});
}
/**
* 根据类型获得视频种类
* @param cdx
* @param map
* @return
*/
public void setVideoList(Object cdx, Map<String, Object> map){
String type = (String) map.get("search_order");
Log.d(TAG, "setVideoList: 请求获取目标为 => " + type);
LinkedList<?> videoList = (LinkedList<?>) XposedHelpers.getObjectField(cdx, "Ebz"); // 视频
LinkedList<?> userList = (LinkedList<?>) XposedHelpers.getObjectField(cdx, "RVP"); // 用户
LinkedList<?> ajKA = (LinkedList<?>) XposedHelpers.getObjectField(cdx, "ajKA"); // 第一次这个会存放两个列表 后续才会到ebz中
map.put("user", userList); // 获取用户列表
if(ajKA.size() > 0){
for(Object ajKA_object:ajKA){
String ajKg = (String) XposedHelpers.getObjectField(ajKA_object, "ajKg"); // 这个字段为 综合 确定为什么搜索类型
Log.d(TAG, "getVideoList: 获取当前搜索类型为 => " + ajKg);
if(type.equals(ajKg)) {
LinkedList<?> aikW = (LinkedList<?>) XposedHelpers.getObjectField(ajKA_object, "aikW"); // 视频列表
map.put("data", aikW);
}
}
}else{ // 一般是第二页这个才会有值
map.put("data", videoList);
}
}
/**
*
* @param lpparam
*/
public void hookSearch(XC_LoadPackage.LoadPackageParam lpparam){
XposedHelpers.findAndHookMethod("com.tencent.mm.plugin.finder.cgi.du", lpparam.classLoader, "d",
int.class, int.class, int.class, String.class, XposedHelpers.findClass("com.tencent.mm.network.t", lpparam.classLoader), byte[].class
, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
Log.d(TAG, "beforeHookedMethod: 进入搜索信息 数据 ***********************");
Log.d(TAG, "beforeHookedMethod: 获得参数1" + param.args[0]);
Log.d(TAG, "beforeHookedMethod: 获得参数2" + param.args[1]);
Log.d(TAG, "beforeHookedMethod: 获得参数3" + param.args[2]);
Log.d(TAG, "beforeHookedMethod: 获得参数4" + param.args[3]);
Log.d(TAG, "beforeHookedMethod: 获取目标对象 长度 *************************");
Object O = param.thisObject;
Object cdx = XposedHelpers.getObjectField(
XposedHelpers.getObjectField(
XposedHelpers.getObjectField(O, "rfO"), "qVP")
, "qVU"); // response 对象
LinkedList videoList = (LinkedList) XposedHelpers.getObjectField(cdx, "Ebz"); // 视频
LinkedList userList = (LinkedList) XposedHelpers.getObjectField(cdx, "RVP"); // 用户
LinkedList ajKA = (LinkedList) XposedHelpers.getObjectField(cdx, "ajKA"); // 第一次这个会存放两个列表 后续才会到ebz中
int offset = XposedHelpers.getIntField(cdx, "offset");
Log.d(TAG, "beforeHookedMethod: offset is " + offset);
Log.d(TAG, "beforeHookedMethod: video size " + videoList.size());
Log.d(TAG, "beforeHookedMethod: user size " + userList.size());
Log.d(TAG, "beforeHookedMethod: ajKA size " + ajKA.size());
Log.d(TAG, "beforeHookedMethod: 测试数据*******************************");
String uuid = StoredObject.getUuidKey();
Log.d(TAG, "afterHookedMethod: uuidk is " + uuid);
if (uuid != null){
Map<String, Object> map = map1.get(uuid);
setVideoList(cdx, map);
synchronized (uuid){
uuid.notify(); // 通知消息
Log.d(TAG, "beforeHookedMethod: 封装对象成功");
}
}
}
});
}
/**
* hook 用户主页
* @param lpparam
*/
public void hookUser(XC_LoadPackage.LoadPackageParam lpparam){
// 拦截数据
XposedHelpers.findAndHookMethod("com.tencent.mm.plugin.finder.cgi.ee", lpparam.classLoader, "d",
int.class, int.class, int.class, String.class, XposedHelpers.findClass("com.tencent.mm.network.t", lpparam.classLoader),
byte[].class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
Object o = param.thisObject;
Object finderUserPageResponse = XposedHelpers.getObjectField(XposedHelpers.getObjectField(XposedHelpers.getObjectField(o, "rr"), "qVP"), "qVU");
LinkedList<?> f = (LinkedList<?>) XposedHelpers.getObjectField(finderUserPageResponse, "object");
Log.d(TAG, "beforeHookedMethod: LinkedList size is " + f.size());
String uuid = StoredObject.getUuidKey();
Log.d(TAG, "afterHookedMethod: uuidk is " + uuid);
if (uuid != null && f.size() > 0){
Map<String, Object> map = map1.get(uuid);
map.put("data", f); // 封装消息
synchronized (uuid){
uuid.notify(); // 通知消息
Log.d(TAG, "beforeHookedMethod: 封装对象成功");
}
}
}
});
// 翻页修改目标参数
XposedHelpers.findAndHookConstructor("com.tencent.mm.plugin.finder.cgi.ee", lpparam.classLoader,
String.class, long.class, XposedHelpers.findClass("com.tencent.mm.cc.b", lpparam.classLoader),
int.class, XposedHelpers.findClass("com.tencent.mm.protocal.protobuf.ccz", lpparam.classLoader),
int.class, long.class, boolean.class, String.class, long.class, Integer.class, Long.class, String.class, boolean.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
String userId = (String) param.args[0];
long maxId = (long) param.args[1];
int a = (int) param.args[3]; // 翻页为2 不翻为1
long b = (long) param.args[9]; // -1 翻页变为0
String uuid = StoredObject.getUuidKey();
Log.d(TAG, "构造函数中拦截: userId is => " + userId);
if (uuid != null){
param.args[1] = Long.valueOf(uuid);
if(!Objects.equals(uuid, "0")){
param.args[3] = 2;
param.args[9] = 0;
Log.d(TAG, "afterHookedMethod: 修改翻页内容 ");
}
Log.d(TAG, "构造函数中拦截: 目标偏移量修改完毕 => " + uuid);
}
}
});
}
public void InitialObject(XC_LoadPackage.LoadPackageParam lpparam){
Log.d(TAG, "InitialObject: 进入初始化");
XposedHelpers.findAndHookMethod(Application.class, "attach",
Context.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
CONTEXT = (Context) param.args[0];
classLoader = lpparam.classLoader;
Log.d(TAG, "afterHookedMethod: 进入hook 某组");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {// TODO 这里需要兼容
String paramName = Application.getProcessName(); // 获取进程名程 只对主进程进行hook
Log.d(TAG, "afterHookedMethod: " + paramName);
if ("com.tencent.mm".equals(paramName)) { // 只hook主进程
initSekiroClient(); // 服务注入 网络波动
Log.d(TAG, "afterHookedMethod: 注 入 服 务 成功");
hookSearch(lpparam);
hookUser(lpparam);
hookComments(lpparam);
}
}
}
});
}
private void initSekiroClient() {
Log.d(TAG, "initSekiroClient: 启动 sekiro ing");
String groupName = "vxin";
String clientId = (String) SpUtil.readObjectByProvider(CONTEXT, "vx_client", String.class);
String serverHost = (String) SpUtil.readObjectByProvider(CONTEXT, "vx_server", String.class);
int serverPort = 5612;
Log.d(TAG, String.format("initSekiroClient: 获取provier数据 clientId is %s; serverHost is %s",
clientId, serverHost));
if ("".equals(serverHost)){
Log.d(TAG, "initSekiroClient: provider 初始化失败");
}else{
Log.d(TAG, "initSekiroClient: clientId is " + clientId);
SekiroClient sekiroClient = new SekiroClient(groupName, clientId, serverHost, serverPort);
// 注册服务
sekiroClient.setupSekiroRequestInitializer(new SekiroRequestInitializer() {
@Override
public void onSekiroRequest(SekiroRequest sekiroRequest, HandlerRegistry handlerRegistry) {
handlerRegistry.registerSekiroHandler(new SearchUser825Handler()); // 搜索
handlerRegistry.registerSekiroHandler(new UserVideo825Handler()); // 用户
handlerRegistry.registerSekiroHandler(new CommentListHandler()); // 评论
}
}).start();
Log.d(TAG, "initSekiroClient: 测试启动rpc服务");
}
}
}

225
wechatvideo/src/main/java/com/example/wechatvideo/HookVideoDataEntity.java

@ -0,0 +1,225 @@
package com.example.wechatvideo;
import static com.example.wechatvideo.model.StoredObject.map1;
import android.app.Application;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import com.example.wechatvideo.handler.SearchUserHandler;
import com.example.wechatvideo.handler.UserVideoHandler;
import com.example.wechatvideo.model.StoredObject;
import com.example.wechatvideo.utils.SpUtil;
import java.util.LinkedList;
import java.util.Map;
import cn.iinti.sekiro3.business.api.SekiroClient;
import cn.iinti.sekiro3.business.api.interfaze.HandlerRegistry;
import cn.iinti.sekiro3.business.api.interfaze.SekiroRequest;
import cn.iinti.sekiro3.business.api.interfaze.SekiroRequestInitializer;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
/**
* 微信视频号hookdemo
*/
public class HookVideoDataEntity implements IXposedHookLoadPackage {
public static final String TAG = "wxdemo";
private final String TAGLOG = "log point -> ";
public static ClassLoader classLoader;
public static Object FEED_PROFILE; // 构造函数第一个 枚举类型
public static Context Main_context; // 随时修改classloader
public static Object FinderMixSearchPresenter; // 搜索目标对象 一般只有一个
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if ("com.tencent.mm".equals(lpparam.packageName)){
InitialObject(lpparam);
}
}
/**
* 搜索接口 回执信息拦截
* @param lpparam
*/
public void hook_Video(XC_LoadPackage.LoadPackageParam lpparam){
// hook 搜索测试 回执函数测试
XposedHelpers.findAndHookMethod("com.tencent.mm.plugin.finder.cgi.bm", lpparam.classLoader,
"onGYNetEnd",
int.class, int.class, int.class, String.class,
XposedHelpers.findClass("com.tencent.mm.network.q", lpparam.classLoader), byte[].class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
Object o = param.thisObject;
Log.d(TAG, "beforeHookedMethod: hook 搜索接口");
Object avd = XposedHelpers.callMethod(XposedHelpers.getObjectField(o, "hYT"), "aGA");
LinkedList videoList = (LinkedList) XposedHelpers.getObjectField(avd, "rKi"); // 视频
LinkedList userList = (LinkedList) XposedHelpers.getObjectField(avd, "wND"); // 用户
// Log.d(TAG, "beforeHookedMethod: video likedList 0 is " + JSONObject.toJSONString(videoList.get(0)));
// Log.d(TAG, "beforeHookedMethod: user likedList 0 is " + JSONObject.toJSONString(userList.get(0)));
String uuid = StoredObject.getUuidKey();
Log.d(TAG, "hook_Video: uuid is " + uuid);
Log.d(TAG, "beforeHookedMethod: size is "+ videoList.size());
if (uuid != null && videoList.size() > 0){
Map<String, Object> map = map1.get(uuid);
map.put("data", videoList); // 封装消息 视频
map.put("user", userList); // 封装消息 用户
synchronized (uuid){
uuid.notify(); // 通知消息
Log.d(TAG, "hook_Video: 封装对象成功");
}
}
super.beforeHookedMethod(param);
}
});
}
/**
* 用户接口 回执信息拦截
* @param lpparam
*/
public void hook_User(XC_LoadPackage.LoadPackageParam lpparam){
// 测试一下获得的 FinderUserPageResponse 对象
XposedHelpers.findAndHookMethod("com.tencent.mm.plugin.finder.cgi.bs", lpparam.classLoader,
"onGYNetEnd",
int.class, int.class, int.class, String.class, XposedHelpers.findClass("com.tencent.mm.network.q", lpparam.classLoader),
byte[].class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
Object o = param.thisObject;
Object FinderUserPageResponse = XposedHelpers.callMethod(XposedHelpers.getObjectField(o, "rr"), "aGA");
// Log.d(TAG, "afterHookedMethod: 1输出对象的结果: " + JSONObject.toJSONString(XposedHelpers.getObjectField(FinderUserPageResponse, "contact")));
LinkedList f = (LinkedList) XposedHelpers.getObjectField(FinderUserPageResponse, "object");
Log.d(TAG, "beforeHookedMethod: LinkedList size is " + f.size());
// Log.d(TAG, "beforeHookedMethod: likedList 0 is " + JSONObject.toJSONString(f.get(0)));
String uuid = StoredObject.getUuidKey();
Log.d(TAG, "afterHookedMethod: uuidk is " + uuid);
if (uuid != null && f.size() > 0){
Map<String, Object> map = map1.get(uuid);
map.put("data", f); // 封装消息
synchronized (uuid){
uuid.notify(); // 通知消息
Log.d(TAG, "beforeHookedMethod: 封装对象成功");
}
}
super.beforeHookedMethod(param);
}
});
// 每次翻页 部分参数需要改变
XposedHelpers.findAndHookConstructor("com.tencent.mm.plugin.finder.cgi.bs", lpparam.classLoader,
String.class, long.class, XposedHelpers.findClass("com.tencent.mm.bw.b", lpparam.classLoader), int.class,
XposedHelpers.findClass("com.tencent.mm.protocal.protobuf.auu", lpparam.classLoader), new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
String userId = (String) param.args[0];
long maxId = (long) param.args[1];
String uuid = StoredObject.getUuidKey();
Log.d(TAG, "构造函数中拦截: userId is => " + userId);
if (uuid != null){
param.args[1] = Long.valueOf(uuid);
Log.d(TAG, "构造函数中拦截: 目标偏移量修改完毕 => " + uuid);
}
super.beforeHookedMethod(param);
}
});
}
/**
* 入口hook函数
* @param lpparam
*/
private void InitialObject(XC_LoadPackage.LoadPackageParam lpparam){
XposedHelpers.findAndHookMethod(Application.class,
"attach",
Context.class,
new XC_MethodHook(){
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Main_context = (Context) param.args[0];
classLoader = lpparam.classLoader;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {// TODO 这里需要兼容
String paramName = Application.getProcessName(); // 获取进程名程 只对主进程进行hook
Log.d(TAG, "afterHookedMethod: " + paramName);
if("com.tencent.mm".equals(paramName)){ // 只hook主进程
initSekiroClient(); // 服务注入 网络波动
Log.d(TAG, "afterHookedMethod: 注 入 服 务 成功");
hook_Video(lpparam);
hook_User(lpparam);
}
}else{
Log.d(TAG, "afterHookedMethod: ???");
// ApplicationInfo applicationInfo = Main_context.getApplicationInfo();
// String processName = applicationInfo.processName;
// Log.d(TAG, "afterHookedMethod: " + processName);
}
super.afterHookedMethod(param);
}
});
}
/**
* 自定义 客户端ID以及服务器地址
*/
private void initSekiroClient() {
Log.d(TAG, "initSekiroClient: 启动 sekiro ing");
String groupName = (String) SpUtil.readObjectByProvider(Main_context, "vx_group_name", String.class);
String clientId = (String) SpUtil.readObjectByProvider(Main_context, "vx_client", String.class);
String serverHost = (String) SpUtil.readObjectByProvider(Main_context, "vx_server", String.class);
int serverPort = 5612;
Log.d(TAG, String.format("initSekiroClient: 获取provier数据 groupName is %s; clientId is %s; serverHost is %s",
groupName, clientId, serverHost));
if ("".equals(serverHost)){
Log.d(TAG, "initSekiroClient: provider 初始化失败");
}else{
Log.d(TAG, "initSekiroClient: clientId is " + clientId);
SekiroClient sekiroClient = new SekiroClient(groupName, clientId, serverHost, serverPort);
// 注册服务
sekiroClient.setupSekiroRequestInitializer(new SekiroRequestInitializer() {
@Override
public void onSekiroRequest(SekiroRequest sekiroRequest, HandlerRegistry handlerRegistry) {
handlerRegistry.registerSekiroHandler(new UserVideoHandler()); // 用户
handlerRegistry.registerSekiroHandler(new SearchUserHandler()); // 搜索
}
}).start();
Log.d(TAG, "initSekiroClient: 测试启动rpc服务");
XposedBridge.log(String.format("sekiro启动成功: 获取provier数据 groupName is %s; clientId is %s; serverHost is %s",
groupName, clientId, serverHost));
}
}
}

102
wechatvideo/src/main/java/com/example/wechatvideo/MainActivity.java

@ -0,0 +1,102 @@
package com.example.wechatvideo;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.example.wechatvideo.content.MultiprocessSharedPreferences;
import com.example.wechatvideo.utils.SpUtil;
public class MainActivity extends AppCompatActivity {
private EditText clientId, server;
private Button update;
private TextView clientIdText, serverText, groupName;
private String vx_group_name = "vxin";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 设置权限
MultiprocessSharedPreferences.setAuthority("com.example.wechatvideo.provider");
// 初始化 视图
initView(this);
clientIdText = (TextView) findViewById(R.id.get_phone);
serverText = (TextView) findViewById(R.id.get_web);
groupName = (TextView) findViewById(R.id.get_group);
update = (Button) findViewById(R.id.btn_update);
update.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 获取当前修改的
String clientId = ((EditText) findViewById(R.id.set_phone)).getText().toString().trim();
String server = ((EditText) findViewById(R.id.set_web)).getText().toString().trim();
String address = String.format("%s:%s", server, "5612");
SpUtil.putString(MainActivity.this, "vx_client", clientId);
SpUtil.putString(MainActivity.this, "vx_server" , server);
SpUtil.putString(MainActivity.this, "vx_group_name", vx_group_name); // 初始化group_name
// 展示区域
clientIdText.setText(clientId);
groupName.setText(vx_group_name);
serverText.setText(address);
Toast.makeText(MainActivity.this.getApplicationContext(),
"更新配置成功, 重启微信生效!",
Toast.LENGTH_SHORT).show();
}
});
}
/**
* 初始化视图
* @param context
*/
public void initView(Context context){
// 可编辑区域
clientId = (EditText) findViewById(R.id.set_phone);
server = (EditText) findViewById(R.id.set_web);
// 展示区域
clientIdText = (TextView) findViewById(R.id.get_phone);
serverText = (TextView) findViewById(R.id.get_web);
groupName = (TextView) findViewById(R.id.get_group);
String clientId_text = (String)SpUtil.readObjectByProvider(context, "vx_client", String.class);
String address = String.format("%s:%s", (String) SpUtil.readObjectByProvider(context, "vx_server", String.class), "5612");
// 从provider中读出结果 到展示区域
clientIdText.setText(clientId_text);
serverText.setText((String)SpUtil.readObjectByProvider(context, "vx_server", String.class));
groupName.setText(vx_group_name);
// 从provider中读出结果 到可编辑区域
clientId.setText((String)SpUtil.readObjectByProvider(context, "vx_client", String.class));
server.setText((String)SpUtil.readObjectByProvider(context, "vx_server", String.class));
}
}

864
wechatvideo/src/main/java/com/example/wechatvideo/content/MultiprocessSharedPreferences.java

@ -0,0 +1,864 @@
package com.example.wechatvideo.content;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.UriMatcher;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.util.Log;
import androidx.annotation.NonNull;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
/**
* 使用ContentProvider实现多进程SharedPreferences读写;<br>
* 1ContentProvider天生支持多进程访问<br>
* 2使用内部私有BroadcastReceiver实现多进程OnSharedPreferenceChangeListener监听<br>
*
* 使用方法AndroidManifest.xml中添加provider申明<br>
* <pre>
* &lt;provider android:name="com.android.zgj.utils.MultiprocessSharedPreferences"
* android:authorities="com.android.zgj.MultiprocessSharedPreferences"
* android:process="com.android.zgj.MultiprocessSharedPreferences"
* android:exported="false" /&gt;
* &lt;!-- authorities属性里面最好使用包名做前缀apk在安装时authorities同名的provider需要校验签名否则无法安装--!/&gt;<br>
* </pre>
*
* ContentProvider方式实现要注意<br>
* 1当ContentProvider所在进程android.os.Process.killProcess(pid)会导致整个应用程序完全意外退出或者ContentProvider所在进程重启<br>
* 重启报错信息Acquiring provider <processName> for user 0: existing object's process dead<br>
* 2如果设备处在安全模式只有系统自带的ContentProvider才能被正常解析使用因此put值时默认返回falseget值时默认返回null<br>
*
* 其他方式实现SharedPreferences的问题<br>
* 使用FileLock和FileObserver也可以实现多进程SharedPreferences读写但是维护成本高需要定期对照系统实现更新新的特性
*
* 引用git:https://github.com/seven456/MultiprocessSharedPreferences
*/
public class MultiprocessSharedPreferences extends ContentProvider implements SharedPreferences {
private static final String TAG_CONTENT = "MultiprocessSharedPreferences";
public static final boolean DEBUG = true; // TODO
private Context mContext;
private String mName;
private int mMode;
private boolean mIsSafeMode;
private static final Object CONTENT = new Object();
private WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners;
private BroadcastReceiver mReceiver;
private static String AUTHORITY;
private static volatile Uri AUTHORITY_URI;
private UriMatcher mUriMatcher;
private static final String KEY = "value";
private static final String KEY_NAME = "name";
private static final String PATH_WILDCARD = "*/";
private static final String PATH_GET_ALL = "getAll";
private static final String PATH_GET_STRING = "getString";
private static final String PATH_GET_INT = "getInt";
private static final String PATH_GET_LONG = "getLong";
private static final String PATH_GET_FLOAT = "getFloat";
private static final String PATH_GET_BOOLEAN = "getBoolean";
private static final String PATH_CONTAINS = "contains";
private static final String PATH_APPLY = "apply";
private static final String PATH_COMMIT = "commit";
private static final String PATH_REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER = "registerOnSharedPreferenceChangeListener";
private static final String PATH_UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER = "unregisterOnSharedPreferenceChangeListener";
private static final String PATH_GET_STRING_SET = "getStringSet";
private static final int GET_ALL = 1;
private static final int GET_STRING = 2;
private static final int GET_INT = 3;
private static final int GET_LONG = 4;
private static final int GET_FLOAT = 5;
private static final int GET_BOOLEAN = 6;
private static final int CONTAINS = 7;
private static final int APPLY = 8;
private static final int COMMIT = 9;
private static final int REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER = 10;
private static final int UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER = 11;
private static final int GET_STRING_SET = 12;
private HashMap<String, Integer> mListenersCount;
private static class ReflectionUtil {
public static ContentValues contentValuesNewInstance(HashMap<String, Object> values) {
try {
Constructor<ContentValues> c = ContentValues.class.getDeclaredConstructor(new Class[] { HashMap.class }); // hide
c.setAccessible(true);
return c.newInstance(values);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
}
}
public static Editor editorPutStringSet(Editor editor, String key, Set<String> values) {
try {
Method method = editor.getClass().getDeclaredMethod("putStringSet", new Class[] { String.class, Set.class }); // Android 3.0
return (Editor) method.invoke(editor, key, values);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("unchecked")
public static Set<String> sharedPreferencesGetStringSet(SharedPreferences sharedPreferences, String key, Set<String> values) {
try {
Method method = sharedPreferences.getClass().getDeclaredMethod("getStringSet", new Class[] { String.class, Set.class }); // Android 3.0
return (Set<String>) method.invoke(sharedPreferences, key, values);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
public static void editorApply(Editor editor) {
try {
Method method = editor.getClass().getDeclaredMethod("apply"); // Android 2.3
method.invoke(editor);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
public static String contentProvidermAuthority(ContentProvider contentProvider) {
try {
Field mAuthority = ContentProvider.class.getDeclaredField("mAuthority"); // Android 5.0
mAuthority.setAccessible(true);
return (String) mAuthority.get(contentProvider);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
// 如果设备处在安全模式只有系统自带的ContentProvider才能被正常解析使用
private boolean isSafeMode(Context context) {
boolean isSafeMode = false;
try {
isSafeMode = context.getPackageManager().isSafeMode();
// 解决崩溃
// java.lang.RuntimeException: Package manager has died
// at android.app.ApplicationPackageManager.isSafeMode(ApplicationPackageManager.java:820)
} catch (RuntimeException e) {
if (!isPackageManagerHasDiedException(e)) {
throw e;
}
}
return isSafeMode;
}
/**
* 可选设置AUTHORITY不用在初始化时遍历程序的AndroidManifest.xml文件获取android:authorities的值减少初始化时间提高运行速度
* @param authority
*/
public static void setAuthority(String authority) {
AUTHORITY = authority;
}
@SuppressLint("LongLogTag")
private boolean checkInitAuthority(Context context) {
if (AUTHORITY_URI == null) {
synchronized (MultiprocessSharedPreferences.this) {
if (AUTHORITY_URI == null) {
if(AUTHORITY == null) {
if (Build.VERSION.SDK_INT >= 21 && this instanceof ContentProvider) {
AUTHORITY = ReflectionUtil.contentProvidermAuthority(this);
} else {
PackageInfo packageInfos = null;
try {
packageInfos = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PROVIDERS);
} catch (PackageManager.NameNotFoundException e) {
if (DEBUG) {
e.printStackTrace();
}
} catch (RuntimeException e) {
if (!isPackageManagerHasDiedException(e)) {
throw new RuntimeException("checkInitAuthority", e);
}
}
if (packageInfos != null && packageInfos.providers != null) {
for (ProviderInfo providerInfo : packageInfos.providers) {
if (providerInfo.name.equals(MultiprocessSharedPreferences.class.getName())) {
AUTHORITY = providerInfo.authority;
break;
}
}
}
}
}
if (DEBUG) {
if (AUTHORITY == null) {
throw new IllegalArgumentException("'AUTHORITY' initialize failed, Unable to find explicit provider class " + MultiprocessSharedPreferences.class.getName() + "; have you declared this provider in your AndroidManifest.xml?");
} else {
Log.d(TAG_CONTENT, "checkInitAuthority.AUTHORITY = " + AUTHORITY);
}
}
AUTHORITY_URI = Uri.parse(ContentResolver.SCHEME_CONTENT + "://" + AUTHORITY);
}
}
}
return AUTHORITY_URI != null;
}
private boolean isPackageManagerHasDiedException(Throwable e) {
// 1packageManager.getPackageInfo
// java.lang.RuntimeException: Package manager has died
// at android.app.ApplicationPackageManager.getPackageInfo(ApplicationPackageManager.java:80)
// ...
// Caused by: android.os.DeadObjectException
// at android.os.BinderProxy.transact(Native Method)
// at android.content.pm.IPackageManager$Stub$Proxy.getPackageInfo(IPackageManager.java:1374)
// 2contentResolver.query
// java.lang.RuntimeException: Package manager has died
// at android.app.ApplicationPackageManager.resolveContentProvider(ApplicationPackageManager.java:636)
// at android.app.ActivityThread.acquireProvider(ActivityThread.java:4750)
// at android.app.ContextImpl$ApplicationContentResolver.acquireUnstableProvider(ContextImpl.java:2234)
// at android.content.ContentResolver.acquireUnstableProvider(ContentResolver.java:1425)
// at android.content.ContentResolver.query(ContentResolver.java:445)
// at android.content.ContentResolver.query(ContentResolver.java:404)
// at com.qihoo.storager.MultiprocessSharedPreferences.getValue(AppStore:502)
// ...
// Caused by: android.os.TransactionTooLargeException
// at android.os.BinderProxy.transact(Native Method)
// at android.content.pm.IPackageManager$Stub$Proxy.resolveContentProvider(IPackageManager.java:2500)
// at android.app.ApplicationPackageManager.resolveContentProvider(ApplicationPackageManager.java:634)
if (e instanceof RuntimeException
&& e.getMessage() != null
&& e.getMessage().contains("Package manager has died")) {
Throwable cause = getLastCause(e);
if (cause instanceof DeadObjectException || cause.getClass().getName().equals("android.os.TransactionTooLargeException")) {
return true;
}
}
return false;
}
private boolean isUnstableCountException(Throwable e) {
// java.lang.RuntimeException: java.lang.IllegalStateException: unstableCount < 0: -1
// at com.qihoo.storager.MultiprocessSharedPreferences.getValue(AppStore:459)
// at com.qihoo.storager.MultiprocessSharedPreferences.getBoolean(AppStore:282)
// ...
// Caused by: java.lang.IllegalStateException: unstableCount < 0: -1
// at android.os.Parcel.readException(Parcel.java:1628)
// at android.os.Parcel.readException(Parcel.java:1573)
// at android.app.ActivityManagerProxy.refContentProvider(ActivityManagerNative.java:3680)
// at android.app.ActivityThread.releaseProvider(ActivityThread.java:5052)
// at android.app.ContextImpl$ApplicationContentResolver.releaseUnstableProvider(ContextImpl.java:2036)
// at android.content.ContentResolver.query(ContentResolver.java:534)
// at android.content.ContentResolver.query(ContentResolver.java:435)
// at com.qihoo.storager.MultiprocessSharedPreferences.a(AppStore:452)
if (e instanceof RuntimeException
&& e.getMessage() != null
&& e.getMessage().contains("unstableCount < 0: -1")) {
if (getLastCause(e) instanceof IllegalStateException) {
return true;
}
}
return false;
}
/**
* 获取异常栈中最底层的 Throwable Cause
*
* @param tr
* @return
*/
private Throwable getLastCause(Throwable tr) {
Throwable cause = tr.getCause();
Throwable causeLast = null;
while (cause != null) {
causeLast = cause;
cause = cause.getCause();
}
if (causeLast == null) {
causeLast = new Throwable();
}
return causeLast;
}
/**
* mode不使用{@link Context#MODE_MULTI_PROCESS}特可以支持多进程了
*
* @param mode
*
* @see Context#MODE_PRIVATE
* @see Context#MODE_WORLD_READABLE
* @see Context#MODE_WORLD_WRITEABLE
*/
public static SharedPreferences getSharedPreferences(Context context, String name, int mode) {
return new MultiprocessSharedPreferences(context, name, mode);
}
/**
* @deprecated 此默认构造函数只用于父类ContentProvider在初始化时使用
*/
// @Deprecated TODO: 不知道为啥加这个会报错
public MultiprocessSharedPreferences() {
}
private MultiprocessSharedPreferences(Context context, String name, int mode) {
mContext = context;
mName = name;
mMode = mode;
mIsSafeMode = isSafeMode(mContext);
}
@SuppressWarnings("unchecked")
@Override
public Map<String, ?> getAll() {
Map<String, ?> value = (Map<String, ?>) getValue(PATH_GET_ALL, null, null);
return value == null ? new HashMap<String, Object>() : value;
}
@Override
public String getString(String key, String defValue) {
return (String) getValue(PATH_GET_STRING, key, defValue);
}
// @Override // Android 3.0
@SuppressWarnings("unchecked")
public Set<String> getStringSet(String key, Set<String> defValues) {
return (Set<String>) getValue(PATH_GET_STRING_SET, key, defValues);
}
@Override
public int getInt(String key, int defValue) {
return (Integer) getValue(PATH_GET_INT, key, defValue);
}
@Override
public long getLong(String key, long defValue) {
return (Long) getValue(PATH_GET_LONG, key, defValue);
}
@Override
public float getFloat(String key, float defValue) {
return (Float) getValue(PATH_GET_FLOAT, key, defValue);
}
@Override
public boolean getBoolean(String key, boolean defValue) {
return (Boolean) getValue(PATH_GET_BOOLEAN, key, defValue);
}
@Override
public boolean contains(String key) {
return (Boolean) getValue(PATH_CONTAINS, key, false);
}
@Override
public Editor edit() {
return new EditorImpl();
}
@Override
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized (this) {
if (mListeners == null) {
mListeners = new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
}
Boolean result = (Boolean) getValue(PATH_REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER, null, false);
if (result != null && result) {
mListeners.put(listener, CONTENT);
if (mReceiver == null) {
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String name = intent.getStringExtra(KEY_NAME);
@SuppressWarnings("unchecked")
List<String> keysModified = (List<String>) intent.getSerializableExtra(KEY);
if (mName.equals(name) && keysModified != null) {
Set<OnSharedPreferenceChangeListener> listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
for (int i = keysModified.size() - 1; i >= 0; i--) {
final String key = keysModified.get(i);
for (OnSharedPreferenceChangeListener listener : listeners) {
if (listener != null) {
listener.onSharedPreferenceChanged(MultiprocessSharedPreferences.this, key);
}
}
}
}
}
};
mContext.registerReceiver(mReceiver, new IntentFilter(makeAction(mName)));
}
}
}
}
@Override
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized (this) {
getValue(PATH_UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER, null, false); // WeakHashMap
if (mListeners != null) {
mListeners.remove(listener);
if (mListeners.isEmpty() && mReceiver != null) {
mContext.unregisterReceiver(mReceiver);
}
}
}
}
public final class EditorImpl implements Editor {
private final Map<String, Object> mModified = new HashMap<String, Object>();
private boolean mClear = false;
@Override
public Editor putString(String key, String value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
// @Override // Android 3.0
public Editor putStringSet(String key, Set<String> values) {
synchronized (this) {
mModified.put(key, (values == null) ? null : new HashSet<String>(values));
return this;
}
}
@Override
public Editor putInt(String key, int value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
@Override
public Editor putLong(String key, long value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
@Override
public Editor putFloat(String key, float value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
@Override
public Editor putBoolean(String key, boolean value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
@Override
public Editor remove(String key) {
synchronized (this) {
mModified.put(key, null);
return this;
}
}
@Override
public Editor clear() {
synchronized (this) {
mClear = true;
return this;
}
}
@Override
public void apply() {
setValue(PATH_APPLY);
}
@Override
public boolean commit() {
return setValue(PATH_COMMIT);
}
@SuppressLint("LongLogTag")
private boolean setValue(String pathSegment) {
boolean result = false;
if (!mIsSafeMode && checkInitAuthority(mContext)) { // 如果设备处在安全模式返回false
String[] selectionArgs = new String[] { String.valueOf(mMode), String.valueOf(mClear) };
synchronized (this) {
Uri uri = Uri.withAppendedPath(Uri.withAppendedPath(AUTHORITY_URI, mName), pathSegment);
ContentValues values = ReflectionUtil.contentValuesNewInstance((HashMap<String, Object>) mModified);
try {
result = mContext.getContentResolver().update(uri, values, null, selectionArgs) > 0;
} catch (IllegalArgumentException e) {
// 解决ContentProvider所在进程被杀时的抛出的异常
// java.lang.IllegalArgumentException: Unknown URI content://xxx.xxx.xxx/xxx/xxx
// at android.content.ContentResolver.update(ContentResolver.java:1312)
if (DEBUG) {
e.printStackTrace();
}
} catch (RuntimeException e) {
if (!isPackageManagerHasDiedException(e) && !isUnstableCountException(e)) {
throw new RuntimeException(e);
}
}
}
}
if (DEBUG) {
Log.d(TAG_CONTENT, "setValue.mName = " + mName + ", pathSegment = " + pathSegment + ", mModified.size() = " + mModified.size());
}
return result;
}
}
@SuppressLint("LongLogTag")
private Object getValue(String pathSegment, String key, Object defValue) {
Object v = null;
if (!mIsSafeMode && checkInitAuthority(mContext)) { // 如果设备处在安全模式返回defValue
Uri uri = Uri.withAppendedPath(Uri.withAppendedPath(AUTHORITY_URI, mName), pathSegment);
String[] projection = null;
if (PATH_GET_STRING_SET.equals(pathSegment) && defValue != null) {
@SuppressWarnings("unchecked")
Set<String> set = (Set<String>) defValue;
projection = new String[set.size()];
set.toArray(projection);
}
String[] selectionArgs = new String[] { String.valueOf(mMode), key, defValue == null ? null : String.valueOf(defValue) };
Cursor cursor = null;
try {
cursor = mContext.getContentResolver().query(uri, projection, null, selectionArgs, null);
} catch (SecurityException e) {
// 解决崩溃
// java.lang.SecurityException: Permission Denial: reading com.qihoo.storager.MultiprocessSharedPreferences uri content://com.qihoo.appstore.MultiprocessSharedPreferences/LogUtils/getBoolean from pid=2446, uid=10116 requires the provider be exported, or grantUriPermission()
// at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:332)
// ...
// at android.content.ContentResolver.query(ContentResolver.java:317)
if (DEBUG) {
e.printStackTrace();
}
} catch (RuntimeException e) {
if (!isPackageManagerHasDiedException(e) && !isUnstableCountException(e)) {
throw new RuntimeException(e);
}
}
if (cursor != null) {
Bundle bundle = null;
try {
bundle = cursor.getExtras();
} catch (RuntimeException e) {
// 解决ContentProvider所在进程被杀时的抛出的异常
// java.lang.RuntimeException: android.os.DeadObjectException
// at android.database.BulkCursorToCursorAdaptor.getExtras(BulkCursorToCursorAdaptor.java:173)
// at android.database.CursorWrapper.getExtras(CursorWrapper.java:94)
if (DEBUG) {
e.printStackTrace();
}
}
if (bundle != null) {
v = bundle.get(KEY);
bundle.clear();
}
cursor.close();
}
}
if (DEBUG) {
Log.d(TAG_CONTENT, "getValue.mName = " + mName + ", pathSegment = " + pathSegment + ", key = " + key + ", defValue = " + defValue);
}
return v == null ? defValue : v;
}
private String makeAction(String name) {
return String.format("%1$s_%2$s", MultiprocessSharedPreferences.class.getName(), name);
}
@Override
public boolean onCreate() {
if (checkInitAuthority(getContext())) {
mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_GET_ALL, GET_ALL);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_GET_STRING, GET_STRING);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_GET_INT, GET_INT);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_GET_LONG, GET_LONG);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_GET_FLOAT, GET_FLOAT);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_GET_BOOLEAN, GET_BOOLEAN);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_CONTAINS, CONTAINS);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_APPLY, APPLY);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_COMMIT, COMMIT);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER, REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER, UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_GET_STRING_SET, GET_STRING_SET);
return true;
} else {
return false;
}
}
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
String name = uri.getPathSegments().get(0);
int mode = Integer.parseInt(selectionArgs[0]);
String key = selectionArgs[1];
String defValue = selectionArgs[2];
Bundle bundle = new Bundle();
switch (mUriMatcher.match(uri)) {
case GET_ALL:
bundle.putSerializable(KEY, (HashMap<String, ?>) getSystemSharedPreferences(name, mode).getAll());
break;
case GET_STRING:
bundle.putString(KEY, getSystemSharedPreferences(name, mode).getString(key, defValue));
break;
case GET_INT:
bundle.putInt(KEY, getSystemSharedPreferences(name, mode).getInt(key, Integer.parseInt(defValue)));
break;
case GET_LONG:
bundle.putLong(KEY, getSystemSharedPreferences(name, mode).getLong(key, Long.parseLong(defValue)));
break;
case GET_FLOAT:
bundle.putFloat(KEY, getSystemSharedPreferences(name, mode).getFloat(key, Float.parseFloat(defValue)));
break;
case GET_BOOLEAN:
bundle.putBoolean(KEY, getSystemSharedPreferences(name, mode).getBoolean(key, Boolean.parseBoolean(defValue)));
break;
case CONTAINS:
bundle.putBoolean(KEY, getSystemSharedPreferences(name, mode).contains(key));
break;
case REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER: {
checkInitListenersCount();
Integer countInteger = mListenersCount.get(name);
int count = (countInteger == null ? 0 : countInteger) + 1;
mListenersCount.put(name, count);
countInteger = mListenersCount.get(name);
bundle.putBoolean(KEY, count == (countInteger == null ? 0 : countInteger));
}
break;
case UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER: {
checkInitListenersCount();
Integer countInteger = mListenersCount.get(name);
int count = (countInteger == null ? 0 : countInteger) - 1;
if (count <= 0) {
mListenersCount.remove(name);
bundle.putBoolean(KEY, !mListenersCount.containsKey(name));
} else {
mListenersCount.put(name, count);
countInteger = mListenersCount.get(name);
bundle.putBoolean(KEY, count == (countInteger == null ? 0 : countInteger));
}
}
break;
case GET_STRING_SET: {
if (Build.VERSION.SDK_INT >= 11) { // Android 3.0
Set<String> set = null;
if (projection != null) {
set = new HashSet<String>(Arrays.asList(projection));
}
bundle.putSerializable(KEY, (HashSet<String>) ReflectionUtil.sharedPreferencesGetStringSet(getSystemSharedPreferences(name, mode), key, set));
}
}
default:
if (DEBUG) {
throw new IllegalArgumentException("At query, This is Unknown Uri:" + uri + ", AUTHORITY = " + AUTHORITY);
}
}
return new BundleCursor(bundle);
}
@SuppressWarnings("unchecked")
@Override
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int result = 0;
String name = uri.getPathSegments().get(0);
int mode = Integer.parseInt(selectionArgs[0]);
SharedPreferences preferences = getSystemSharedPreferences(name, mode);
int match = mUriMatcher.match(uri);
switch (match) {
case APPLY:
case COMMIT:
boolean hasListeners = mListenersCount != null && mListenersCount.get(name) != null && mListenersCount.get(name) > 0;
ArrayList<String> keysModified = null;
Map<String, Object> map = null;
if (hasListeners) {
keysModified = new ArrayList<String>();
map = (Map<String, Object>) preferences.getAll();
}
Editor editor = preferences.edit();
boolean clear = Boolean.parseBoolean(selectionArgs[1]);
if (clear) {
if (hasListeners && !map.isEmpty()) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
keysModified.add(entry.getKey());
}
}
editor.clear();
}
for (Map.Entry<String, Object> entry : values.valueSet()) {
String k = entry.getKey();
Object v = entry.getValue();
// Android 5.L_preview : "this" is the magic value for a removal mutation. In addition,
// setting a value to "null" for a given key is specified to be
// equivalent to calling remove on that key.
if (v instanceof EditorImpl || v == null) {
editor.remove(k);
if (hasListeners && map.containsKey(k)) {
keysModified.add(k);
}
} else {
if (hasListeners && (!map.containsKey(k) || (map.containsKey(k) && !v.equals(map.get(k))))) {
keysModified.add(k);
}
}
if (v instanceof String) {
editor.putString(k, (String) v);
} else if (v instanceof Set) {
ReflectionUtil.editorPutStringSet(editor, k, (Set<String>) v); // Android 3.0
} else if (v instanceof Integer) {
editor.putInt(k, (Integer) v);
} else if (v instanceof Long) {
editor.putLong(k, (Long) v);
} else if (v instanceof Float) {
editor.putFloat(k, (Float) v);
} else if (v instanceof Boolean) {
editor.putBoolean(k, (Boolean) v);
}
}
if (hasListeners && keysModified.isEmpty()) {
result = 1;
} else {
switch (match) {
case APPLY:
ReflectionUtil.editorApply(editor); // Android 2.3
result = 1;
// Okay to notify the listeners before it's hit disk
// because the listeners should always get the same
// SharedPreferences instance back, which has the
// changes reflected in memory.
notifyListeners(name, keysModified);
break;
case COMMIT:
if (editor.commit()) {
result = 1;
notifyListeners(name, keysModified);
}
break;
default:
break;
}
}
values.clear();
break;
default:
if (DEBUG) {
throw new IllegalArgumentException("At update, This is Unknown Uri:" + uri + ", AUTHORITY = " + AUTHORITY);
}
}
return result;
}
@Override
public String getType(@NonNull Uri uri) {
throw new UnsupportedOperationException("No external call");
}
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
throw new UnsupportedOperationException("No external insert");
}
@Override
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("No external delete");
}
private SharedPreferences getSystemSharedPreferences(String name, int mode) {
return getContext().getSharedPreferences(name, mode);
}
private void checkInitListenersCount() {
if (mListenersCount == null) {
mListenersCount = new HashMap<String, Integer>();
}
}
private void notifyListeners(String name, ArrayList<String> keysModified) {
if (keysModified != null && !keysModified.isEmpty()) {
Intent intent = new Intent();
intent.setAction(makeAction(name));
intent.setPackage(getContext().getPackageName());
intent.putExtra(KEY_NAME, name);
intent.putExtra(KEY, keysModified);
getContext().sendBroadcast(intent);
}
}
private static final class BundleCursor extends MatrixCursor {
private Bundle mBundle;
public BundleCursor(Bundle extras) {
super(new String[] {}, 0);
mBundle = extras;
}
@Override
public Bundle getExtras() {
return mBundle;
}
@Override
public Bundle respond(Bundle extras) {
mBundle = extras;
return mBundle;
}
}
}

166
wechatvideo/src/main/java/com/example/wechatvideo/handler/CommentListHandler.java

@ -0,0 +1,166 @@
package com.example.wechatvideo.handler;
import static com.example.wechatvideo.HookVideoData825Entity.TAG;
import static com.example.wechatvideo.HookVideoData825Entity.classLoader;
import static com.example.wechatvideo.HookVideoData825Entity.i;
import android.util.Log;
import com.example.wechatvideo.model.StoredObject;
import com.example.wechatvideo.utils.SearchClassUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.UUID;
import cn.iinti.sekiro3.business.api.fastjson.JSONObject;
import cn.iinti.sekiro3.business.api.interfaze.Action;
import cn.iinti.sekiro3.business.api.interfaze.AutoBind;
import cn.iinti.sekiro3.business.api.interfaze.RequestHandler;
import cn.iinti.sekiro3.business.api.interfaze.SekiroRequest;
import cn.iinti.sekiro3.business.api.interfaze.SekiroResponse;
import de.robv.android.xposed.XposedHelpers;
/**
* 8.0.25 版本视频评论
* post请求
*/
@Action("comments")
public class CommentListHandler implements RequestHandler {
@AutoBind("videoId")
private String videoId; // 视频id
@AutoBind("objectNonceId")
private String objectNonceId; // objectid
@AutoBind("userId")
private String userId; // 用户id
@AutoBind("lastBuffer")
private String lastBuffer; // 翻页
@AutoBind("commentId")
private String commentId; //二级评论需要
@AutoBind("displayId")
private String displayId; // 二级评论需要
public final static String api = "comments";
/**
* 初始化所需要的对象
* @return
*/
public ArrayList<Object> setVideoModel(){
ArrayList<Object> enume_objects = SearchClassUtils.getAllInstancesOfClass(XposedHelpers.findClass("com.tencent.mm.plugin.finder.feed.model.i", classLoader));
Log.d(TAG, "getFinderMixSearchPresenter: 枚举对象数量 : " + enume_objects.size());
if(enume_objects.size() > 0){
Random random = new Random();
int randomInt = random.nextInt(enume_objects.size() - 1);
i = enume_objects.get(randomInt);
}
return enume_objects;
}
/**
* 主动调用获得评论
* 并返回入参
* @param videoId
* @param objectNonceId
* @param userId
* @param lastBuffer
* @param commentId
* @param displayId
* @return
*/
public JSONObject invoke(long videoId, String objectNonceId, String userId, String lastBuffer,
long commentId, long displayId){
String uuidKey = api + UUID.randomUUID().toString().replaceAll("-", "") + api; //
Map<String, Object> map = new HashMap<>();
Map<String, Object> params = new HashMap<>(); // 存储需要的参数
params.put("lastBuffer", lastBuffer);
params.put("commentId", commentId);
params.put("displayId", displayId);
params.put("videoId", videoId);
params.put("objectNonceId", objectNonceId);
params.put("userId", userId);
map.put("params", params); // 初始化需要的参数
map.put("data", new JSONObject()); // 初始化一个最终数据
StoredObject.setUuidKey(uuidKey);
StoredObject.map1.put(uuidKey, map);
Random random = new Random();
int randomInt = random.nextInt(10);
XposedHelpers.callMethod(i, "a", videoId, objectNonceId, randomInt, false, userId, 0);
return StoredObject.getData(uuidKey);
}
/**
* 用作处理异常
* @param _videoId
* @param _commentId
* @param _displayId
* @return
*/
public JSONObject retry(long _videoId, long _commentId, long _displayId){
JSONObject jsonObject = null;
ArrayList<Object> objects = setVideoModel();
for (Object o:objects){
i = o;
jsonObject = invoke(_videoId, objectNonceId, userId, lastBuffer, _commentId, _displayId);
Map<String, Object> data = (Map<String, Object>) jsonObject.get("data");
Log.d(TAG, "循环测试 run: demo -> success is " + jsonObject.toJSONString());
if(data.size() > 0){
break;
}
}
return jsonObject;
}
@Override
public void handleRequest(SekiroRequest sekiroRequest, SekiroResponse sekiroResponse) {
JSONObject jsonObject = new JSONObject();
long _videoId = (!Objects.equals(videoId, "")) ? Long.parseLong(videoId): 0;
long _commentId = (!Objects.equals(commentId, "")) ? Long.parseLong(commentId): 0;
long _displayId = (!Objects.equals(displayId, "")) ? Long.parseLong(displayId): 0;
Log.d(TAG, String.format("获取的 videoId: %s; objectNonceId: %s; userId: %s; lastBuffer: %s; commentId: %s; displayId:%s;", _videoId, objectNonceId, userId, lastBuffer, _commentId, _displayId));
if(i == null){ // 初始化对象
setVideoModel();
}
if(classLoader != null && i != null){ // 具备调用条件
jsonObject = invoke(_videoId, objectNonceId, userId, lastBuffer, _commentId, _displayId);
Log.d(TAG, "handleRequest: jsonObject is => " + jsonObject.size());
Map<String, Object> data = (Map<String, Object>) jsonObject.get("data");
if(data.size() == 0){
jsonObject = retry(_videoId, _commentId, _displayId);
}
LinkedList<Object> ab = new LinkedList<>();
// 由于修改了 返回数据则修改这块读取
LinkedList<?> m = (LinkedList<?>) data.get("commentList");
if(m != null){
for(Object o:m){
LinkedList<?> p = (LinkedList<?>) XposedHelpers.getObjectField(o, "levelTwoComment");
Log.d(TAG, "handleRequest???: canshu levelComment is => " + p.size());
if(p.size() > 0){
Log.d(TAG, "handleRequest: ??????????");
ab.add(p);
}
}
Log.d(TAG, "handleRequest: ad length is => " + ab.size());
}
sekiroResponse.success(jsonObject);
Log.d(TAG, "run: success is " + jsonObject);
}else{
sekiroResponse.success(jsonObject);
}
}
}

103
wechatvideo/src/main/java/com/example/wechatvideo/handler/SearchUser825Handler.java

@ -0,0 +1,103 @@
package com.example.wechatvideo.handler;
import static com.example.wechatvideo.HookVideoData825Entity.FinderMixSearchPresenter;
import static com.example.wechatvideo.HookVideoData825Entity.TAG;
import static com.example.wechatvideo.HookVideoData825Entity.classLoader;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.example.wechatvideo.model.StoredObject;
import com.example.wechatvideo.utils.SearchClassUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import cn.iinti.sekiro3.business.api.fastjson.JSONObject;
import cn.iinti.sekiro3.business.api.interfaze.Action;
import cn.iinti.sekiro3.business.api.interfaze.RequestHandler;
import cn.iinti.sekiro3.business.api.interfaze.SekiroRequest;
import cn.iinti.sekiro3.business.api.interfaze.SekiroResponse;
import de.robv.android.xposed.XposedHelpers;
/**
* 8.0.25版本搜索
*/
@Action("search825")
public class SearchUser825Handler implements RequestHandler {
public static String KEY_WORD = ""; // 用来记录上下文的keyword
public final static Map<Integer, String> sorts = new HashMap<Integer,String>(){{
put(2, "综合");
put(1, "实时");
}};
/**
* 全局遍历 获取keyword关键词
*/
public void setFinderMixSearchPresenter(){
ArrayList<Object> enume_objects = SearchClassUtils.getAllInstancesOfClass(XposedHelpers.findClass("com.tencent.mm.plugin.finder.search.FinderMixSearchPresenter", classLoader));
Log.d(TAG, "getFinderMixSearchPresenter: 枚举对象 : " + enume_objects.size());
if(enume_objects.size() > 0){
FinderMixSearchPresenter = enume_objects.get(0);
}
}
/**
* 根据 isNext 来确定是重新搜索 还是翻页
* @param keyword
* @param isNext
* @param type: 综合是2最新是1
* @return
*/
public JSONObject invoke(String keyword, int isNext, int type){
String uuidKey = UUID.randomUUID().toString().replaceAll("-", ""); // 32位
Map<String, Object> map = new HashMap<>();
map.put("search_order", sorts.get(type)); // 把字段加进来
if(isNext == 1){ // 翻页
StoredObject.setUuidKey(uuidKey);
StoredObject.map1.put(uuidKey, map);
XposedHelpers.callMethod(FinderMixSearchPresenter, "aCK", type);
}else{ // 首页
StoredObject.setUuidKey(uuidKey);
StoredObject.map1.put(uuidKey, map);
XposedHelpers.callMethod(FinderMixSearchPresenter, "a", keyword, 1, false, null, 0);
KEY_WORD = keyword;
}
return StoredObject.getData(uuidKey);
}
@Override
public void handleRequest(SekiroRequest sekiroRequest, SekiroResponse sekiroResponse) {
// 切换至主线程中进行
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
Map<String, String> result = null;
JSONObject jsonObject = null;
String keyword = (String) sekiroRequest.get("keyword");
int isNext = Integer.parseInt((String) sekiroRequest.get("is_next"));
int type = Integer.parseInt((String) sekiroRequest.get("type"));;
Log.d(TAG, String.format("获取的 keyword: %s; 是否翻页: %s; 原始keyword: %s; 排序为:%s", keyword, isNext, KEY_WORD, type));
if(FinderMixSearchPresenter == null){ // 初始化搜索对象
setFinderMixSearchPresenter();
}
if(classLoader != null && FinderMixSearchPresenter != null){
jsonObject = invoke(keyword, isNext, type);
Log.d(TAG, "handleRequest: " + sekiroRequest.getSekiroClient().getClientId() + " res is " + jsonObject);
sekiroResponse.success(jsonObject);
}else{
sekiroResponse.success(JSONObject.toJSONString(result));
}
}
});
}
}

104
wechatvideo/src/main/java/com/example/wechatvideo/handler/SearchUserHandler.java

@ -0,0 +1,104 @@
package com.example.wechatvideo.handler;
import static com.example.wechatvideo.HookVideoDataEntity.FinderMixSearchPresenter;
import static com.example.wechatvideo.HookVideoDataEntity.TAG;
import static com.example.wechatvideo.HookVideoDataEntity.classLoader;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.example.wechatvideo.model.StoredObject;
import com.example.wechatvideo.utils.SearchClassUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import cn.iinti.sekiro3.business.api.fastjson.JSONObject;
import cn.iinti.sekiro3.business.api.interfaze.Action;
import cn.iinti.sekiro3.business.api.interfaze.RequestHandler;
import cn.iinti.sekiro3.business.api.interfaze.SekiroRequest;
import cn.iinti.sekiro3.business.api.interfaze.SekiroResponse;
import de.robv.android.xposed.XposedHelpers;
/**
* 微信视频号搜索模块
* 用户 && 视频
* demo: http://localhost:5612/business/invoke?group=vxin&action=search&keyword=比亚迪&is_next=0
*/
@Action("search")
public class SearchUserHandler implements RequestHandler {
public static String KEY_WORD = ""; // 用来记录上下文的keyword
/**
* 全局遍历 获取keyword关键词
*/
public void setFinderMixSearchPresenter(){
ArrayList<Object> enume_objects = SearchClassUtils.getAllInstancesOfClass(XposedHelpers.findClass("com.tencent.mm.plugin.finder.search.FinderMixSearchPresenter", classLoader));
Log.d(TAG, "getFinderMixSearchPresenter: 枚举对象 : " + enume_objects.size());
if(enume_objects.size() > 0){
FinderMixSearchPresenter = enume_objects.get(0);
}
}
/**
* 根据 isNext 来确定是重新搜索 还是翻页
* @param keyword
* @param isNext
* @return
*/
public JSONObject invoke(String keyword, int isNext){
String uuidKey = UUID.randomUUID().toString().replaceAll("-", ""); // 32位
Map<String, Object> map = new HashMap<>();
if(isNext == 1){
StoredObject.setUuidKey(uuidKey);
StoredObject.map1.put(uuidKey, map);
XposedHelpers.callMethod(FinderMixSearchPresenter, "cNZ");
}else{
StoredObject.setUuidKey(uuidKey);
StoredObject.map1.put(uuidKey, map);
XposedHelpers.callMethod(FinderMixSearchPresenter, "anp", keyword);
KEY_WORD = keyword;
}
return StoredObject.getData(uuidKey);
}
@Override
public void handleRequest(SekiroRequest sekiroRequest, SekiroResponse sekiroResponse) {
// 切换至主线程中进行
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
Map<String, String> result = null;
JSONObject jsonObject = null;
String keyword = (String) sekiroRequest.get("keyword");
int isNext = Integer.parseInt((String) sekiroRequest.get("is_next"));
Log.d(TAG, String.format("获取的 keyword: %s; 是否翻页: %s; 原始keyword: %s", keyword, isNext, KEY_WORD));
if(FinderMixSearchPresenter == null){ // 初始化搜索对象
setFinderMixSearchPresenter();
}
if(classLoader != null && FinderMixSearchPresenter != null){
jsonObject = invoke(keyword, isNext);
Log.d(TAG, "handleRequest: " + sekiroRequest.getSekiroClient().getClientId() + " res is " + jsonObject);
sekiroResponse.success(jsonObject);
}else{
sekiroResponse.success(JSONObject.toJSONString(result));
}
}
});
}
}

125
wechatvideo/src/main/java/com/example/wechatvideo/handler/UserVideo825Handler.java

@ -0,0 +1,125 @@
package com.example.wechatvideo.handler;
import static com.example.wechatvideo.HookVideoData825Entity.FEED_PROFILE;
import static com.example.wechatvideo.HookVideoData825Entity.TAG;
import static com.example.wechatvideo.HookVideoData825Entity.classLoader;
import android.util.Log;
import com.example.wechatvideo.model.StoredObject;
import com.example.wechatvideo.utils.SearchClassUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import cn.iinti.sekiro3.business.api.fastjson.JSONObject;
import cn.iinti.sekiro3.business.api.interfaze.Action;
import cn.iinti.sekiro3.business.api.interfaze.RequestHandler;
import cn.iinti.sekiro3.business.api.interfaze.SekiroRequest;
import cn.iinti.sekiro3.business.api.interfaze.SekiroResponse;
import de.robv.android.xposed.XposedHelpers;
/**
* 8.0.25 版本用户主页
*/
@Action("user825")
public class UserVideo825Handler implements RequestHandler {
/**
* 全局过滤 一个枚举对象 用于构建 作者对象
*/
public void setProFile(){
ArrayList<Object> enume_objects = SearchClassUtils.getAllInstancesOfClass(XposedHelpers.findClass("com.tencent.mm.plugin.finder.feed.model.internal.g", classLoader));
Log.d(TAG, "afterHookedMethod: 枚举对象 : " + enume_objects.size());
for(Object oi: enume_objects){
Log.d(TAG, "InitialObject: 枚举类初始化 " + oi);
if("FEED_PROFILE".equals(String.valueOf(oi))){
FEED_PROFILE = oi;
Log.d(TAG, "InitialObject: 构造参数结束: "+ oi);
break;
}
}
}
/**
* 构造一个对目标用户的作者的对象
* 只构造一次过一会儿貌似就回收了
* 因此每次请求构造一个
* @param username
* @return
*/
public Object setProfileFeedLoader(String username) throws Exception {
Class<?> FinderProfileFeedLoaderClazz = XposedHelpers.findClass("com.tencent.mm.plugin.finder.feed.model.FinderProfileFeedLoader", classLoader);
Class<?> auu_clazz = XposedHelpers.findClass("com.tencent.mm.protocal.protobuf.ccz", classLoader);
Object targetObject = XposedHelpers.newInstance(FinderProfileFeedLoaderClazz, FEED_PROFILE, username, auu_clazz.newInstance(), false, false);
Log.d(TAG, "setProfileFeedLoader: 目标对象构造完毕 => " + username);
return targetObject;
}
/**
* 用户首页
* @param username
* @return
*/
public JSONObject invoke(String username) throws Exception {
Object o = setProfileFeedLoader(username);
String uuidKey = "0";
StoredObject.setUuidKey(uuidKey);
Map<String, Object> map = new HashMap<>();
StoredObject.map1.put(uuidKey, map);
XposedHelpers.callMethod(o, "requestRefresh");
return StoredObject.getData(uuidKey); // 初始化异步消息捕获
}
/**
* 用户翻页
* @param username 目标用户数据
* @param offset 偏移量 默认为long
*/
public JSONObject invoke(String username, String offset) throws Exception {
Object o = setProfileFeedLoader(username);
StoredObject.setUuidKey(offset); // 变为offset
Map<String, Object> map = new HashMap<>();
StoredObject.map1.put(offset, map);
XposedHelpers.callMethod(o, "requestLoadMore", false);
return StoredObject.getData(offset);
}
@Override
public void handleRequest(SekiroRequest sekiroRequest, SekiroResponse sekiroResponse) {
Map<String, String> result = null;
JSONObject jsonObject = null;
String username = (String) sekiroRequest.get("user_id");
String offset = (String) sekiroRequest.get("offset");
Log.d(TAG, String.format("获取的 username: %s; 获取的偏移量: %s", username, offset));
Log.d(TAG, "handleRequest: classLoader => " + classLoader);
Log.d(TAG, "handleRequest: FEED_PROFILE => " + FEED_PROFILE);
if(FEED_PROFILE == null){
setProFile(); // 初始化feed
}
if(classLoader != null && FEED_PROFILE != null){
try {
if("0".equals(offset)){
jsonObject = invoke(username); // 第一页
}else{
jsonObject = invoke(username, offset); // 第n页
}
Log.d(TAG, "handleRequest: " + sekiroRequest.getSekiroClient().getClientId() + " res is " + jsonObject);
}catch (Exception e){
throw new RuntimeException(e);
}
sekiroResponse.success(jsonObject);
}else {
sekiroResponse.success(JSONObject.toJSONString(result));
}
}
}

129
wechatvideo/src/main/java/com/example/wechatvideo/handler/UserVideoHandler.java

@ -0,0 +1,129 @@
package com.example.wechatvideo.handler;
import static com.example.wechatvideo.HookVideoDataEntity.FEED_PROFILE;
import static com.example.wechatvideo.HookVideoDataEntity.TAG;
import static com.example.wechatvideo.HookVideoDataEntity.classLoader;
import android.util.Log;
import com.example.wechatvideo.model.StoredObject;
import com.example.wechatvideo.utils.SearchClassUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import cn.iinti.sekiro3.business.api.fastjson.JSONObject;
import cn.iinti.sekiro3.business.api.interfaze.Action;
import cn.iinti.sekiro3.business.api.interfaze.RequestHandler;
import cn.iinti.sekiro3.business.api.interfaze.SekiroRequest;
import cn.iinti.sekiro3.business.api.interfaze.SekiroResponse;
import de.robv.android.xposed.XposedHelpers;
/**
* 微信视频号 用户主页视频获取
* demo: http://localhost:5612/business/invoke?group=vxin&action=user&user_id=v2_060000231003b20faec8c6ea891ac4d5cb02e531b077c2083a8c3b90f2e4618deb0754a398a2@finder&offset=0
*/
@Action("user")
public class UserVideoHandler implements RequestHandler {
/**
* 构造一个对目标用户的作者的对象
* 只构造一次过一会儿貌似就回收了
* 因此每次请求构造一个
* @param username
* @return
*/
public Object setProfileFeedLoader(String username) throws Exception {
Class<?> FinderProfileFeedLoaderClazz = XposedHelpers.findClass("com.tencent.mm.plugin.finder.feed.model.FinderProfileFeedLoader", classLoader);
Class<?> auu_clazz = XposedHelpers.findClass("com.tencent.mm.protocal.protobuf.auu", classLoader);
Object targetObject = XposedHelpers.newInstance(FinderProfileFeedLoaderClazz, FEED_PROFILE, username, auu_clazz.newInstance());
Log.d(TAG, "setProfileFeedLoader: 目标对象构造完毕 => " + username);
return targetObject;
}
/**
* 用户首页
* @param username
* @return
*/
public JSONObject invoke(String username) throws Exception {
Object o = setProfileFeedLoader(username);
String uuidKey = "0";
StoredObject.setUuidKey(uuidKey);
Map<String, Object> map = new HashMap<>();
StoredObject.map1.put(uuidKey, map);
XposedHelpers.callMethod(o, "requestRefresh");
return StoredObject.getData(uuidKey); // 初始化异步消息捕获
}
/**
* 用户翻页
* @param username 目标用户数据
* @param offset 偏移量 默认为long
*/
public JSONObject invoke(String username, String offset) throws Exception {
Object o = setProfileFeedLoader(username);
StoredObject.setUuidKey(offset); // 变为offset
Map<String, Object> map = new HashMap<>();
StoredObject.map1.put(offset, map);
XposedHelpers.callMethod(o, "requestLoadMore");
return StoredObject.getData(offset);
}
/**
* 全局过滤 一个枚举对象 用于构建 作者对象
*/
public void setProFile(){
ArrayList<Object> enume_objects = SearchClassUtils.getAllInstancesOfClass(XposedHelpers.findClass("com.tencent.mm.plugin.finder.feed.model.internal.e", classLoader));
Log.d(TAG, "afterHookedMethod: 枚举对象 : " + enume_objects.size());
for(Object oi: enume_objects){
Log.d(TAG, "InitialObject: 枚举类初始化 " + oi);
if("FEED_PROFILE".equals(String.valueOf(oi))){
FEED_PROFILE = oi;
Log.d(TAG, "InitialObject: 构造参数结束: "+ oi);
break;
}
}
}
@Override
public void handleRequest(SekiroRequest sekiroRequest, SekiroResponse sekiroResponse) {
Map<String, String> result = null;
JSONObject jsonObject = null;
String username = (String) sekiroRequest.get("user_id");
String offset = (String) sekiroRequest.get("offset");
Log.d(TAG, String.format("获取的 username: %s; 获取的偏移量: %s", username, offset));
Log.d(TAG, "handleRequest: classLoader => " + classLoader);
Log.d(TAG, "handleRequest: FEED_PROFILE => " + FEED_PROFILE);
if(FEED_PROFILE == null){
setProFile(); // 初始化feed
}
if(classLoader != null && FEED_PROFILE != null){
try {
if("0".equals(offset)){
jsonObject = invoke(username); // 第一页
}else{
jsonObject = invoke(username, offset); // 第n页
}
Log.d(TAG, "handleRequest: " + sekiroRequest.getSekiroClient().getClientId() + " res is " + jsonObject);
}catch (Exception e){
throw new RuntimeException(e);
}
sekiroResponse.success(jsonObject);
}else {
sekiroResponse.success(JSONObject.toJSONString(result));
}
}
}

77
wechatvideo/src/main/java/com/example/wechatvideo/model/StoredObject.java

@ -0,0 +1,77 @@
package com.example.wechatvideo.model;
import com.google.common.collect.Maps;
import java.util.Map;
import cn.iinti.sekiro3.business.api.fastjson.JSONObject;
/**
* 异步拦截数据
*/
public class StoredObject {
public static Map<String, Map<String, Object>> map1 = Maps.newConcurrentMap(); // 控制跨线程变量
static String uuidKey;
public static String getUuidKey() {
return uuidKey;
}
public static void setUuidKey(String uuidKey) {
StoredObject.uuidKey = uuidKey;
}
/**
* 视频号数据拦截
* 获得目标uuid中的结果
* @param uuidKey
* @return
*/
public static JSONObject getData(String uuidKey){
JSONObject jsonObject = new JSONObject();
try {
synchronized (uuidKey){ // 等待锁结束
uuidKey.wait(5000); // 5s可能不够
Map<String, Object> removeMap = map1.remove(uuidKey);
jsonObject = new JSONObject(removeMap);
}
}catch (InterruptedException e){
e.printStackTrace();
jsonObject.put("msg", "timeOut");
}finally {
setUuidKey(null);
}
return jsonObject;
}
/**
* 获得目标uuid中的结果
* => 嵌套map
* @param uuidKey
* @return
*/
public static JSONObject getData1(String uuidKey){
JSONObject jsonObject = new JSONObject();
try {
synchronized (uuidKey){ // 等待锁结束
uuidKey.wait(5000); // 5s可能不够 修改为十秒
Map<String, Object> removeMap = map1.remove(uuidKey);
Map<String, Object> data = (Map<String, Object>) removeMap.get("data"); // 这是有数据的
jsonObject = new JSONObject(data);
return jsonObject;
}
}catch (InterruptedException e){
e.printStackTrace();
jsonObject.put("msg", "timeOut");
}finally {
setUuidKey(null);
}
return jsonObject;
}
}

146
wechatvideo/src/main/java/com/example/wechatvideo/utils/SearchClassUtils.java

@ -0,0 +1,146 @@
package com.example.wechatvideo.utils;
import android.annotation.TargetApi;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
/**
* 安卓内存漫游
* 源码参考:
* http://androidxref.com/9.0.0_r3/s?refs=ClassD&project=art
*/
public class SearchClassUtils {
private static final Method startMethodTracingMethod;
private static final Method stopMethodTracingMethod;
private static final Method getMethodTracingModeMethod;
private static final Method getRuntimeStatMethod;
private static final Method getRuntimeStatsMethod;
private static final Method countInstancesOfClassMethod;
private static final Method countInstancesOfClassesMethod;
private static Method getInstancesOfClassesMethod;
static {
try {
Class<?> c = Class.forName("dalvik.system.VMDebug");
startMethodTracingMethod = c.getDeclaredMethod("startMethodTracing", String.class,
Integer.TYPE, Integer.TYPE, Boolean.TYPE, Integer.TYPE);
stopMethodTracingMethod = c.getDeclaredMethod("stopMethodTracing");
getMethodTracingModeMethod = c.getDeclaredMethod("getMethodTracingMode");
getRuntimeStatMethod = c.getDeclaredMethod("getRuntimeStat", String.class);
getRuntimeStatsMethod = c.getDeclaredMethod("getRuntimeStats");
countInstancesOfClassMethod = c.getDeclaredMethod("countInstancesOfClass",
Class.class, Boolean.TYPE);
countInstancesOfClassesMethod = c.getDeclaredMethod("countInstancesOfClasses",
Class[].class, Boolean.TYPE);
// android 9.0以上才有这个方法
if(android.os.Build.VERSION.SDK_INT >= 28) {
getInstancesOfClassesMethod = c.getDeclaredMethod("getInstancesOfClasses",
Class[].class, Boolean.TYPE);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 根据Class 获取当前进程该class 全部的实例
* @param clazz
* @return
*/
@TargetApi(28)
public static ArrayList<Object> getAllInstancesOfClass(Class clazz) { return getClassInstancesOfCurrentThread(clazz, false); }
/**
* 根据Class 获取当前进程该class 全部的实例
* @param clazz:当前class
* @param assignable: 是否包含子类的实例
* @return
*/
@TargetApi(28)
private static synchronized ArrayList<Object> getClassInstancesOfCurrentThread(Class clazz, Boolean assignable){
ArrayList<Object> result = null;
try {
Object[][] instancesOfClasses = getInstancesOfClasses(new Class[]{clazz}, assignable);
if (instancesOfClasses != null){
result = new ArrayList<>();
for (Object[] instancesOfClass: instancesOfClasses){
System.out.println("实体个数 ~~ " + instancesOfClass.length);
result.addAll(Arrays.asList(instancesOfClass));
// result.add(instancesOfClass);
}
}
}catch (Throwable e){
e.printStackTrace();
}
return result;
}
/**
* 获得所有class的实例
* android 9 以上
* @param classes:当前class集合
* @param assignable:是否包含子类的实例
* @return
* @throws Exception
*/
@TargetApi(28)
private static Object[][] getInstancesOfClasses(Class<?>[] classes, boolean assignable)throws Exception {
return (Object[][]) getInstancesOfClassesMethod.invoke(null, new Object[]{classes, assignable});
}
public static void startMethodTracing(String filename, int bufferSize, int flags,
boolean samplingEnabled, int intervalUs) throws Exception {
startMethodTracingMethod.invoke(null, filename, bufferSize, flags, samplingEnabled, intervalUs);
}
public static void stopMethodTracing() throws Exception {
stopMethodTracingMethod.invoke(null);
}
public static int getMethodTracingMode() throws Exception {
return (int) getMethodTracingModeMethod.invoke(null);
}
/**
* String gc_count = VMDebug.getRuntimeStat("art.gc.gc-count");
* String gc_time = VMDebug.getRuntimeStat("art.gc.gc-time");
* String bytes_allocated = VMDebug.getRuntimeStat("art.gc.bytes-allocated");
* String bytes_freed = VMDebug.getRuntimeStat("art.gc.bytes-freed");
* String blocking_gc_count = VMDebug.getRuntimeStat("art.gc.blocking-gc-count");
* String blocking_gc_time = VMDebug.getRuntimeStat("art.gc.blocking-gc-time");
* String gc_count_rate_histogram = VMDebug.getRuntimeStat("art.gc.gc-count-rate-histogram");
* String blocking_gc_count_rate_histogram =VMDebug.getRuntimeStat("art.gc.gc-count-rate-histogram");
*/
public static String getRuntimeStat(String statName) throws Exception {
return (String) getRuntimeStatMethod.invoke(null, statName);
}
/**
* 获取当前进程的状态信息
*/
public static Map<String, String> getRuntimeStats() throws Exception {
return (Map<String, String>) getRuntimeStatsMethod.invoke(null);
}
public static long countInstancesofClass(Class<?> c, boolean assignable) throws Exception {
return (long) countInstancesOfClassMethod.invoke(null, new Object[]{c, assignable});
}
public static long[] countInstancesofClasses(Class<?>[] classes, boolean assignable) throws Exception {
return (long[]) countInstancesOfClassesMethod.invoke(null, new Object[]{classes, assignable});
}
}

179
wechatvideo/src/main/java/com/example/wechatvideo/utils/SpUtil.java

@ -0,0 +1,179 @@
package com.example.wechatvideo.utils;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import com.example.wechatvideo.content.MultiprocessSharedPreferences;
public class SpUtil {
// 声明为 static volatile会迫使线程每次读取时作为一个全局变量读取
private static volatile SpUtil spUtil = null;
private static SharedPreferences sp = null;
private static final String XML_NAME = "config";
private SpUtil(Context context) {
// 上下文配置名称(data/data/shared_prefs )
sp = MultiprocessSharedPreferences.
getSharedPreferences(context, XML_NAME, Context.MODE_PRIVATE);
// sp =
}
public static SpUtil newInstance(Context context) {
if(spUtil == null){
synchronized (SpUtil.class){
while (spUtil == null){
spUtil = new SpUtil(context);
}
}
}
return spUtil;
}
/**
* 存储字符串
* @param context
* @param key
* @param value
*/
public static void putString(Context context, String key, String value){
//(存储节点文件名称,读写方式)
if(spUtil == null){
spUtil = newInstance(context);
}
sp.edit().putString(key, value).commit();
}
/**
* 获取字符串
* @param context
* @param key
* @param defValue
* @return
*/
public static String getString(Context context, String key, String defValue){
//(存储节点文件名称,读写方式)
if(spUtil == null){
spUtil = newInstance(context);
}
return sp.getString(key, defValue);
}
/**
* 存储boolean
* @param context
* @param key
* @param value
*/
public static void putBoolean(Context context, String key, boolean value){
//(存储节点文件名称,读写方式)
if(spUtil == null){
spUtil = newInstance(context);
}
sp.edit().putBoolean(key, value).commit();
}
/**
* 获取boolean
* @param context
* @param key
* @param defValue
* @return
*/
public static boolean getBoolean(Context context,String key,boolean defValue){
//(存储节点文件名称,读写方式)
if(spUtil == null){
spUtil = newInstance(context);
}
return sp.getBoolean(key, defValue);
}
/**
* 存储int
* @param context
* @param key
* @param defValue
*/
public static void putInt(Context context,String key, int defValue){
//(存储节点文件名称,读写方式)
if(spUtil == null){
spUtil = newInstance(context);
}
sp.edit().putInt(key, defValue).commit();
}
/**
* 获取int
* @param context
* @param key
* @param defValue
* @return
*/
public static int getInt(Context context,String key,int defValue){
//(存储节点文件名称,读写方式)
if(spUtil == null){
spUtil = newInstance(context);
}
return sp.getInt(key, defValue);
}
/**
* 删除节点
* @param context
* @param key
*/
public static void remove(Context context, String key) {
if(spUtil == null){
spUtil = newInstance(context);
}
sp.edit().remove(key).commit();
}
/**
* 根据类型 通过provider 返回结果
* @param context 传递当前上下文
* @param key 传递key
* @param objType 存储key的类型
* @return 对应key-value
*/
public static Object readObjectByProvider(Context context, String key, Class objType){
StringBuilder stringBuilder = new StringBuilder("content://");
stringBuilder.append("com.example.wechatvideo.provider/"); // 包名
stringBuilder.append(XML_NAME + "/");
//(存储节点文件名称,读写方式) 追加方式 参考MultiprocessSharedPreferences中
// eg. content://com.px.xpcrossprocess.provider/config/getString 获取config.xml中的字符串类型的
// TODO
if(objType.getName() == "java.lang.String"){
stringBuilder.append("getString");
}else if(objType.getName() == "java.lang.Boolean"){
stringBuilder.append("getBoolean");
}else if(objType.getName() == "int"){
stringBuilder.append("getInt");
}
Uri uri = Uri.parse(stringBuilder.toString());
ContentResolver provider = context.getContentResolver();
Cursor cursorTid;
try {
//第一个参数是否是安全模式一般为0第二个参数读取的key第三个参数defaultValue
String[] selectionArgs = {"0", key, ""};
cursorTid = provider.query(uri, null, null, selectionArgs, null);
Bundle extras = cursorTid.getExtras();
Object res = extras.get("value");
return res;
}catch (Exception e){
Log.e("crossprocess",e.getMessage()+"\t"+e.toString());
e.printStackTrace();
}
return null;
}
}

30
wechatvideo/src/main/res/drawable-v24/ic_launcher_foreground.xml

@ -0,0 +1,30 @@
<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:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
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:fillColor="#FFFFFF"
android:fillType="nonZero"
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:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

170
wechatvideo/src/main/res/drawable/ic_launcher_background.xml

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

30
wechatvideo/src/main/res/drawable/ic_launcher_foreground.xml

@ -0,0 +1,30 @@
<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:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
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:fillColor="#FFFFFF"
android:fillType="nonZero"
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:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

74
wechatvideo/src/main/res/drawable/new_icon_background.xml

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<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>

118
wechatvideo/src/main/res/layout/activity_main.xml

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<ScrollView
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_gravity="center"
android:gravity="center"
android:text="RPC配置信息" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="设备调度编号: "/>
<EditText
android:id="@+id/set_phone"
android:singleLine="true"
android:hint="数字或字母"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="服务调度地址: "/>
<EditText
android:id="@+id/set_web"
android:singleLine="true"
android:hint="ip"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<Button
android:id="@+id/btn_update"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="更新" />
<LinearLayout
android:layout_marginTop="20dp"
android:layout_width="match_parent"
android:layout_height="30dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="设备调度编号: "/>
<TextView
android:id="@+id/get_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="30dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="服务调度地址: "/>
<TextView
android:id="@+id/get_web"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="30dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="调度组名为: "/>
<TextView
android:id="@+id/get_group"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</ScrollView>
</LinearLayout>

6
wechatvideo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@ -0,0 +1,6 @@
<?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>

6
wechatvideo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@ -0,0 +1,6 @@
<?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>

5
wechatvideo/src/main/res/mipmap-anydpi-v26/new_icon.xml

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/new_icon_background"/>
<foreground android:drawable="@mipmap/new_icon_foreground"/>
</adaptive-icon>

5
wechatvideo/src/main/res/mipmap-anydpi-v26/new_icon_round.xml

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/new_icon_background"/>
<foreground android:drawable="@mipmap/new_icon_foreground"/>
</adaptive-icon>

BIN
wechatvideo/src/main/res/mipmap-hdpi/ic_launcher.webp

BIN
wechatvideo/src/main/res/mipmap-hdpi/ic_launcher_round.webp

BIN
wechatvideo/src/main/res/mipmap-hdpi/new_icon.webp

BIN
wechatvideo/src/main/res/mipmap-hdpi/new_icon_foreground.webp

BIN
wechatvideo/src/main/res/mipmap-hdpi/new_icon_round.webp

BIN
wechatvideo/src/main/res/mipmap-mdpi/ic_launcher.webp

BIN
wechatvideo/src/main/res/mipmap-mdpi/ic_launcher_round.webp

BIN
wechatvideo/src/main/res/mipmap-mdpi/new_icon.webp

BIN
wechatvideo/src/main/res/mipmap-mdpi/new_icon_foreground.webp

BIN
wechatvideo/src/main/res/mipmap-mdpi/new_icon_round.webp

BIN
wechatvideo/src/main/res/mipmap-xhdpi/ic_launcher.webp

BIN
wechatvideo/src/main/res/mipmap-xhdpi/ic_launcher_round.webp

BIN
wechatvideo/src/main/res/mipmap-xhdpi/new_icon.webp

BIN
wechatvideo/src/main/res/mipmap-xhdpi/new_icon_foreground.webp

BIN
wechatvideo/src/main/res/mipmap-xhdpi/new_icon_round.webp

BIN
wechatvideo/src/main/res/mipmap-xxhdpi/ic_launcher.webp

BIN
wechatvideo/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp

BIN
wechatvideo/src/main/res/mipmap-xxhdpi/new_icon.webp

BIN
wechatvideo/src/main/res/mipmap-xxhdpi/new_icon_foreground.webp

BIN
wechatvideo/src/main/res/mipmap-xxhdpi/new_icon_round.webp

BIN
wechatvideo/src/main/res/mipmap-xxxhdpi/ic_launcher.webp

BIN
wechatvideo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp

BIN
wechatvideo/src/main/res/mipmap-xxxhdpi/new_icon.webp

BIN
wechatvideo/src/main/res/mipmap-xxxhdpi/new_icon_foreground.webp

BIN
wechatvideo/src/main/res/mipmap-xxxhdpi/new_icon_round.webp

16
wechatvideo/src/main/res/values-night/themes.xml

@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Xp_module" 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>

10
wechatvideo/src/main/res/values/colors.xml

@ -0,0 +1,10 @@
<?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>

4
wechatvideo/src/main/res/values/strings.xml

@ -0,0 +1,4 @@
<resources>
<string name="app_name">视频号模块</string>
<string name="xposed_description">微信 视频号hook组件 8.0.25版本</string>
</resources>

16
wechatvideo/src/main/res/values/themes.xml

@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Xp_module" 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>

17
wechatvideo/src/test/java/com/example/wechatvideo/ExampleUnitTest.java

@ -0,0 +1,17 @@
package com.example.wechatvideo;
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);
}
}
Loading…
Cancel
Save