Android webview, загружающий файл javascript в папку assets

#javascript #android #webview #android-webview

#javascript #Android #webview #android-webview

Вопрос:

Я видел, что этот вопрос задавали много раз, но до сих пор не могу заставить мой код работать.

Я хочу, чтобы мой webview загружал некоторый URL (скажем www.google.com ) а затем примените некоторый javascript, хранящийся в assets/jstest.js , который содержит следующее:

 function test(){
document.bgColor="#00FF00"; //turns to green the background color
}
  

И вот здесь я пытаюсь загрузить JS:

 @Override  
public void onPageFinished(WebView view, String url){
    view.loadUrl("javascript:(function() { "
                  " document.bgColor='#FF0000';" //turns to red the background color
                  " var script=document.createElement('script'); "
                  " script.setAttribute('type','text/javascript'); "
                  " script.setAttribute('src', 'file:///android_asset/jstest.js'); "
                  " script.onload = function(){ "
                  "     test(); "
                  " }; "
                  " document.getElementsByTagName('head')[0].appendChild(script); "
                  "})()"); 
} 
  

Я знаю, что javascript здесь работает, потому что цвет фона на самом деле становится красным, но по какой-то причине он не загружается jstest.js . Я думаю, что проблема может быть в пути к файлу (я уверен, что каждая вторая строка кода javascript верна), но для меня это выглядит правильно. И файл находится в нужной папке.

Чего я не понимаю?

Редактировать:

Поскольку WebResourceResponse класс доступен только с API 11-го уровня, вот что я выяснил в конце.

 public void onPageFinished(WebView view, String url){
        String jscontent = "";
        try{
            InputStream is = am.open("jstest.js"); //am = Activity.getAssets()
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);

            String line;
            while (( line = br.readLine()) != null) {
                jscontent  = line;
            }
            is.close(); 
        }
        catch(Exception e){}
        view.loadUrl("javascript:("   jscontent   ")()"); 
    } 
  

с jstest.js просто содержащим:

 function() {
    document.bgColor="#00FF00";
}
  

Ответ №1:

Я попробовал то же самое, загрузив букмарклет (код javascript в вашем вызове loadUrl ()) на стороннюю страницу. Мой букмарклет также зависит от других ресурсов (файлов javascript и css), которые не будут загружаться с файлом: ///URL android_asset.

Это потому, что контекст безопасности страницы по-прежнему соответствует, например,http://www.google.com, и это не разрешенный доступ к file: URLS. Вы должны быть в состоянии увидеть ошибки, если вы укажете / переопределите WebChromeClient.onConsoleMessage().

В итоге я получил ошибку, в которой я изменил ссылки на ресурсы букмарклета на фиктивную схему URL, например:

 asset:foo/bar/baz.js
  

и добавлено переопределение WebViewClient.shouldInterceptRequest(), которое ищет их и загружает из ресурсов с помощью AssetManager.open().

Единственное, что мне не нравится в этом клудже, это то, что asset: scheme открыт для любого стороннего HTML / Javascript на любой странице, загружаемой моим представлением, предоставляя им доступ к ресурсам моего приложения.

Одной из альтернатив, которую я не пробовал, было бы встроить вложенные ресурсы в букмарклет с использованием data: URLs, но это может стать громоздким.

Я бы предпочел, чтобы существовал способ манипулировать контекстом безопасности только букмарклета JS, который я загружаю в loadUrl (), но я не могу найти ничего подобного.

Вот фрагмент:

 import android.webkit.WebResourceResponse;
...
    private final class FooViewClient extends WebViewClient
    {
    private final String bookmarklet;
    private final String scheme;

    private FooViewClient(String bookmarklet, String scheme)
        {
        this.bookmarklet = bookmarklet;
        this.scheme = scheme;
        }

    @Override
    public void onPageFinished(WebView view, String url)
        {
        view.loadUrl(bookmarklet);
        }

    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, String url)
        {
        if (url.startsWith(scheme))
            try
                {
                return new WebResourceResponse(url.endsWith("js") ? "text/javascript" : "text/css", "utf-8",
                        Foo.this.getAssets().open(url.substring(scheme.length())));
                }
            catch (IOException e)
                {
                Log.e(getClass().getSimpleName(), e.getMessage(), e);
                }

        return null;
        }
    }
  

Комментарии:

1. Хорошо, похоже, что для этой проблемы нет «чистого» решения.. Я думал о написании всего кода javascript внутри loadUrl (), который работал бы без проблем, но, конечно, это был бы не лучший выбор. Итак, я пробовал ваш kludge, но, похоже, я не могу переопределить shouldInterceptRequest() — я не могу импортировать класс WebResourceResponse — не могли бы вы помочь мне опубликовать некоторый код?

2. Я пробовал ваш фрагмент, но класс WebResourceResponse доступен только с API 11-го уровня (я использую 10-й уровень), поэтому, боюсь, это не сработает .. в любом случае, я думаю, что нашел другой обходной путь! Еще раз спасибо за вашу помощь!

3. приятно! Другим вариантом было бы ввести ваш локальный JS (из assets) во входной поток, который считывает WevView, который также был бы прозрачен для WebView. Но мне больше нравится ваше решение, потому что оно более удобное. Хорошо, вместо использования схемы URL я бы предпочел использовать какой-то хорошо известный путь для сопоставления с активами, а не явно раскрывать схему, т. Е. <script src =»jsl/jquery.js «></script> завершится загрузкой jquery.js из локальных ресурсов из-за пути «jsl», который известен моему приложению для предоставления ресурсов из asset.

