移动端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




