一、错误描述:
调试某Android硬件盒子,在升级app后,断电重启,出现以下错误信息:UnsatisfiedLinkError: dlopen failed: “/data/app/~~pGPujfYZls2be5Bj5rrwSg==/xxxx(这里是包名)-m5mN2cWGRTn-ZUO_CY4Fng==/lib/arm64/xxx_jni.so” has bad ELF magic: 00000000
二、错误解析:
这个错误表示在加载共享库时发生了问题。下面逐个解释每部分的含义:
1)UnsatisfiedLinkError:这是Java中的一个异常类型,表示无法满足对本地代码(如共享库)的链接请求。
2)dlopen failed:这指的是在尝试使用动态链接器打开共享库时发生了错误。
3)“/data/app/~~pGPujfYZls2be5Bj5rrwSg==/xxxxx-m5mN2cWGRTn-ZUO_CY4Fng==/lib/arm64/xxxx_jni.so”:这是共享库文件的完整路径。它指示共享库位于设备的特定路径中。
4)has bad ELF magic: 00000000:ELF(Executable and Linkable Format)是一种常见的二进制文件格式,用于可执行文件和共享库。这部分错误消息指示共享库的 ELF 魔数不正确,魔数应该是一个特定的值,但此处为 00000000,表示共享库的文件格式不正确。
综上所述,该错误表示应用程序在尝试加载共享库时遇到了问题。可能的原因是共享库文件损坏、找不到so文件、与设备架构不匹配或缺少依赖项。需要获取正确的共享库文件,确保与设备架构兼容,并检查是否存在所需的依赖项。
三、尝试解决:
该问题是在断电重启后发生的,可能有以下一些原因导致该错误:
文件系统损坏:断电重启可能导致文件系统损坏,特别是如果共享库文件在断电时正在被访问或修改。这可能会导致共享库文件出现损坏或不完整的情况。可以尝试对文件系统进行修复或重新安装应用程序来解决此问题。
数据丢失:断电重启可能导致应用程序数据的丢失,包括共享库文件。如果共享库文件被删除或丢失,加载时会出现错误。可以尝试重新安装应用程序来确保所有相关文件完整。
配置丢失:断电重启后,应用程序的配置文件或设置可能会丢失或损坏。如果共享库的路径或其他相关配置发生了变化,加载共享库时会发生错误。可以尝试重新配置应用程序或还原先前的配置来解决问题。
四、最终解决方案:
在app升级完成后,执行sync命令,调试阶段可通过:adb shell sync来同步。代码中可以用ShellUtils.execCommand(“sync”, false);来执行该命令。
五、关于sync命令
【adb shell sync 命令】
1)在shell中执行;
2)将内存缓冲区中的数据写入到磁盘;
【adb sync 命令】
命令意思:同步更新/data/或/system/下的数据
【命令用法】
1)adb sync [directory];如果不指定目录,将同步更新/data/和/system/
2)adb sync (将内存缓冲区的数据写入磁盘)
【adb -s ip:port shell sync 命令】
1)将内存缓冲区中的数据 写入到磁盘
2)如果不指定目录,将同步更新/data/和/system/
六、ShellUtils工具类
import androidx.annotation.NonNull;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
/**
* description:ShellUtils,通过Java代码执行adb Shell命令,Shell命令支持执行String(单个命令), List(多个命令),String[](多个命令)
* author: Trinea
* date: 2022/10/24
* update: 2022/10/24 -- 17:38
* version:
*/
public class ShellUtils {
public static final String COMMAND_SU = "su";
public static final String COMMAND_SH = "sh";
public static final String COMMAND_EXIT = "exit\n";
public static final String COMMAND_LINE_END = "\n";
private ShellUtils() {
throw new AssertionError();
}
/**
* check whether has root permission,检测是否有root权限
*
* @return true--有;false--没有
*/
public static boolean checkRootPermission() {
return execCommand("echo root", true, false).result == 0;
}
/**
* execute shell command, default return result msg,执行shell命令,默认返回消息串
*
* @param command command
* @param isRoot whether need to run with root 是否以su用户执行(需要手机已经root)
* @return CommandResult
* @see ShellUtils#execCommand(String[], boolean, boolean)
*/
public static CommandResult execCommand(String command, boolean isRoot) {
return execCommand(new String[]{command}, isRoot, true);
}
/**
* execute shell commands, default return result msg
*
* @param commands command list
* @param isRoot whether need to run with root 是否以su用户执行(需要手机已经root)
* @return CommandResult
* @see ShellUtils#execCommand(String[], boolean, boolean)
*/
public static CommandResult execCommand(List<String> commands, boolean isRoot) {
return execCommand(commands == null ? null : commands.toArray(new String[]{}), isRoot, true);
}
/**
* execute shell commands, default return result msg
*
* @param commands command array 依次要执行的shell命令数组
* @param isRoot whether need to run with root 是否以su用户执行(需要手机已经root)
* @return CommandResult
* @see ShellUtils#execCommand(String[], boolean, boolean)
*/
public static CommandResult execCommand(String[] commands, boolean isRoot) {
return execCommand(commands, isRoot, true);
}
/**
* execute shell command
*
* @param command command 要执行的shell命令
* @param isRoot whether need to run with root 是否以su用户执行(需要手机已经root)
* @param isNeedResultMsg whether need result msg 是否存储命令执行成功及失败后的信息
* @return CommandResult
* @see ShellUtils#execCommand(String[], boolean, boolean)
*/
public static CommandResult execCommand(String command, boolean isRoot, boolean isNeedResultMsg) {
return execCommand(new String[]{command}, isRoot, isNeedResultMsg);
}
/**
* execute shell commands
*
* @param commands command list 依次要执行的shell命令集合
* @param isRoot whether need to run with root 是否以su用户执行(需要手机已经root)
* @param isNeedResultMsg whether need result msg 是否存储命令执行成功及失败后的信息
* @return CommandResult
* @see ShellUtils#execCommand(String[], boolean, boolean)
*/
public static CommandResult execCommand(List<String> commands, boolean isRoot, boolean isNeedResultMsg) {
return execCommand(commands == null ? null : commands.toArray(new String[]{}), isRoot, isNeedResultMsg);
}
/**
* execute shell commands
*
* @param commands command array 依次要执行的shell命令数组
* @param isRoot whether need to run with root 是否以su用户执行(需要手机已经root)
* @param isNeedResultMsg whether need result msg 是否存储命令执行成功及失败后的信息
* @return <ul>
* <li>if isNeedResultMsg is false, {@link CommandResult#successMsg} is null and
* {@link CommandResult#errorMsg} is null.</li>
* <li>if {@link CommandResult#result} is -1, there maybe some exception.</li>
* </ul>
*/
public static CommandResult execCommand(String[] commands, boolean isRoot, boolean isNeedResultMsg) {
int result = -1;
if (commands == null || commands.length == 0) {
return new CommandResult(result, null, null);
}
Process process = null;
BufferedReader successResult = null;
BufferedReader errorResult = null;
StringBuilder successMsg = null;
StringBuilder errorMsg = null;
DataOutputStream os = null;
try {
process = Runtime.getRuntime().exec(isRoot ? COMMAND_SU : COMMAND_SH);
os = new DataOutputStream(process.getOutputStream());
for (String command : commands) {
if (command == null) {
continue;
}
// do not use os.writeBytes(command), avoid chinese charset error
os.write(command.getBytes());
os.writeBytes(COMMAND_LINE_END);
os.flush();
}
os.writeBytes(COMMAND_EXIT);
os.flush();
result = process.waitFor();
// get command result
if (isNeedResultMsg) {
successMsg = new StringBuilder();
errorMsg = new StringBuilder();
successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String s;
while ((s = successResult.readLine()) != null) {
successMsg.append(s);
}
while ((s = errorResult.readLine()) != null) {
errorMsg.append(s);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (os != null) {
os.close();
}
if (successResult != null) {
successResult.close();
}
if (errorResult != null) {
errorResult.close();
}
} catch (IOException e) {
e.printStackTrace();
}
if (process != null) {
process.destroy();
}
}
return new CommandResult(result, successMsg == null ? null : successMsg.toString(), errorMsg == null ? null
: errorMsg.toString());
}
/**
* result of command,命令执行后返回的数据结构
* <ul>
* <li>{@link CommandResult#result} means result of command, 0 means normal, else means error, same to execute in
* linux shell</li>
* <li>{@link CommandResult#successMsg} means success message of command result</li>
* <li>{@link CommandResult#errorMsg} means error message of command result</li>
* </ul>
*
* @author <a href="http://www.trinea.cn" target="_blank">Trinea</a> 2013-5-16
*
* 应用场景实例:
*
* (1) 拷贝文件
* 原理是adb执行命令:
* mount -o rw,remount /system
* cp /mnt/sdcard/xx.apk /system/app/
* Java代码如下:
*
* String[] commands = new String[] { "mount -o rw,remount /system", "cp /mnt/sdcard/xx.apk /system/app/" };
* CommandResult result = ShellUtils.execCommand(commands, true);
* 注意一般拷贝文件是不需要root的,上面用root是因为需要拷贝到/system/app/下面
*
* (2) 静默安装和卸载
* Android root权限静默安装或卸载应用,原理是执行命令:pm install apkFilePath及pm uninstall packageName
* 具体代码可见:PackageUtils installSilent(Context context, String filePath, String pmParams)
*
* (3)获取系统设置->存储->首选安装位置
* 原理是执行命令:pm get-install-location
* 具体代码可见:PackageUtils getInstallLocation()
*
* (4) Android修改hosts文件
* 原理是执行命令:
* mount -o rw,remount /system
* echo “127.0.0.1 localhost” > /etc/hosts
* echo “185.31.17.184 github.global.ssl.fastly.net” >> /etc/hosts
* chmod 644 /etc/hosts
* 代码如下:
* Java
*
* List<String> commnandList = new ArrayList<String>();
* commnandList.add("mount -o rw,remount /system");
* commnandList.add("echo \"127.0.0.1 localhost\" > /etc/hosts");
* commnandList.add("echo \"185.31.17.184 github.global.ssl.fastly.net\" >> /etc/hosts");
* commnandList.add("chmod 644 /etc/hosts");
* CommandResult result = ShellUtils.execCommand(commnandList, true);
*
* 用echo命令改hosts文件不用重启可以直接生效
*/
public static class CommandResult {
/**
* result of command:result表示执行的结果,根据linux命令执行规则,0表示成功,其他为相应错误码
**/
public int result;
/**
* success message of command result:successMsg存储执行成功后的输出信息,errorMsg存储执行失败后的输出信息
**/
public String successMsg;
/**
* error message of command result:失败信息,如果isNeedResultMsg为false,successMsg和errorMsg会始终为空,而result依然为正常结果
**/
public String errorMsg;
public CommandResult(int result) {
this.result = result;
}
public CommandResult(int result, String successMsg, String errorMsg) {
this.result = result;
this.successMsg = successMsg;
this.errorMsg = errorMsg;
}
@NonNull
@Override
public String toString() {
return "CommandResult{" +
"result=" + result +
", successMsg='" + successMsg + '\'' +
", errorMsg='" + errorMsg + '\'' +
'}';
}
}
}