Ответ №2:

Я думаю, что клиент iceam cream webview от cordova делает именно то, что вы хотите сделать.

Было бы неплохо, если бы это было где-нибудь задокументировано, но, насколько я вижу, это не так.

Взгляните на Android github от cordova: https://github.com/apache/incubator-cordova-android/blob/master/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java

Ответ №3:

Вот как я в итоге это сделал. Я использовал протокол Content: // и настроил contentprovider для обработки возврата файлового дескриптора в систему

Вот мой fileContentProvider:

 import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.io.IOUtils;


import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.util.Log;

public class FileContentProvider extends ContentProvider {
    @Override
    public ParcelFileDescriptor openFile(Uri uri, String mode) {

        Log.d("FileContentProvider","fetching: "   uri);

        ParcelFileDescriptor parcel = null;

        String fileNameRequested = uri.getLastPathSegment();
        String[] name=fileNameRequested.split("\.");
        String prefix=name[0];
        String suffix=name[1];
       // String path = getContext().getFilesDir().getAbsolutePath()   "/"   uri.getPath();
        //String path=file:///android_asset/" Consts.FILE_JAVASCRIPT "

/*check if this is a javascript file*/

        if(suffix.equalsIgnoreCase("js")){
        InputStream is = null;
        try {
            is = getContext().getAssets().open("www/" Consts.FILE_JAVASCRIPT);
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }


        File file = stream2file(is,prefix,suffix);
        try {
            parcel = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
        } catch (FileNotFoundException e) {
            Log.e("FileContentProvider", "uri "   uri.toString(), e);
        }
        }
        return parcel;
    }

    /*converts an inputstream to a temp file*/

    public File stream2file (InputStream in,String prefix,String suffix) {
        File tempFile = null;
        try {
            tempFile = File.createTempFile(prefix, suffix);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        tempFile.deleteOnExit();

            FileOutputStream out = null;
            try {
                out = new FileOutputStream(tempFile);
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } 

            try {
                IOUtils.copy(in, out);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        return tempFile;
    }


    @Override
    public boolean onCreate() {
        return true;
    }

    @Override
    public int delete(Uri uri, String s, String[] as) {
        throw new UnsupportedOperationException("Not supported by this provider");
    }

    @Override
    public String getType(Uri uri) {
        throw new UnsupportedOperationException("Not supported by this provider");
    }

    @Override
    public Uri insert(Uri uri, ContentValues contentvalues) {
        throw new UnsupportedOperationException("Not supported by this provider");
    }

    @Override
    public Cursor query(Uri uri, String[] as, String s, String[] as1, String s1) {
        throw new UnsupportedOperationException("Not supported by this provider");
    }

    @Override
    public int update(Uri uri, ContentValues contentvalues, String s, String[] as) {
        throw new UnsupportedOperationException("Not supported by this provider");
    }
}
  

в манифесте я определил поставщика:

 <provider android:name="com.example.mypackage.FileContentProvider"
          android:authorities="com.example.fileprovider"
        />
  

Вот javascript для ввода в webview:

 webView.loadUrl("javascript:(function() { "

             "var script=document.createElement('script'); "
             " script.setAttribute('type','text/javascript'); "
             " script.setAttribute('src', 'content://com.example.fileprovider/myjavascriptfile.js'); "
      /*        " script.onload = function(){ "
             "     test(); "
             " }; "
      */       "document.body.appendChild(script); "
             "})();");
  

и вот myjavascriptfile.js (в качестве примера):

    function changeBackground(color) {
       document.body.style.backgroundColor = color;
   }
  

Ответ №4:

Возможно, у вас могли бы быть ресурсы в виде «шаблонов html / javascript». Вы можете комбинировать различные из этих текстовых источников и строковую логику, чтобы составить желаемый HTML-код для загрузки в WebViewer. Затем вы используете .loadData вместо .loadUrl

Я использую его самостоятельно, и, похоже, он работает довольно хорошо.

Надеюсь, это поможет!

Ответ №5:

При соблюдении следующих двух условий:

  • minSdkVersion 21
  • targetSdkVersion 28

Я могу успешно загрузить любой локальный ресурс (js, png, css) с помощью следующего Java-кода

 @Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
    Uri uri = request.getUrl();
    if (uri.getHost().equals("assets")) {
        try {
            return new WebResourceResponse(
                URLConnection.guessContentTypeFromName(uri.getPath()),
                "utf-8",
                MainActivity.this.getAssets().open(uri.toString().substring(15)));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return null;
}
  

И в HTML-коде я могу использовать

 <link rel="stylesheet" href="https://assets/material.min.css">
<script src="https://assets/material.min.js"></script>
<script src="https://assets/moment-with-locales.min.js"></script> 
<img src="https://assets/stackoverflow.png">
  

В Java также работает следующее (вам также нужно будет добавить favicon.ico к ресурсам)

 webView.loadUrl("https://assets/example.html");
  

Использование https:// в качестве схемы позволяет мне загружать локальные ресурсы со страницы, обслуживаемой через HTTPS, без проблем с безопасностью из-за смешанного содержимого.

Ни одно из них не требует установки:

 webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
webSettings.setDomStorageEnabled(true);
webSettings.setAllowContentAccess(true);
webSettings.setAllowFileAccess(true);
webSettings.setAllowFileAccessFromFileURLs(true);
webSettings.setAllowUniversalAccessFromFileURLs(true);