You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

移动端WebView开发:JavaScript读写本地文本文件可行性问询

安卓WebView读写本地文本文件:可行方案解析

当然可行!不过直接通过WebView里的JavaScript用file://协议读写android_asset目录下的文件是行不通的——因为android_asset是APK包内的只读资源目录,JavaScript的File API没有权限直接访问这个路径的文件,而且即使能访问,也没办法写入(APK本身是只读的)。要实现这个需求,得通过Android原生代码与JavaScript交互的方式来完成。

你的MainActivity代码存在的小问题

看了你的代码,有几个细节会影响功能实现:

  • shouldOverrideUrlLoading方法里重复创建WebViewClient、WebChromeClient,还重复设置WebSettings,这会导致不必要的资源浪费和逻辑混乱
  • 没有配置JS与原生交互的接口,这是实现文件读写的核心环节

完整实现方案

1. 修改MainActivity.java,添加JS交互接口

我们需要给WebView注册一个JavaScript接口,让JS能调用原生的文件读写方法。另外要注意:android_asset目录是只读的,所以写入操作只能放到应用的私有存储目录(比如getFilesDir()),读取asset文件则可以通过原生代码直接读取后传给JS。

修改后的完整代码如下:

import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.webkit.JavascriptInterface;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class MainActivity extends AppCompatActivity {

    private WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        webView = findViewById(R.id.webView);
        WebSettings webSettings = webView.getSettings();
        // 启用JS
        webSettings.setJavaScriptEnabled(true);
        // 启用DOM存储
        webSettings.setDomStorageEnabled(true);
        // 启用缩放控件
        webSettings.setBuiltInZoomControls(true);
        webView.requestFocusFromTouch();

        // 注册JS交互接口,"AndroidFileUtils"是JS中调用的对象名称
        webView.addJavascriptInterface(new FileUtils(this), "AndroidFileUtils");

        // 只设置一次WebViewClient,避免重复创建
        webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
        });

        // 加载本地HTML文件
        webView.loadUrl("file:///android_asset/www/index.html");
    }

    // 封装文件读写工具类,通过@JavascriptInterface注解暴露方法给JS
    public class FileUtils {
        private Context context;

        public FileUtils(Context context) {
            this.context = context;
        }

        // 读取asset目录下的文本文件
        @JavascriptInterface
        public String readAssetFile(String fileName) {
            StringBuilder contentBuilder = new StringBuilder();
            try {
                // 打开asset文件流
                InputStream inputStream = context.getAssets().open(fileName);
                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
                String line;
                while ((line = reader.readLine()) != null) {
                    contentBuilder.append(line).append("\n");
                }
                // 关闭流
                reader.close();
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
                return "读取失败:" + e.getMessage();
            }
            return contentBuilder.toString();
        }

        // 写入文本到应用私有存储目录(asset目录不可写)
        @JavascriptInterface
        public String writeFile(String fileName, String content) {
            try {
                // 获取应用私有存储目录
                File targetFile = new File(context.getFilesDir(), fileName);
                FileOutputStream outputStream = new FileOutputStream(targetFile);
                outputStream.write(content.getBytes());
                outputStream.close();
                return "写入成功,文件路径:" + targetFile.getAbsolutePath();
            } catch (IOException e) {
                e.printStackTrace();
                return "写入失败:" + e.getMessage();
            }
        }
    }
}

2. 在HTML/JS中调用原生接口

接下来,在你的file:///android_asset/www/index.html文件里,就可以通过window.AndroidFileUtils调用原生的读写方法了,示例代码如下:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件读写测试</title>
</head>
<body style="padding: 20px;">
    <button onclick="readAssetData()" style="padding: 10px; margin: 10px 0;">读取asset/data/data.txt</button>
    <button onclick="writeLocalData()" style="padding: 10px; margin: 10px 0;">写入测试文本</button>
    <div id="result" style="margin-top: 20px; white-space: pre-line;"></div>

    <script>
        // 读取asset目录下的文件
        function readAssetData() {
            // 调用原生方法,路径是相对于android_asset的
            var fileContent = window.AndroidFileUtils.readAssetFile("data/data.txt");
            document.getElementById("result").innerText = "读取结果:\n" + fileContent;
        }

        // 写入文本到应用私有存储
        function writeLocalData() {
            var testContent = "这是通过JS调用原生写入的测试内容\n时间:" + new Date().toLocaleString();
            var writeResult = window.AndroidFileUtils.writeFile("test.txt", testContent);
            document.getElementById("result").innerText = "写入结果:\n" + writeResult;
        }
    </script>
</body>
</html>

关键注意事项

  • 权限问题:如果需要写入外部存储(比如SD卡),需要在AndroidManifest.xml中声明WRITE_EXTERNAL_STORAGE权限,Android 6.0及以上还要动态申请权限;但示例中用的是应用私有存储,不需要额外权限
  • 安全问题:Android 4.2及以上版本,只有标注了@JavascriptInterface的方法才会被JS调用,这能避免恶意JS调用原生敏感方法的安全漏洞
  • asset路径规则:调用readAssetFile时,传入的路径是相对于android_asset目录的,比如data/data.txt对应file:///android_asset/data/data.txt
  • asset目录不可写android_asset是APK打包时的资源目录,运行时是只读的,所以写入操作只能放到应用私有存储、外部存储等可写目录

内容的提问来源于stack exchange,提问作者Marian Prekop

火山引擎 最新活动