Android实现上传本地或拍摄的视频到Bmob服务器 - CSDN博客

mikel阅读(1755)

来源: Android实现上传本地或拍摄的视频到Bmob服务器 – CSDN博客

好久没有更博客了,今天来说说如何实现上传本地或拍摄的视频到Bmob服务器吧。其实上传文件的代码格式都差不多,可以修改套用就能实现。

Adndroid中通过Intent 可以实现调用图片、视频、音频、录音、拍照等功能。例如调用图片可以用如下格式:

  1. <span style=“font-size:18px;”>//选择图片 requestCode 返回的标识
  2. Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
  3.  //”Android.intent.action.GET_CONTENT”
  4. intent.setType(contentType);
  5. //查看类型 ,可以在之前定义String IMAGE_UNSPECIFIED = “image/*”;
  6. Intent wrapperIntent = Intent.createChooser(intent, null);
  7. ((Activity) context).startActivityForResult(wrapperIntent, requestCode);
  8. //拍照 REQUEST_CODE_TAKE_PICTURE 为返回的标识
  9. Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
  10. //”Android.media.action.IMAGE_CAPTURE”;
  11. intent.putExtra(MediaStore.EXTRA_OUTPUT, Mms.ScrapSpace.CONTENT_URI);
  12.  // output,Uri.parse(“content://mms/scrapSpace”);
  13. startActivityForResult(intent, REQUEST_CODE_TAKE_PICTURE);</span>

调用视频的格式如下:

  1. <span style=“font-size:18px;”>//拍摄视频
  2. int durationLimit = getVideoCaptureDurationLimit();
  3. //SystemProperties.getInt(“ro.media.enc.lprof.duration”, 60);
  4. Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
  5. intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
  6. intent.putExtra(MediaStore.EXTRA_SIZE_LIMIT, sizeLimit);
  7. intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, durationLimit);
  8. startActivityForResult(intent, REQUEST_CODE_TAKE_VIDEO);
  9. //视频
  10. Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
  11. intent.setType(contentType); //String VIDEO_UNSPECIFIED = “video/*”;
  12. Intent wrapperIntent = Intent.createChooser(intent, null);
  13. ((Activity) context).startActivityForResult(wrapperIntent, requestCode);</span>

还有调用调用录音,添加音频的格式如下:

  1. <span style=“font-size:18px;”>//添加音频
  2. Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
  3. intent.setType(contentType);
  4.  //String VIDEO_UNSPECIFIED = “video/*”;
  5. Intent wrapperIntent = Intent.createChooser(intent, null);
  6. ((Activity) context).startActivityForResult(wrapperIntent, requestCode);
  7. //录音
  8. Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
  9. intent.setType(ContentType.AUDIO_AMR);
  10.  //String AUDIO_AMR = “audio/amr”;
  11. intent.setClassName(“com.android.soundrecorder”,
  12. “com.android.soundrecorder.SoundRecorder”);
  13. ((Activity) context).startActivityForResult(intent, requestCode);</span>

本文着重讲讲如何调用视频格式的功能,并上传到Bmob服务器。一开始使用以上的格式的代码时发现调用出现问题,通过查找资料发现 “Intent intent = new Intent(Intent.ACTION_GET_CONTENT);”这个引用方法对Android 4.0以上版本并不适用,需要改成“Intent intent = new Intent(Intent.ACTION_PICK);”这个格式才能解决问题。打开视频文件的目录后选择视频,或者先选择录制视频再选择上传,如图所示效果:

下面贴上具体代码,首先是上传本地视频的格式代码:

  1. <span style=“font-size:18px;”>Uri uri = data.getData();
  2.  File file = getFileByUri(uri);
  3.  MediaMetadataRetriever mmr=new MediaMetadataRetriever();//实例化MediaMetadataRetriever对象  
  4.  mmr.setDataSource(file.getAbsolutePath());
  5.  Bitmap bitmap=mmr.getFrameAtTime();//获得视频第一帧的Bitmap对象
  6.  String duration = mmr.extractMetadata(android.media.MediaMetadataRetriever.METADATA_KEY_DURATION);//时长(毫秒)
  7.  Log.d(“ddd”,“duration==”+duration);
  8.  int int_duration= Integer.parseInt(duration);
  9.       if(int_duration>300000){
  10. Toast.makeText(getApplicationContext(), “视频时长已超过5分钟,请重新选择”, Toast.LENGTH_SHORT).show();
  11.       }</span>

这里我设置了视频的时间限制,不能超过5分钟,否则上传失败。上传成功,在界面的下方会出现视频第一帧的Bitmap图像。上传成功后,Bmob服务器中显示的视频格式如图:

然后如果是拍摄的视频,代码格式如下:

  1. <span style=“font-size:18px;”>if (cursor != null && cursor.moveToNext()) {
  2. int id = cursor.getInt(cursor.getColumnIndex(VideoColumns._ID));
  3. String filePath = cursor.getString(cursor.getColumnIndex(VideoColumns.DATA));
  4.         bitmap1 = Thumbnails.getThumbnail(getContentResolver(), id, Thumbnails.MICRO_KIND, null);
  5. // ThumbnailUtils类2.2以上可用
  6. // Bitmap bitmap = ThumbnailUtils.createVideoThumbnail(filePath,
  7. // Thumbnails.MICRO_KIND);
  8. Log.d(“ddd”“filepath==” + filePath);
  9. File file1=new File(filePath);
  10. cursor.close();
  11. //上传到Bomb部分的代码
  12. final BmobFile icon1 = new BmobFile(file1);
  13.   icon1.upload(thisnew UploadFileListener() {
  14.   @Override
  15.  public void onSuccess() {
  16.       //Person person = new Person();             
  17.       shipin.setDescribe(describe);
  18.       shipin.setTitle(title);
  19.       shipin.setIcon(icon1);
  20.       shipin.setAuthor(author);///////////////////////////
  21.       shipin.setUser(user1);///////////////////////////
  22.       shipin.setUsernames(usernames1);////////////////////////
  23.       shipin.setAtavars(avatars1);//////////////////////////
  24.       shipin.save(Ad2.this);
  25.       Toast.makeText(Ad2.this,“视频上传成功~”,Toast.LENGTH_SHORT).show();
  26.       Toast.makeText(Ad2.this,“视频发表成功~”,Toast.LENGTH_SHORT).show();
  27.     }
  28. @Override
  29.  public void onFailure(int arg0, String arg1) {
  30.            Toast.makeText(Ad2.this,“上传失败,请检查网络~”,Toast.LENGTH_SHORT).show();
  31.  }
  32.  @Override
  33.  public void onProgress(Integer arg0) {//////////////////////////////123
  34.    // TODO Auto-generated method stub
  35.     }
  36.    });
  37.   ivHead.setImageBitmap(bitmap1);
  38.          btn_true.setOnClickListener(new OnClickListener(){
  39.         public void onClick(View v) {
  40.            // TODO Auto-generated method stub
  41.            finish();
  42.            }///////////////////
  43.            });////////////// 
  44.    }</span>

最后在贴一张运行后的效果图:

别忘了添加权限:

  1. <span style=“font-size:18px;”><uses-permission Android:name=“android.permission.INTERNET” />
  2. <uses-permission android:name=“android.permission.MOUNT_UNMOUNT_FILESYSTEMS”  />
  3. <uses-permission android:name=“android.permission.WRITE_EXTERNAL_STORAGE”  /></span>

之后有机会再详细说明视频缩略图的获取显示~谢谢支持~!

前端框架layui下拉框被遮挡问题 - CSDN博客

mikel阅读(1528)

visible

来源: 前端框架layui下拉框被遮挡问题 – CSDN博客

在使用layui做下拉框选项的时候发现有时候下拉框会被遮挡或者根本就出不来的现象

于是看了一下审查元素发现原本的div只有这么大

仔细找了一下发现是layui自带的class属性问题,overflow:hidden,所以下拉框出现的内容被隐藏了

解决方法:在页面里面找到对应div的class 给overflow新的属性visible即可(默认值。内容不会被修剪,会呈现在元素框之外。)即可显示出下拉框

hidden的属性为下

List.Distinct() - 银光 - 博客园

mikel阅读(2149)

来源: List.Distinct() – 银光 – 博客园

class ListDistinctDemo
{
static void Main(string[] args)
{
List<Person> personList = new List<Person>(){
new Person(3),//重复数据
new Person(3),
new Person(2),
new Person(1)
};

//使用匿名方法
List<Person> delegateList = personList.Distinct(new Compare<Person>(
delegate(Person x, Person y)
{
if (null == x || null == y) return false;
return x.ID == y.ID;
})).ToList();

delegateList.ForEach(s => Console.WriteLine(s.ID));

//使用 Lambda 表达式
List<Person> lambdaList = personList.Distinct(new Compare<Person>(
(x, y) => (null != x && null != y) && (x.ID == y.ID))).ToList();

lambdaList.ForEach(s => Console.WriteLine(s.ID));

//排序
personList.Sort((x, y) => x.ID.CompareTo(y.ID));
personList.ForEach(s => Console.WriteLine(s.ID));

}
}
public class Person
{
public int ID { get; set; }
public string Name { get; set; }

public Person(int id)
{
this.ID = id;
}
}

public delegate bool EqualsComparer<T>(T x, T y);

public class Compare<T> : IEqualityComparer<T>
{
private EqualsComparer<T> _equalsComparer;

public Compare(EqualsComparer<T> equalsComparer)
{
this._equalsComparer = equalsComparer;
}

public bool Equals(T x, T y)
{
if (null != this._equalsComparer)
return this._equalsComparer(x, y);
else
return false;
}

public int GetHashCode(T obj)
{
return obj.ToString().GetHashCode();
}
}

深坑之Webview,解决H5调用android相机拍照和录像 - CSDN博客

mikel阅读(3357)

来源: 深坑之Webview,解决H5调用android相机拍照和录像 – CSDN博客

最近在开发过程中遇到一个问题,主要是调用第三方的实名认证,需要拍照和录像;

办过支付宝大宝卡和腾讯的大王卡的都知道这玩意,办卡的时候就需要进行实名认证,人脸识别;

本来第三方平台(xxx流量公司)说的是直接用WebView加载这个H5界面就完事了,我心想这么简单,那不是分分钟的事,放着后面做(公司就我一个安卓,所以开发都是我说的算^_^,独立开发有的时候还是挺爽);

结果到项目快要上线的时候,只想说一句mmp,根本调不了相机,这个时候怎么搞,只有自己去实现了,才发现自己已经进了webview的深坑;

到处找资料,发现webview根本不能让h5自己调用,ios是可以的,项目经理就说是我的锅,真特么又一句mmp(关键是这个H5还特么不能改,不能提供给我调用的方法);

进入正题,首先来了解webview,这里我分享两篇大佬的博客
1,WebView开车指南
2,WebView详解
看完这两篇基本你已经可以随意操作webview了, 但是,当你调用相机的时候你才发现这只是入坑的开始

第一个坑

调用相机之后,文件回调不了,其实根本就是没有回调.这个时候你要去检查你的webview的Settings了关键代码,是否给足了权限;

   WebSettings settings = webView.getSettings();
        settings.setUseWideViewPort(true);
        settings.setLoadWithOverviewMode(true);
        settings.setDomStorageEnabled(true);
        settings.setDefaultTextEncodingName("UTF-8");
        settings.setAllowContentAccess(true); // 是否可访问Content Provider的资源,默认值 true
        settings.setAllowFileAccess(true);    // 是否可访问本地文件,默认值 true
        // 是否允许通过file url加载的Javascript读取本地文件,默认值 false
        settings.setAllowFileAccessFromFileURLs(false);
        // 是否允许通过file url加载的Javascript读取全部资源(包括文件,http,https),默认值 false
        settings.setAllowUniversalAccessFromFileURLs(false);
        //开启JavaScript支持
        settings.setJavaScriptEnabled(true);
        // 支持缩放
        settings.setSupportZoom(true);

其中settings.setDomStorageEnabled(true);这个方法当时我没加,血崩了一次;

第二个坑

WebChromeClient的openFileChooser()只调用了一次

首先了解为什么这个方法只调用了一次,看这篇博客就可
Android开发深入理解WebChromeClient之onShowFileChooser或openFileChooser使用说明
看了他基本你就了解怎么回事了

第三个坑
怎么区分是要调用相机是需要拍照还是录视频
这个时候你就要看WebViewClient的 shouldOverrideUrlLoading()方法了,我是通过拦截url来判断url里面是拍照还是录制视频来加一个flag
这样你调用你的相机的时候就可以分别处理了

第四个坑
Android 7.0的FileProvider的坑
看洪阳大佬的这篇博客基本就了解了
Android 7.0 行为变更 通过FileProvider在应用间共享文件吧

最后附上我自己的代码吧

package com.sihaiwanlian.cmccnev.feature.verify.activitys;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ClipData;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.SystemClock;
import android.provider.MediaStore;
import android.support.annotation.RequiresApi;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import com.orhanobut.logger.Logger;
import com.sihaiwanlian.cmccnev.R;
import com.sihaiwanlian.cmccnev.utils.PhotoUtils;

import java.io.File;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class Certifica extends AppCompatActivity {
    private final static String TAG = "villa";
    @BindView(R.id.titleBar_iv_back)
    ImageView mTitleBarIvBack;
    @BindView(R.id.titleBar_btn_back)
    Button mTitleBarBtnBack;
    @BindView(R.id.titleBar_centerTV)
    TextView mTitleBarCenterTV;
    private WebView webView;
    private ValueCallback<Uri> mUploadMessage;
    private ValueCallback<Uri[]> mUploadCallbackAboveL;
    private final static int PHOTO_REQUEST = 100;
    private final static int VIDEO_REQUEST = 120;
    private final static String url = "your_url";
    private boolean videoFlag = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_certifica);
        ButterKnife.bind(this);
        initToolBar();
        initWebView();
    }

    private void initToolBar() {
        mTitleBarCenterTV.setText("实名认证");
    }

    //初始化webView
    private void initWebView() {
        //从布局文件中扩展webView  
        webView = (WebView) this.findViewById(R.id.certifi_webview);
        initWebViewSetting();
    }

    //初始化webViewSetting
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    private void initWebViewSetting() {
        WebSettings settings = webView.getSettings();
        settings.setUseWideViewPort(true);
        settings.setLoadWithOverviewMode(true);
        settings.setDomStorageEnabled(true);
        settings.setDefaultTextEncodingName("UTF-8");
        settings.setAllowContentAccess(true); // 是否可访问Content Provider的资源,默认值 true
        settings.setAllowFileAccess(true);    // 是否可访问本地文件,默认值 true
        // 是否允许通过file url加载的Javascript读取本地文件,默认值 false
        settings.setAllowFileAccessFromFileURLs(false);
        // 是否允许通过file url加载的Javascript读取全部资源(包括文件,http,https),默认值 false
        settings.setAllowUniversalAccessFromFileURLs(false);
        //开启JavaScript支持
        settings.setJavaScriptEnabled(true);
        // 支持缩放
        settings.setSupportZoom(true);
        //辅助WebView设置处理关于页面跳转,页面请求等操作
        webView.setWebViewClient(new MyWebViewClient());
        //辅助WebView处理图片上传操作
        webView.setWebChromeClient(new MyChromeWebClient());
        //加载地址
        webView.loadUrl(url);
    }

    @OnClick(R.id.titleBar_btn_back)
    public void onViewClicked() {
        if (webView.canGoBack()) {
            webView.goBack();// 返回前一个页面
        } else {
            finish();
        }
    }

    //自定义 WebViewClient 辅助WebView设置处理关于页面跳转,页面请求等操作【处理tel协议和视频通讯请求url的拦截转发】
    private class MyWebViewClient extends WebViewClient {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            Logger.e(url);
            if (!TextUtils.isEmpty(url)) {
                videoFlag = url.contains("vedio");
            }
            if (url.trim().startsWith("tel")) {//特殊情况tel,调用系统的拨号软件拨号【<a href="tel:1111111111">1111111111</a>】
                Intent i = new Intent(Intent.ACTION_VIEW);
                i.setData(Uri.parse(url));
                startActivity(i);
            } else {
                String port = url.substring(url.lastIndexOf(":") + 1, url.lastIndexOf("/"));//尝试要拦截的视频通讯url格式(808端口):【http://xxxx:808/?roomName】
                if (port.equals("808")) {//特殊情况【若打开的链接是视频通讯地址格式则调用系统浏览器打开】
                    Intent i = new Intent(Intent.ACTION_VIEW);
                    i.setData(Uri.parse(url));
                    startActivity(i);
                } else {//其它非特殊情况全部放行
                    view.loadUrl(url);
                }
            }
            return true;
        }
    }

    private File fileUri = new File(Environment.getExternalStorageDirectory().getPath() + "/" + SystemClock.currentThreadTimeMillis() + ".jpg");
    private Uri imageUri;

    //自定义 WebChromeClient 辅助WebView处理图片上传操作【<input type=file> 文件上传标签】
    public class MyChromeWebClient extends WebChromeClient {
        // For Android 3.0-  
        public void openFileChooser(ValueCallback<Uri> uploadMsg) {
            Log.d(TAG, "openFileChoose(ValueCallback<Uri> uploadMsg)");
            mUploadMessage = uploadMsg;
            if (videoFlag) {
                recordVideo();
            } else {
                takePhoto();
            }

        }

        // For Android 3.0+  
        public void openFileChooser(ValueCallback uploadMsg, String acceptType) {
            Log.d(TAG, "openFileChoose( ValueCallback uploadMsg, String acceptType )");
            mUploadMessage = uploadMsg;
            if (videoFlag) {
                recordVideo();
            } else {
                takePhoto();
            }
        }

        //For Android 4.1  
        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
            Log.d(TAG, "openFileChoose(ValueCallback<Uri> uploadMsg, String acceptType, String capture)");
            mUploadMessage = uploadMsg;
            if (videoFlag) {
                recordVideo();
            } else {
                takePhoto();
            }
        }

        // For Android 5.0+  
        public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
            Log.d(TAG, "onShowFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture)");
            mUploadCallbackAboveL = filePathCallback;
            if (videoFlag) {
                recordVideo();
            } else {
                takePhoto();
            }
            return true;
        }
    }

    /**
     * 拍照
     */
    private void takePhoto() {
        imageUri = Uri.fromFile(fileUri);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            imageUri = FileProvider.getUriForFile(Certifica.this, getPackageName() + ".fileprovider", fileUri);//通过FileProvider创建一个content类型的Uri

        }
        PhotoUtils.takePicture(Certifica.this, imageUri, PHOTO_REQUEST);
    }

    /**
     * 录像
     */
    private void recordVideo() {
        Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
        intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
        //限制时长
        intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10);
        //开启摄像机
        startActivityForResult(intent, VIDEO_REQUEST);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        //如果按下的是回退键且历史记录里确实还有页面
        if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) {
            webView.goBack();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == PHOTO_REQUEST) {
            if (null == mUploadMessage && null == mUploadCallbackAboveL) return;
            Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
            if (mUploadCallbackAboveL != null) {
                onActivityResultAboveL(requestCode, resultCode, data);
            } else if (mUploadMessage != null) {
                mUploadMessage.onReceiveValue(result);
                mUploadMessage = null;
            }
        } else if (requestCode == VIDEO_REQUEST) {
            if (null == mUploadMessage && null == mUploadCallbackAboveL) return;

            Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
            if (mUploadCallbackAboveL != null) {
                if (resultCode == RESULT_OK) {
                    mUploadCallbackAboveL.onReceiveValue(new Uri[]{result});
                    mUploadCallbackAboveL = null;
                } else {
                    mUploadCallbackAboveL.onReceiveValue(new Uri[]{});
                    mUploadCallbackAboveL = null;
                }

            } else if (mUploadMessage != null) {
                if (resultCode == RESULT_OK) {
                    mUploadMessage.onReceiveValue(result);
                    mUploadMessage = null;
                } else {
                    mUploadMessage.onReceiveValue(Uri.EMPTY);
                    mUploadMessage = null;
                }

            }
        }
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void onActivityResultAboveL(int requestCode, int resultCode, Intent data) {
        if (requestCode != PHOTO_REQUEST || mUploadCallbackAboveL == null) {
            return;
        }
        Uri[] results = null;
        if (resultCode == Activity.RESULT_OK) {
            if (data == null) {
                results = new Uri[]{imageUri};
            } else {
                String dataString = data.getDataString();
                ClipData clipData = data.getClipData();
                if (clipData != null) {
                    results = new Uri[clipData.getItemCount()];
                    for (int i = 0; i < clipData.getItemCount(); i++) {
                        ClipData.Item item = clipData.getItemAt(i);
                        results[i] = item.getUri();
                    }
                }

                if (dataString != null)
                    results = new Uri[]{Uri.parse(dataString)};
            }
        }
        mUploadCallbackAboveL.onReceiveValue(results);
        mUploadCallbackAboveL = null;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (webView != null) {
            webView.setWebViewClient(null);
            webView.setWebChromeClient(null);
            webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
            webView.clearHistory();
//            ((ViewGroup) webView.getParent()).removeView(webView);
            webView.destroy();
            webView = null;
        }
    }
}  

这里面有个photoUtils,已经封装好了各种调用,简单好用

public class PhotoUtils {
    private static final String TAG = "PhotoUtils";

    /**
     * @param activity    当前activity
     * @param imageUri    拍照后照片存储路径
     * @param requestCode 调用系统相机请求码
     */
    public static void takePicture(Activity activity, Uri imageUri, int requestCode) {
        //调用系统相机
        Intent intentCamera = new Intent();
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                intentCamera.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //添加这一句表示对目标应用临时授权该Uri所代表的文件
            }
            intentCamera.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
            //将拍照结果保存至photo_file的Uri中,不保留在相册中
            intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
        if (activity!=null){
            activity.startActivityForResult(intentCamera, requestCode);
        }
    }

    /**
     * @param activity    当前activity
     * @param requestCode 打开相册的请求码
     */
    public static void openPic(Activity activity, int requestCode) {
        Intent photoPickerIntent = new Intent(Intent.ACTION_GET_CONTENT);
        photoPickerIntent.setType("image/*");
        activity.startActivityForResult(photoPickerIntent, requestCode);
    }

    /**
     * @param activity    当前activity
     * @param orgUri      剪裁原图的Uri
     * @param desUri      剪裁后的图片的Uri
     * @param aspectX     X方向的比例
     * @param aspectY     Y方向的比例
     * @param width       剪裁图片的宽度
     * @param height      剪裁图片高度
     * @param requestCode 剪裁图片的请求码
     */
    public static void cropImageUri(Activity activity, Uri orgUri, Uri desUri, int aspectX, int aspectY, int width, int height, int requestCode) {
        Intent intent = new Intent("com.android.camera.action.CROP");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
        intent.setDataAndType(orgUri, "image/*");
        intent.putExtra("crop", "true");
        intent.putExtra("aspectX", aspectX);
        intent.putExtra("aspectY", aspectY);
        intent.putExtra("outputX", width);
        intent.putExtra("outputY", height);
        intent.putExtra("scale", true);
        //将剪切的图片保存到目标Uri中
        intent.putExtra(MediaStore.EXTRA_OUTPUT, desUri);
        intent.putExtra("return-data", false);
        intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
        intent.putExtra("noFaceDetection", true);
        activity.startActivityForResult(intent, requestCode);
    }

    /**
     * 读取uri所在的图片
     *
     * @param uri      图片对应的Uri
     * @param mContext 上下文对象
     * @return 获取图像的Bitmap
     */
    public static Bitmap getBitmapFromUri(Uri uri, Context mContext) {
        try {
//            Bitmap bitmap = MediaStore.Images.Media.getBitmap(mContext.getContentResolver(), uri);
            Bitmap bitmapFormUri = getBitmapFormUri(mContext, uri);
            return bitmapFormUri;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 通过uri获取图片并进行压缩
     *
     * @param uri
     */
    public static Bitmap getBitmapFormUri(Context ac, Uri uri) throws FileNotFoundException, IOException {
        InputStream input = ac.getContentResolver().openInputStream(uri);
        BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
        onlyBoundsOptions.inJustDecodeBounds = true;
        onlyBoundsOptions.inDither = true;//optional
        onlyBoundsOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional
        BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
        input.close();
        int originalWidth = onlyBoundsOptions.outWidth;
        int originalHeight = onlyBoundsOptions.outHeight;
        if ((originalWidth == -1) || (originalHeight == -1)){
            return null;
        }
        //图片分辨率以480x800为标准
        float hh = 800f;//这里设置高度为800f
        float ww = 480f;//这里设置宽度为480f
        //缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int be = 1;//be=1表示不缩放
        if (originalWidth > originalHeight && originalWidth > ww) {//如果宽度大的话根据宽度固定大小缩放
            be = (int) (originalWidth / ww);
        } else if (originalWidth < originalHeight && originalHeight > hh) {//如果高度高的话根据宽度固定大小缩放
            be = (int) (originalHeight / hh);
        }
        if (be <= 0){
            be = 1;
        }
        //比例压缩
        BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
        bitmapOptions.inSampleSize = be;//设置缩放比例
        bitmapOptions.inDither = true;//optional
        bitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional
        input = ac.getContentResolver().openInputStream(uri);
        Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
        input.close();
        return compressImage(bitmap);//再进行质量压缩
    }

    /**
     * 质量压缩方法
     *
     * @param image
     * @return
     */
    public static Bitmap compressImage(Bitmap image) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
        int options = 100;
        while (baos.toByteArray().length / 1024 > 100) {  //循环判断如果压缩后图片是否大于100kb,大于继续压缩
            baos.reset();//重置baos即清空baos
            //第一个参数 :图片格式 ,第二个参数: 图片质量,100为最高,0为最差  ,第三个参数:保存压缩后的数据的流
            image.compress(Bitmap.CompressFormat.JPEG, options, baos);//这里压缩options%,把压缩后的数据存放到baos中
            options -= 10;//每次都减少10
        }
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把压缩后的数据baos存放到ByteArrayInputStream中
        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStream数据生成图片
        return bitmap;
    }


    /**
     * @param context 上下文对象
     * @param uri     当前相册照片的Uri
     * @return 解析后的Uri对应的String
     */
    @SuppressLint("NewApi")
    public static String getPath(final Context context, final Uri uri) {

        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
        String pathHead = "file:///";
        // DocumentProvider
        if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
            // ExternalStorageProvider
            if (isExternalStorageDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];
                if ("primary".equalsIgnoreCase(type)) {
                    return pathHead + Environment.getExternalStorageDirectory() + "/" + split[1];
                }
            }
            // DownloadsProvider
            else if (isDownloadsDocument(uri)) {

                final String id = DocumentsContract.getDocumentId(uri);

                final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

                return pathHead + getDataColumn(context, contentUri, null, null);
            }
            // MediaProvider
            else if (isMediaDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                Uri contentUri = null;
                if ("image".equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }

                final String selection = "_id=?";
                final String[] selectionArgs = new String[]{split[1]};

                return pathHead + getDataColumn(context, contentUri, selection, selectionArgs);
            }
        }
        // MediaStore (and general)
        else if ("content".equalsIgnoreCase(uri.getScheme())) {
            return pathHead + getDataColumn(context, uri, null, null);
        }
        // File
        else if ("file".equalsIgnoreCase(uri.getScheme())) {
            return pathHead + uri.getPath();
        }
        return null;
    }

    /**
     * Get the value of the data column for this Uri. This is useful for
     * MediaStore Uris, and other file-based ContentProviders.
     *
     * @param context       The context.
     * @param uri           The Uri to query.
     * @param selection     (Optional) Filter used in the query.
     * @param selectionArgs (Optional) Selection arguments used in the query.
     * @return The value of the _data column, which is typically a file path.
     */
    private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {

        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = {column};
        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
            if (cursor != null && cursor.moveToFirst()) {
                final int column_index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(column_index);
            }
        } finally {
            if (cursor != null){
                cursor.close();
            }
        }
        return null;
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     */
    private static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     */
    private static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     */
    private static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }

}

详细了解WebChromeClient源码各方法使用说明 - TeachCourse

mikel阅读(1116)

关于WebChromeClient各个方法的使用说明,WebChromeClient和WebViewClient的区别,如何设置WebView的WebChromeClient和WebViewClient,WebView打开文件管理器,上传文件功能实现

来源: 详细了解WebChromeClient源码各方法使用说明 – TeachCourse

摘要:

WebView有几个定制功能方便添加开发者期待的属性。

1、创建并设置一个WebChromeClient子类,该子类在想要改变浏览器的UI界面时回调对应的方法,比如,进度条的更新和JS弹窗

2、创建并设置一个WebViewClient子类,该子类在改变网页内容的呈现方式时回调对应的方法,比如,网页加载错误或提交表单,同时可以通过回调shouldOverrideUrlLoading()方法拦截URL加载。

3、修改WebSettings,配置WebView各种属性,比如:开启JavaScript脚本功能,setJavaScripeEnabled(true)

4、调用addJavaScriptInterface(Object,String)方法将Java对象注入WebView加载的页面,H5开发者通过JavaScript脚本访问Java对象提供的方法或属性。

一、详细分析WebChromeClient个回调方法

WebChromeClient给TeachCourse的第一印象是要么是抽象类,要么是接口,查看源码后发现其实是一个普通的Java类文件,WebChromeClient声明了一个内部接口WebChromeClient.CustomViewCallback、一个内部类WebChromeClient.FileChooserParams和多个空方法体的方法,比如:

  1. /**
  2.      * Tell the host application the current progress of loading a page.
  3.      * @param view The WebView that initiated the callback.
  4.      * @param newProgress Current page loading progress, represented by
  5.      *                    an integer between 0 and 100.
  6.      */
  7.     public void onProgressChanged(WebView view, int newProgress) {}
  8.     /**
  9.      * Notify the host application of a change in the document title.
  10.      * @param view The WebView that initiated the callback.
  11.      * @param title A String containing the new title of the document.
  12.      */
  13.     public void onReceivedTitle(WebView view, String title) {}
  14.     /**
  15.      * Notify the host application of a new favicon for the current page.
  16.      * @param view The WebView that initiated the callback.
  17.      * @param icon A Bitmap containing the favicon for the current page.
  18.      */
  19.     public void onReceivedIcon(WebView view, Bitmap icon) {}

可能你会像TeachCourse一样感到奇怪,为什么声明的是空的方法体呢?如果是接口,还可以理解,能够实现监听接口;或许是抽象类也可以理解,可以实现方法回调,但WebChromeClient偏偏是一个普通的Java类,却可以实现接口的监听和方法的回调功能。

WebView通过setWebChromeClient传入WebChromeClient对象,读完《深入理解接口的定义和意义》这篇文章的小伙伴们会发现类似于设置接口监听,然后重写WebChromeClient或其子类方法,实现类似抽象类的方法回调,比如,上面提到的进度条的更新或提醒应用程序当前网页title发生改变,在WebView切换另一个页面的时候,回调onReceivedTitle方法获取改变的title信息。

其它回调方法:

  1. public void onReceivedTouchIconUrl(WebView view, String url,boolean precomposed) {}

通知加载当前网页的主机应用程序,获取手机触屏的图标

  1. public interface CustomViewCallback {
  2.     /**
  3.      * Invoked when the host application dismisses the
  4.      * custom view.
  5.      */
  6.     public void onCustomViewHidden();
  7. }

这是内部一个接口,由主机应用程序通知当前网页是否隐藏

  1. public void onShowCustomView(View view, CustomViewCallback callback) {};

6.通知主机应用程序当前页面进入全屏模式,在全屏模式下的主机应用程序必须包含网页内容:video或其他特定的HTML标签,与是否视频全屏播放有关的回调方法

  1. @Deprecated
  2. public void onShowCustomView(View view, int requestedOrientation,CustomViewCallback callback) {};

7.通知主机应用程序当前页面想要在某个位置显示一个自定义视图

  1. public void onHideCustomView() {}

8.通知主机应用程序当前页面退出全屏模式,主机必须隐藏自定义视图;效果和onShowCustomView相反

  1. public boolean onCreateWindow(WebView view, boolean isDialog,
  2.          boolean isUserGesture, Message resultMsg) {
  3.      return false;
  4.  }

9.请求主机程序创建一个新的窗口,主机选择这样的请求,该方法必须返回true,将窗口放到视图系统内同时以参数的形式发送提供的消息结果给目标。反之,主机没有选择这样的请求,该方法返回false,默认,该方法的实现返回false。

  1. public void onRequestFocus(WebView view) {}

10.当前WebView请求显示并获取焦点,这样的情况发生在另一个WebView正在打开当前WebView的一条连接同时正在请求显示。

  1. public void onCloseWindow(WebView window) {}

11.通知主机程序关闭WebView同时需要的时候从系统视图中移除,此时,内核停止加载

  1. public boolean onJsAlert(WebView view, String url, String message,
  2.           JsResult result) {
  3.       return false;
  4.   }

12.告诉客户端显示一个JS弹窗,如果客户端返回true,WebView将会接受客户端处理弹窗;反之,返回false,程序继续执行

  1. public boolean onJsConfirm(WebView view, String url, String message,
  2.         JsResult result) {
  3.     return false;
  4. }

13.告诉客户端向用户显示确认对话框,如果客户端返回true,同样,WebView将会接受客户端处理对话框和回调JsResult方法;反之,返回false,程序继续执行,默认返回false

  1. public boolean onJsPrompt(WebView view, String url, String message,
  2.          String defaultValue, JsPromptResult result) {
  3.      return false;
  4.  }

14.告诉客户端向用户显示警告弹窗,如果客户端返回true,同样,WebView将会接受客户端处理警告弹窗和回调JsPromptResult方法;反之,返回false,程序继续执行,默认返回false

  1. public boolean onJsBeforeUnload(WebView view, String url, String message,
  2.         JsResult result) {
  3.     return false;
  4. }

15.告诉客户端展示一个弹窗确认从当前页面离开,这是未加载之前JavaScript事件的结果。如果客户端返回true,WebView将会接受客户端处理确认弹窗和回调JsResult方法,默认true,返回给javascript接受离开当前页面;否则,返回false,默认的行为将会返回false,设置JsResult返回true,将离开当前页面,该方法false,取消离开。

  1. @Deprecated
  2. public void onExceededDatabaseQuota(String url, String databaseIdentifier,
  3.         long quota, long estimatedDatabaseSize, long totalQuota,
  4.         WebStorage.QuotaUpdater quotaUpdater) {
  5.     // This default implementation passes the current quota back to WebCore.
  6.     // WebCore will interpret this that new quota was declined.
  7.     quotaUpdater.updateQuota(quota);
  8. }

16.告诉客户端数据容量已超过Web SQL数据库,对于一个特定的数据源,并要求一个新的容量。客户端通过调用WebStorage.QuotaUpdater提供实例的方法,新容量可以被设置的最小值是当前页面容量,默认以当前页面容量实现响应,以至于容量不会增长。

  1.    @Deprecated
  2. public void onReachedMaxAppCacheSize(long requiredStorage, long quota,
  3.         WebStorage.QuotaUpdater quotaUpdater) {
  4.     quotaUpdater.updateQuota(quota);
  5. }

17.提醒主机程序当前应用缓冲以达到最大值,客户端必须通过调用WebStorage.QuotaUpdater提供的实例方法响应,新容量可以被设置的最小值是当前页面容量,默认以当前页面容量实现响应,以至于容量不会增长。

  1. public void onGeolocationPermissionsShowPrompt(String origin,GeolocationPermissions.Callback callback) {}

18.提醒主机程序来自特殊数据源的网页内容想要使用定位API,当前设置没有允许状态。主机程序应该以期待的权限调用指定的回调,详细信息可以参考GeolocationPermissions

  1. public void onGeolocationPermissionsHidePrompt() {}

19.提醒主机程序请求地理位置权限,先前的调用onGeolocationPermissionsShowPromptonGeolocationPermissionsShowPrompt被取消,所有相关的UI因此被隐藏。

  1. public void onPermissionRequest(PermissionRequest request) {
  2.     request.deny();
  3. }

20.提醒主机程序当前网页内容正在请求访问特殊的资源,当前权限是否获取或禁止,主机程序必须调用PermissionRequest的grant()方法或PermissionRequest的deny()方法。如果该方法没有被重写,默认禁止。

  1. public void onPermissionRequestCanceled(PermissionRequest request) {}

21.提醒主机程序给出的权限请求已经被取消,所有相关的UI因此被隐藏。

  1. public boolean onJsTimeout() {
  2.     return true;
  3. }

22.告诉客户端javascript执行超时,客户端选择是否阻止执行。如果客户端返回true,javascript被阻止;否则,返回false,脚本继续执行。在继续执行的例子中,超时次数将会被重置,同时如果下次检查脚本没有结束,继续回调该方法。

  1. @Deprecated
  2. blic void onConsoleMessage(String message, int lineNumber, String sourceID) { }

23.向主机程序报告javascript错误消息,ChromeClient应该重写该方法检查日志消息正如他们期待的一样。

  1. public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
  2.     // Call the old version of this function for backwards compatability.
  3.     onConsoleMessage(consoleMessage.message(), consoleMessage.lineNumber(),
  4.             consoleMessage.sourceId());
  5.     return false;
  6. }

24.想主机程序报告javascript控制台消息,ChromeClient应该重写该方法检查日志消息正如他们期待的一样。返回true,消息由客户端处理。

  1. public Bitmap getDefaultVideoPoster() {
  2.     return null;
  3. }

25.当没有正在播放视频时,video标签显示“提示”图片,使用的这个“提示”图片可以被video标签的属性指定。如果缺少该属性,使用默认“提示”图片,这个方法运行ChromeClient提供默认的图片。

  1. public View getVideoLoadingProgressView() {
  2.        return null;
  3.    }

26.获取一个显示全屏视频正在缓冲的一个View,主机程序重写该方法获取一个旋转的进度提示或相似的

  1. public void getVisitedHistory(ValueCallback<String[]> callback) {}

27.获取所有被访问选项列表

  1. public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
  2.         FileChooserParams fileChooserParams) {
  3.     return false;
  4. }

28.告诉客户端显示一个文件选择器。该方法被回调处理表单中的“file”输入类型,调用filePathCallback.onReceiveValue(null)方法取消请求打开文件选择器并返回true。

  1. @SystemApi
  2. @Deprecated
  3. public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType, String capture) {
  4.     uploadFile.onReceiveValue(null);
  5. }

29.告诉客户端打开一个文件选择器。

  1. public void setupAutoFill(Message msg) { }

30.告诉客户端被浏览器的页面有一个自动补全表单功能,当用户想要设置一个配置文件。

  1. public static abstract class FileChooserParams {
  2.        public static final int MODE_OPEN = 0;
  3.        public static final int MODE_OPEN_MULTIPLE = 1;
  4.        public static final int MODE_OPEN_FOLDER = 2;
  5.        public static final int MODE_SAVE = 3;
  6.        public static Uri[] parseResult(int resultCode, Intent data) {
  7.            return WebViewFactory.getProvider().getStatics().parseFileChooserResult(resultCode, data);
  8.        }
  9.        public abstract int getMode();
  10.        public abstract String[] getAcceptTypes();
  11.        public abstract boolean isCaptureEnabled();
  12.        public abstract CharSequence getTitle();
  13.        public abstract String getFilenameHint();
  14.        public abstract Intent createIntent();
  15.    }

二、WebChromeClient常见使用实例

分析完WebChromeClient源码的所有方法后,TeachCourse可以肯定其所有的方法在系统的内部被调用,然后通过setWebChromeClient()方法获取传递回来的值,那么我们就可以根据开发需要重写对应的方法,这是WebChromeClient为什么不声明为抽象类或接口的原因吧。

1、onProgressChanged(WebView view, int newProgress)实现各式各样的加载进度

newsProgress是0到100的整数,即每次更新的进度,那么显示进度条视图可以调用ProgressBar或ProgressDialog的setProgress(int)方法,当newProgress达到100时,进度条消失。具体例子参考《ProgressBar+WebView实现自定义浏览器

2、onReceivedTitle(WebView view, String title)显示当前网页的HTML的title标签内容。标准的HTML页面都可能包含一个title标签,类似一篇文章都必须包含标题一样,TeachCourse用WebView开发新闻客户端,喜欢回调用该方法获取title标签内容,然后在浏览器导航栏显示。

PS:查看源码,发现WebView提供的getTitle()方法直接调用返回空值,获取不到当前网页的title标签内容。原因是,只有当WebChromeClient.onReceivedTitle方法被回调的时候,才可以使用上述方法。

3、onReceivedIcon(WebView view, Bitmap icon)获取当前网页的logo图标。该logo显示在浏览器的地址栏,打开浏览器输入任何一个网站,基本都有一个icon图标,如果我们在开发自己的浏览器,就需要这样一个回调方法获取head标签内的link内容,同时结合上面第二个方法一起使用。

PS:同理,调用WebView的getFavicon()方法也必须回调上面方法,否则返回null。

  1. @Override
  2. public void onReceivedTitle(WebView view, String title) {
  3.     super.onReceivedTitle(view, title);
  4.     if (!TextUtils.isEmpty(mWebview.getTitle()))
  5.         mAddressUrl_tv.setText(mWebview.getTitle());
  6. }
  7. @Override
  8. public void onReceivedIcon(WebView view, Bitmap icon) {
  9.     super.onReceivedIcon(view, icon);
  10.     Bitmap bitmap = mWebview.getFavicon();
  11.     Drawable drawable = new BitmapDrawable(bitmap);
  12.     if (drawable != null)
  13.         mAddressUrl_tv.setCompoundDrawablesWithIntrinsicBounds(drawable, nullnullnull);
  14. }

注意:为了保证,调用getTitle()或getFavcion()方法时对应的回调方法已执行,参照上述保险的做法。

4、最后分析一下如何打开文件管理器onShowFileChooser(WebView webView, ValueCallback<uri[]> filePathCallback,FileChooserParams fileChooserParams)方法使用。
1209-2026-how-to-upload-file-by-webview
在WebView中想要回调该方法,在表单中声明input的类型为file,指定手机文件的存放路径。这个时候使用到ValueCallBack接口,取消打开文件管理器,filePathCallback.onReceiveValue(null)方法传入null,同时onShowFileChooser()返回true。</uri[]>

  1. @Override
  2.            public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
  3.                super.onShowFileChooser(webView, filePathCallback, fileChooserParams);
  4.                mFilePathCallback = filePathCallback;
  5.                Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
  6.                intent.addCategory(Intent.CATEGORY_OPENABLE);
  7.                intent.setType(“*/*”);
  8.                startActivityForResult(Intent.createChooser(intent, “选择文件”),
  9.                        REQUEST_FILE_PICKER);
  10.                return false;
  11.            }
  12.            /**
  13.             * 过时的方法:openFileChooser
  14.             */
  15.            public void openFileChooser(ValueCallback<Uri> filePathCallback) {
  16.                mFilePathCallback = filePathCallback;
  17.                Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
  18.                intent.addCategory(Intent.CATEGORY_OPENABLE);
  19.                intent.setType(“image/*”);
  20.                startActivityForResult(
  21.                        Intent.createChooser(intent, “File Chooser”),
  22.                        REQUEST_FILE_PICKER);
  23.            }
  24.            /**
  25.             * 过时的方法:openFileChooser
  26.             */
  27.            public void openFileChooser(ValueCallback filePathCallback,
  28.                                        String acceptType) {
  29.                mFilePathCallback = filePathCallback;
  30.                Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
  31.                intent.addCategory(Intent.CATEGORY_OPENABLE);
  32.                intent.setType(“image/*”);
  33.                startActivityForResult(
  34.                        Intent.createChooser(intent, “File Chooser”),
  35.                        REQUEST_FILE_PICKER);
  36.            }
  37.            /**
  38.             * 过时的方法:openFileChooser
  39.             */
  40.            public void openFileChooser(ValueCallback<Uri> filePathCallback,
  41.                                        String acceptType, String capture) {
  42.                mFilePathCallback = filePathCallback;
  43.                Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
  44.                intent.addCategory(Intent.CATEGORY_OPENABLE);
  45.                intent.setType(“image/*”);
  46.                startActivityForResult(
  47.                        Intent.createChooser(intent, “File Chooser”),
  48.                        REQUEST_FILE_PICKER);
  49.            }

执行流程:WebView加载带表单的HTML标签,同时input类型为file,点击按钮回调onShowFileChooser()方法或openFileChooser()方法,执行打开媒体管理器的操作,完成后选择文件后,在onActivityResult()方法中回调,同时再调接口方法onReceiveValue()将路径显示在WebView加载的表单页面中。

  1. @Override
  2. protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
  3.     super.onActivityResult(requestCode, resultCode, intent);
  4.     if (requestCode == REQUEST_FILE_PICKER && mFilePathCallback != null) {
  5.         Uri result = intent == null || resultCode != RESULT_OK ? null
  6.                 : intent.getData();
  7.         if (result != null) {
  8.             String path = GetPathFromUri4kitkat.getPath(this, result);
  9.             Uri uri = Uri.fromFile(new File(path));
  10.             mFilePathCallback.onReceiveValue(uri);
  11.             String url = uri.getPath();
  12.             Log.d(TAG, “onActivityResult: “ + url);
  13.         } else {
  14.             mFilePathCallback.onReceiveValue(null);
  15.         }
  16.     }
  17. }

1209-2027-how-to-open-filemanager

下一篇《详细分析WebViewClient源码各个回调方法使用说明》

推荐阅读:

一个例子让我理解WebViewClient各方法重写的作用

Android开发深入理解WebChromeClient之onShowFileChooser或openFileChooser使用说明

Android Studio如何引入依赖包 - CSDN博客

mikel阅读(1487)

来源: Android Studio如何引入依赖包 – CSDN博客

直接进正题,第一步如图
第一步 File —>new —>import Module –> 然后选择你要导入的依赖包 –>NEXT—> Finish
File  new  import Module   然后选择你要导入的依赖包  NEXT  Finish
第二步
这里写图片描述
第三步
这里写图片描述
第四步
这里写图片描述
第五步
这里写图片描述
第六步
这里写图片描述

以上步骤操作完毕应该就可以正常的导入依赖库中的文件使用了,但是如果在编译的过程中出现xxxxxx文件丢失/找不到的问题,请参考以下方案

选择你导入的依赖包的build.gradle文件,修改里面的配置
版本号什么的改成和你主项目一样的,下面的true改为false
这里写图片描述
这里写图片描述

不同版本引入的方式存在一些区别 仅供参考

Android开发深入理解WebChromeClient之onShowFileChooser或openFileChooser使用说明 - TeachCourse

mikel阅读(4964)

Android开发使用WebView控件加载包含表单的H5网页,点击上传文件按钮,弹出对话框,选择从相册获取照片、拍照或打开手机文件管理器,从Android手机选取一张图片或一个文件,然后通过ValueCallback接口传递,在WebView加载的H5网页显示。这里有一个问题,点击“取消”或返回按钮,无法重复回调onShowFileChooser或openFileChooser方法

来源: Android开发深入理解WebChromeClient之onShowFileChooser或openFileChooser使用说明 – TeachCourse

Android开发使用WebView控件加载包含表单的H5网页,点击上传文件按钮,弹出对话框,选择从相册获取照片、拍照或打开手机文件管理器,从Android手机选取一张图片或一个文件,然后通过ValueCallback接口传递,在WebView加载的H5网页显示。这里有一个问题,点击“取消”或返回按钮,无法重复回调onShowFileChooser或openFileChooser方法,控制台打印:Attempted to finish an input event but the input event receiver has already been disposed

一、深入理解onShowFileChooser或openFileChooser

上一篇文章TeachCourse详细分析WebChromeClient各个方法的使用,特殊说明关于WebChromeClient,它既不是接口也不是抽象类,但声明的方法很多方法体都是空的,这是让钊林感到疑惑之一,查看WebView源码,setWebChromeClient()传入WebChromeClient对象,然后使用传入的对象,调用WebChromeClient声明的方法,再将一些参数传递返回WebChromeClient空方法体重。在WebView源码里面代码也很简单,详细的处理处理逻辑看不到,这是让钊林感到疑惑之二,感觉像一个黑箱子。

然后就一直想,那么重写WebChromeClient的方法有什么作用呢?先看一下onShowFileChooser,如下:

  1. /**
  2.  * Tell the client to show a file chooser.
  3.  *
  4.  * This is called to handle HTML forms with ‘file’ input type, in response to the
  5.  * user pressing the “Select File” button.
  6.  * To cancel the request, call <code>filePathCallback.onReceiveValue(null)</code> and
  7.  * return true.
  8.  *
  9.  * @param webView The WebView instance that is initiating the request.
  10.  * @param filePathCallback Invoke this callback to supply the list of paths to files to upload,
  11.  *                         or NULL to cancel. Must only be called if the
  12.  *                         <code>showFileChooser</code> implementations returns true.
  13.  * @param fileChooserParams Describes the mode of file chooser to be opened, and options to be
  14.  *                          used with it.
  15.  * @return true if filePathCallback will be invoked, false to use default handling.
  16.  *
  17.  * @see FileChooserParams
  18.  */
  19. public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
  20.         FileChooserParams fileChooserParams) {
  21.     return false;
  22. }

该方法的作用,告诉当前APP,打开一个文件选择器,比如:打开相册、启动拍照或打开本地文件管理器,实际上更好的理解,WebView加载包含上传文件的表单按钮,HTML定义了input标签,同时input的type类型为file,手指点击该按钮,回调onShowFileChooser这个方法,在这个重写的方法里面打开相册、启动照片或打开本地文件管理器,甚至做其他任何的逻辑处理,点击一次回调一次的前提是请求被取消,而取消该请求回调的方法:给ValueCallback接口的onReceiveValue抽象方法传入null,同时onShowFileChooser方法返回true;

ValueCallback的抽象方法被回调onShowFileChooser方法返回true;反之返回false;再来看一下openFileChooser的源码,如下:

  1. /**
  2.  * Tell the client to open a file chooser.
  3.  * @param uploadFile A ValueCallback to set the URI of the file to upload.
  4.  *      onReceiveValue must be called to wake up the thread.a
  5.  * @param acceptType The value of the ‘accept’ attribute of the input tag
  6.  *         associated with this file picker.
  7.  * @param capture The value of the ‘capture’ attribute of the input tag
  8.  *         associated with this file picker.
  9.  *
  10.  * @deprecated Use {@link #showFileChooser} instead.
  11.  * @hide This method was not published in any SDK version.
  12.  */
  13. @SystemApi
  14. @Deprecated
  15. public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType, String capture) {
  16.     uploadFile.onReceiveValue(null);
  17. }

在所有发布的SDK版本中,openFileChooser是一个隐藏的方法,使用onShowFileChooser代替,但是最好同时重写showFileChooser和openFileChooser方法,Android 4.4.X以上的系统回调onShowFileChooser方法,低于或等于Android 4.4.X的系统回调openFileChooser方法,只重写onShowFileChooser或openFileChooser造成在有的系统可以正常回调,在有的系统点击没有反应。

仔细分析onShowFileChooser和openFileChooser回调方法,这两个方法之间的区别,

第一个区别:前者ValueCallback接口回传一个Uri数组,后者回传一个Uri对象,在onActivityResult回调方法中调用ValueCallback接口方法onReceiveValue传入参数特别注意;

  1. /**
  2.  *回调onShowFileChooser方法,onReceiveValue传入Uri对象数组
  3.  */
  4. mFilePathCallback.onReceiveValue(new Uri[]{uri});
  1. /**
  2.  *回调openFileChooser方法,onReceiveValue传入一个Uri对象
  3.  */
  4. mFilePathCallback4.onReceiveValue(uri);

第二个区别:前者将后者的acceptType、capture封装成FileChooserParams抽象类

二、实例展示onShowFileChooser或openFileChooser处理过程

1223-0941-popupdialog-2
这是实例运行的效果图,H5表单写入两个上传文件的按钮,点击其中一个从底部弹出对话框,选择相册文件或拍照,点击“取消”按钮,再次点击“上传文件”按钮能够再次回调onShowFileChooser或openFileChooser方法。

在之前的理解中,误解onShowFileChooser或openFileChooser只能打开相册或启动相机拍照,其实不仅仅是这样,onShowFileChooser或openFileChooser既然是一个回调的方法,可以重复执行各种逻辑代码,比如:启动另一个Activity、弹窗对话框、录制视频或录音等

在上面的例子中,执行弹窗操作,将弹窗的处理代码放置onShowFileChooser或openFileChooser方法体,如下:

  1. @Override
  2. public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
  3.     super.onShowFileChooser(webView, filePathCallback, fileChooserParams);
  4.          popupDialog();
  5.     PickPhotoUtil.mFilePathCallback = filePathCallback;
  6.     /**
  7.      * 返回true,如果filePathCallback被调用;返回false,如果忽略处理
  8.      */
  9.     return true;
  10. }

  1. public void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
  2.              popupDialog();
  3.         String title = acceptType;
  4.         PickPhotoUtil.mFilePathCallback4 = filePathCallback;
  5.     }

点击弹窗取消按钮、点击打开相册取消操作或取消拍照,可能无法再次回调onShowFileChooser或openFileChooser方法,如果你没有在点击弹窗取消方法中或onActivityResult回调方法resultCode==RESULT_CANCELED处理,再次点击上传按钮,打印出log:

Attempted to finish an input event but the input event receiver has already been disposed

同时,点击没有效果

  1. /**
  2.      * 弹窗,启动拍照或打开相册
  3.      */
  4.     public void popupDialog() {
  5.         ActionSheetDialog actionSheetDialog= new ActionSheetDialog(activity).builder()
  6.                 .setCancelable(false)
  7.                 .setCanceledOnTouchOutside(false)
  8.                 .addSheetItem(“手机拍照”, ActionSheetDialog.SheetItemColor.Blue,
  9.                         new ActionSheetDialog.OnSheetItemClickListener() {
  10.                             @Override
  11.                             public void onClick(int which) {
  12.                                 goToTakePhoto();
  13.                             }
  14.                         })
  15.                 .addSheetItem(“手机相册”, ActionSheetDialog.SheetItemColor.Blue,
  16.                         new ActionSheetDialog.OnSheetItemClickListener() {
  17.                             @Override
  18.                             public void onClick(int which) {
  19.                                 goForPicFile();
  20.                             }
  21.                         });
  22.         actionSheetDialog.show();
  23.         /**
  24.          * 设置点击“取消”按钮监听,目的取消mFilePathCallback回调,可以重复调起弹窗
  25.          */
  26.         actionSheetDialog.setOnClickListener(new View.OnClickListener() {
  27.             @Override
  28.             public void onClick(View v) {
  29.                 cancelFilePathCallback();
  30.             }
  31.         });
  32.     }
  1. /**
  2.      * onActivityResult回调方法,当resultCode==RESULT_CANCELED,取消mFilePathCallback回调,可以* 重复调起弹窗
  3.      */
  4.         @Override
  5.     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  6.         super.onActivityResult(requestCode, resultCode, data);
  7.         switch (requestCode) {
  8.         /**
  9.          *打开系统自带文件管理器回调
  10.          */
  11.             case PickPhotoUtil.REQUEST_FILE_PICKER:
  12.                 pickPhotoResult(resultCode, data);
  13.                 break;
  14.         /**
  15.          *打开相册回调
  16.          */
  17.             case PickPhotoUtil.REQUEST_CODE_PICK_PHOTO:
  18.                 pickPhotoResult(resultCode, data);
  19.                 break;
  20.         /**
  21.          *拍照后回调
  22.          */
  23.             case PickPhotoUtil.REQUEST_CODE_TAKE_PHOTO:
  24.                 takePhotoResult(resultCode);
  25.                 break;
  26.             default:
  27.                 break;
  28.         }
  29.     }
  1. /**
  2.      *取消mFilePathCallback回调
  3.      */
  4.     private void cancelFilePathCallback() {
  5.         if (PickPhotoUtil.mFilePathCallback4 != null) {
  6.             PickPhotoUtil.mFilePathCallback4.onReceiveValue(null);
  7.             PickPhotoUtil.mFilePathCallback4 = null;
  8.         } else if (PickPhotoUtil.mFilePathCallback != null) {
  9.             PickPhotoUtil.mFilePathCallback.onReceiveValue(null);
  10.             PickPhotoUtil.mFilePathCallback = null;
  11.         }
  12.     }

在不期待回调mFilePathCallback的onReceiveValue方法时,调用cancelFilePathCallback(),解决点击上传按钮无法重复回调的问题。

ps:

Demo已上传GitHub,路径view\webview\UploadImgForH5Activity.java

X5内核的问题 - CSDN博客

mikel阅读(1672)

来源: X5内核的问题 – CSDN博客

常常被人问及微信中使用的X5内核的问题,其实我也不是很清楚,只知道它是基于Android 4.2的webkit,版本号是webkit 534。今天正好从X5团队拿到了一份问题汇总,梳理下发出来,给各位开发者以参考——不排除明天会删除这篇文章的可能。


1.Android WebView常见问题及解决方案汇总:

http://blog.csdn.net/t12x3456/article/details/13769731

2.请问各位碰到过X5浏览器内,局部滑动使用iscroll卡顿的问题么?

回答:是使用 iscroll.js 这个JS去滚动么?如果只是为了产生滚动,建议使用 overflow 属性来,目前有一种滚动优化在线上版本效率不是太好,通过JS去改变CSS的属性产生滚动

3.调用:-webkit-filter:
blur(10px);filter: blur(10px);
Android下 背景图没有blur掉,只是被单纯的放大而已

回答:-webkit-filter目前还不支持,可以先用图片替换的方式 后续版本会支持这个属性

4.shadowBlur是阴影效果吧,我们是想实现图片毛玻璃

回答: http://blog.csdn.net/jia20003/article/details/9195915 这个有用么

5.LBS相关,定位频繁失败

第一步,首先确认定位失败是个别站点原因还是所有站点定位都失败。如果是所有站点定位都不成功,很有可能是内核问题,转内核相关同学跟进调查定位逻辑是否有缺陷;如果是某个站点才会出现的问题,继续第二步排查,从站点源码着手。

第二步,找到站点请求定位的js代码段,检查获取定位信息函数的options字段,很有可能是 options 中 timeout 字段设置的超时太短导致,建议前端开发将该字段时间设置长一些(建议10s以上)或者不设置该字段。如果js没有设置 timeout 字段的情况下仍然定位不成功,则转内核同学调查内核流程。

ps:H5获取LBS信息的js接口

回答:

navigator.geolocation.getCurrentPosition(showPosition,showError,{  
    enableHighAccuracy:false,
    timeout:10*1000,
    maximumAge:0
});

navigator.geolocation.watchPosition(watchPosition,showError,options);  
  • showPosition:定位成功时回调;
  • showError:定位出错时回调;
  • options:可选的地理定位请求特征
  • enableHightAccuracy:可选,是否开启高精度模式,参数默认值为 false
  • timeout:可选,单位为ms,浏览器需要在该时间段内完成定位,否则定位失败,默认值为 – – infinity,无穷大。如果该值设置较小,会有很高的定位失败率。
  • maximumAge:可选,单位ms,重新计算位置的时间间隔。默认为0,即每次时时计算位置信息。

6.打开视频播放,后退视频仍然在播放

回答:部分机型,浏览页面时打开视频播放,点击返回,页面上的视频仍然在播放。解决办法是捕获后退事件,主动调用 onHideCustomView() 方法,并且在该方法里将 onShowCustomView 里关联的view解除关联

7.请问一下微信浏览器的cookie清理机制是怎么样的?

回答:X5内核是不会清除的。

8.打开WWW页面,缩放显示的问题

回答:
使用webview打开www页面,如果页面被放大显示,确定websettings有没有设置,webSettings.setUseWideViewPort(true),默认为false,www页面不会被缩放显示的。手机QQ浏览器默认为true,显示www页面更美观

9. 关于滚动时候动画的问题

回答:

  1. 页面滑动过程中动画不会被触发
  2. 页面滑动过程中动画会被停止

这个是X5内核为了做滚动优化而做的限制

10.关于connection type定义的问题

回答:X5执行的标准比较老,NetworkInfo_API

enum ConnectionType {  
UNKNOWN = 0,  
ETHERNET = 1,  
WIFI = 2,  
CELL_2G = 3,  
CELL_3G = 4,  
CELL_4G = 5,  
NONE = 6,  
};

11.出现网络正常,但是页面打不开的情况

回答:关于设置里面的云加速试下是否可以打开。

12.js阻塞和css阻塞的不同

回答:css是阻塞渲染过程,js阻塞解析过程!对于用户来说,没什么区别,都是空白的 js的执行时,如果js中有读写css的属性的代码,并且下载队列中有待加载的css,js执行会被阻塞掉。

13.cookie的4k限制

回答:浏览器端cookie的数量可能会超过4k,有http请求时,内核只取前4k的cookie数据!

14.Js Defer与把js放到html底部的区别

回答:js defer:先加载,ondownload 后执行。和js放到html底部类似。不同的就是html预扫描到会先加载

15.首屏显示后,为什么又会重排版

回答:浏览器的排版宽度受上层ui设置的webview宽度影响。如果webview没设置或者是设置成0,浏览器内核会用默认的排版宽度320px进行排版。此时若webview的宽度值被正常设置,计算出来的排版宽度不是320px(一般是360px),这样浏览器就要用360px宽度,对页面进行重排。

16.canvas的数量是不是有限制

回答:

  • 小于1G的内存, canvas的内存不能超过100M
  • 1G到2G的内存, canvas的内存不能超过300M
  • 大于2G的内存 canvas的内存不能超过500M
  • 为了防止内存占用过多,硬件加速的CANVAS最多支持5个

小于等于1G内存手机,由于内存没办法精确统计,当达到75M以上,CANVAS数量最多支持20个

上面说的canvas内存,仅仅是说canvas 长宽计算出来的内存,不包括canavs使用的图片等资源内存

17.x5浏览器CSS3有些不支持,同样的样式,在chrome里能起到效果,在X5就没用。并且js性能也差的多,微信还内置X5,用起来太不爽

回答:是否方便具体说下是什么样的性能问题和css样式缺陷影响到您?我们这边可以跟进查看下原因。

X5内核也在不断改进,您的反馈可以帮助我们进一步优化。

chrome在标准的支持和性能优化方面确实目前走在业界前面,不过android系统的碎片化,android系统webview更是碎片化严重。

android系统上的web开发可能也需要考虑到不同rom的兼容情况。X5内核致力于为开发者提供统一的web体验,并通过不断的优化,来向业界标准对齐。

目前我们也在做基于chromium内核的研究工作,后续在标准的支持和性能上会有进一步提高。

18.请问下x5中js调用android怎么实现?

回答:跟系统内核下一样,都借助 addjavainterface 实现

19.请问一下,android手机微信上用的x5支持webgl吗?

回答:android手机中微信上的X5都是支持webgl的,不过部分机型上还有兼容性问题,由于兼容性问题,webgl之前我们是采用软绘的方式支持,目前切换到了硬绘,但整体来说还是会有兼容性和性能问题,后续这块还会持续更新

20.如果在某个设备第一次打开应用的时候 没有网络,x5内核可以启动吗?单网络恢复后还要再次认证,还是 不用在认证?

回答:第一次打开应用是不会拉起X5的 在第二次打开之后 不管有无网络都可以拉起

21.如果我不安装浏览器,安装微信,x5能调用起来吗?

回答:不能,只能安装QQ浏览器才能调用

22.web audio api是否已经支持?

回答:暂时还不支持

23.什么时候支持html5的onunload和onbeforeunload事件

回答:现在是支持html5的 onunload 和 onbeforeunload 事件的

24.要用X5的内核,必须要用QQ浏览器么?

回答:目前 SDK 版本是这样的,我们正在预研的版本,手机如果安装了微信或手Q ,其它 app 可以不依赖 QQ浏览器 而共享 x5 内核。

25.关于svg问题。

回答:关于SVG的问题:

关于svg模糊的问题,有2种情况:

  1. 直接访问一个.svg url的页面或者object,embed加入的svg, 出现模糊的问题
    此问题已解决, qq浏览器5.8上已经修复, tbs下个版本也会修复
  2. svg用作background-image, 模糊
    这个问题是我们目前渲染机制导致不能兼容非标准写法: 在用svg作为background-image的时候, 需要指定background-size, 不然会模糊

关于svg支持情况:
在5.3之前的qq浏览器不支持svg, 我们会在后台把svg转成一张jpeg图片, 供浏览器显示,5.4及以上版本支持svg,如果遇到被转成图片的问题,需要升级浏览器版本。

26.请问flexbox近期会支持吗?

回答:flexbox我们正在做开发支持

27.x5内核 目前是独立运行的 还是需要安装QQ浏览器?

回答:sdk是需要QQ浏览器的,微信手Q里的是不需要的

28.现在X5内核怎么调试?在微信或者手q或者qq浏览器中调试页面

回答:现在的微信手Q里面的X5还无法通过inspector调试 后期我们会把带有inspector调试的版本挂在开发者后台下载区 敬请期待

29.请问x5支持webgl的所有接口吗?能在所有版本的android机(4.0+)和ios机(5.0+)上运行webgl吗?

回答:webgl目前是支持的,不过部分机型上还有兼容性问题

30.X5上支持哪些扩展?支持多少个纹理单元?

回答:X5上只能支持:

WEBGL_lose_context  
EXT_texture_filter_anisotropic  
OES_texture_float  
OES_standard_derivatives  
OES_vertex_array_object  
WEBGL_debug_renderer_info  
WEBGL_debug_shaders  
WEBKIT_WEBGL_compressed_texture_s3tc  

而且这些是必须手机GPU有对应的扩展指令才行的。

关于纹理单元。我们这边没有限制

上面的扩展,也都是基本每一个对应opengl的一个扩展

支持多少纹理单元,也是从opengl查询得到的。

主要应该是看手机GPU支持到啥程度,我们是做个对接。

31.x5的文件分片功能解决了吗,blob
= file.webkitSlice(start, stop)
,这样分片出来的blob用不了啊。

回答:分片问题这边已经定位处理,浏览器会在5.8版本修复

32.手机qq浏览器是否有调试工具呢?

回答:有的,Inspector。

33.请问现在微信调用的手机QQ浏览器支持websocket 吗?

回答:支持websocket ,暂不支持wss,不支持webrtc

34.有计划支持webRTC吗?

回答:这个我们后续会评估的。

35.我是HTML5游戏开发者,制作中的H5游戏需要有音乐音效。但是我在android机器上使用QQ浏览器出现了如下问题:

  1. 循环播放BGM时,如果同时播放音效,BGM会被强行暂停
  2. 多个音效同时播放时,会出现明显的无法忍受的延迟和播放失败

这个问题在同一台机器的微信上同样存在,但是同一台机器的chrome没有这个问题。

我能想到的最合理的解释是:X5内核同一时间只能播放一个音频通道。

希望官方能解答我的疑惑和遇到的问题,谢谢。

PS:

  1. 使用的H5音频库 : SoundJS
  2. 使用的音频格式 : mp3
  3. 使用的Android机型:三星 Note3

回答:播放音效需要获取声音输出焦点,目前只支持同时播放一个音效 您提的需求我们会讨论评估后期是否能有方案现

36.播放音效时,为啥会把我后台的BGM播放给暂停掉?

回答:播放声音时当前音频需要获取 audiofocus ,系统在audiofocus丢失时会通知其它音频播放软件,这个暂停应该是播放软件自身的行为,我们本身并没有暂停后台音频,只是向系统申请了 audiofocus

37.300ms延迟是指什么?click 和 touch ?

回答:touch 点击之后,到 click 事件被触发,click 事件有延迟,touch不存在,用 touchstart 事件替代 click 事件就OK

38.现在微信内置的浏览器能支持flexbox么,现在有没有什么好办法能够替代呢?做好的网页一放到微信上,大量的flex的页面

回答:你好flexbox正在开发支持,flexbox在android4.1到4.3系统内核上,也有类似问题,页面是需要兼容的

39.有没有什么x5内核的论坛或者wiki之类的?可以参考下。

回答:http://bbs.browser.qq.com/ http://x5.tencent.com/ 这两个论坛可以关注下

另外有我们的公众账号 有问题可以随时交流

40.X5公众号叫什么?

回答:腾讯X5浏览服务

41.手q上面的内核应该也是x5的吧?这样直接在手q上面的效果和在微信里面应该是一样的?

回答:是的手Q微信内的webview都是X5。

42.x5不支持font-face吗?

回答:支持

43.x5 支持flex吗?有兼容性文档吗?

回答:不支持.http://1.h5support.sinaapp.com/incoming/cow.html

这个页面是参考caniuse的测试用例得到的测试结果,5.7是对应qq浏览器5.7版本,可以对比下和系统浏览器4.1~.4.4的支持度。有需要的同学可以先参考下,相关的文档建设我们也在逐步完……

44.iscroll+lazyload在x5浏览器里面卡顿很严重,有人碰到过类似问题么,小米手机 列表内元素200个左右。

回答:iscroll本身对内核要求比较高,较新的blink版本支持才比较好。可以对比测试下android 4.x 的系统浏览器看看。建议还是在前端做些优化,避免较长的元素,并减少动画效果。http://www.cnblogs.com/vbluebirdv/archive/2012/11/18/2776300.html 可以参考网上一些iscroll调优的文章

45.微信里面缓存问题,在安卓下和 ios下,刷新机制是不是不一样?

回答:ios因为有刷新功能,点击之后请求到的都是最新的文件,安卓下,不管怎样请求都不会更新文件

46. <meta
name="x5-orientation" content="portrait"/>
现在微信里面没有假如这个啊?为什么IOS QQ浏览器不识别这个?

回答:ios内核不是X5, ios 浏览器后续版本也会支持http://open.mb.qq.com/doc?id=1201
目前ios 浏览器仅支持 x5-page-mode

47.现在白鹭游戏引擎是不是内置在x5里面了?

回答:内置了白鹭引擎runtime

48.x5内核不支持 canvas 的 background 属性吗?

<!doctype html>  
<html xmlns="http://www.w3.org/1999/xhtml">  
<head>  
<meta charset="utf-8">  
<title>canvas test</title>  
    <!--<meta HTTP-EQUIV="pragma" CONTENT="no-cache">-->
    <!--<meta HTTP-EQUIV="Cache-Control" CONTENT="no-store, must-revalidate">-->
</head>

<body>  
<div>  
    <canvas id="testCanvas" width="500" height="300" style="background: #00FF00;"></canvas>

<!--<canvas id="testCanvas" width="1136" height="640" style="background:#000"></canvas> -->  
</div>  
</body>  
</html>  

背景颜色显示不出来

回答:这个是做了优化,当canvas下盖了背景,就没有去绘制背景图片,当初是因为一些手机的GPu在绘制这块,如果存在这种情况绘制非常慢

49.qq浏览器有没有准备支持asm.js的计划?

回答:我们也正在筹备相关工作,会对市面上各种游戏引擎进行全面评测并设计合理的架构方案整合。预计下半年可以推出,敬请期待

50.X5不支持canvas.toDataUrl()image/jpeg参数,还是转成了默认的png格式请问有什么替换方法吗?

回答:当前确实还不支持…我们在修复

51.问一下 ios版的微信里面是用的系统自带的UIWebView还是用的qq浏览器的内核?

回答:ios版微信里面用的是系统自带的

本文转自:http://www.qianduan.net/qqliu-lan-qi-x5nei-he-wen-ti-hui-zong/

android webview中上传控件点击无效的解决办法 - CSDN博客

mikel阅读(1105)

来源: android webview中上传控件点击无效的解决办法 – CSDN博客

一、介绍

当我们在使用webview控件打开一个web网页时,如果we页面中带有<input type=”file” …>的控件,在webview中能正常显示这个上传控件,但是你会发现无论你如何点击都无效果,这个是很让人恼火的,一时也不知道如何下手去改,这里阿汤哥会告诉你如何解决该问题,如果我的解决办法能帮到你,请给我点掌声,并给你自己点掌声。

二、解决办法

第一步:重写WebChromeClient

webview的坑比较多,在这个上传文件的坑中遇到一个问题:

Android 5.0+ 重写onShowFileChooser生效;

Android 4.4   重写openFileChooser没有生效;

Android 4.4- 重写openFileChooser生效;

  1. import android.net.Uri;
  2. import android.webkit.ValueCallback;
  3. import android.webkit.WebChromeClient;
  4. import android.webkit.WebView;
  5. /**
  6.  * Created by tangbin on 16/5/12.
  7.  */
  8. public class MyWebChromeClient extends WebChromeClient {
  9.     private WebCall webCall;
  10.     public void setWebCall(WebCall webCall) {
  11.         this.webCall = webCall;
  12.     }
  13.     // For Android 3.0+
  14.     public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
  15.         if (webCall != null)
  16.             webCall.fileChose(uploadMsg);
  17.     }
  18.     // For Android < 3.0
  19.     public void openFileChooser(ValueCallback<Uri> uploadMsg) {
  20.         openFileChooser(uploadMsg, “”);
  21.     }
  22.     // For Android > 4.1.1
  23.     public void openFileChooser(ValueCallback<Uri> uploadMsg,
  24.             String acceptType, String capture) {
  25.         openFileChooser(uploadMsg, acceptType);
  26.     }
  27.     // For Android > 5.0
  28.     @Override
  29.     public boolean onShowFileChooser(WebView webView,
  30.             ValueCallback<Uri[]> filePathCallback,
  31.             FileChooserParams fileChooserParams) {
  32.         if (webCall != null)
  33.             webCall.fileChose5(filePathCallback);
  34.         return super.onShowFileChooser(webView, filePathCallback,
  35.                 fileChooserParams);
  36.     }
  37.     public interface WebCall {
  38.         void fileChose(ValueCallback<Uri> uploadMsg);
  39.         void fileChose5(ValueCallback<Uri[]> uploadMsg);
  40.     }
  41. }

 

第二步:监听ValueCallback

  1. WebSettings webSettings = mWebView.getSettings();
  2.         // 设置WebView属性,能够执行JavaScript脚本
  3.         webSettings.setJavaScriptEnabled(true);
  4.         // 设置可以访问文件
  5.         webSettings.setAllowFileAccess(true);
  6.         mWebView.setWebViewClient(new webViewClient());
  1. public final static int FILECHOOSER_RESULTCODE = 1;
  2. public final static int FILECHOOSER_RESULTCODE_FOR_ANDROID_5 = 2;
  3. public ValueCallback<Uri> mUploadMessage;
  4. public ValueCallback<Uri[]> mUploadMessageForAndroid5;

 

  1. @Override
  2. public void fileChose(ValueCallback<Uri> uploadMsg) {
  3.     openFileChooserImpl(uploadMsg);
  4. }
  5. @Override
  6. public void fileChose5(ValueCallback<Uri[]> uploadMsg) {
  7.     openFileChooserImplForAndroid5(uploadMsg);
  8. }
  9. private void openFileChooserImpl(ValueCallback<Uri> uploadMsg) {
  10.     mUploadMessage = uploadMsg;
  11.     Intent i = new Intent(Intent.ACTION_GET_CONTENT);
  12.     i.addCategory(Intent.CATEGORY_OPENABLE);
  13.     i.setType(“image/*”);
  14.     startActivityForResult(Intent.createChooser(i, “File Chooser”),
  15.             FILECHOOSER_RESULTCODE);
  16. }
  17. private void openFileChooserImplForAndroid5(ValueCallback<Uri[]> uploadMsg) {
  18.     mUploadMessageForAndroid5 = uploadMsg;
  19.     Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
  20.     contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
  21.     contentSelectionIntent.setType(“image/*”);
  22.     Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
  23.     chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
  24.     chooserIntent.putExtra(Intent.EXTRA_TITLE, “Image Chooser”);
  25.     startActivityForResult(chooserIntent,
  26.             FILECHOOSER_RESULTCODE_FOR_ANDROID_5);
  27. }

 

第三步:创建onActivityResult

  1. @Override
  2. protected void onActivityResult(int requestCode, int resultCode,
  3.         Intent intent) {
  4.     if (requestCode == FILECHOOSER_RESULTCODE) {
  5.         if (null == mUploadMessage)
  6.             return;
  7.         Uri result = intent == null || resultCode != RESULT_OK ? null
  8.                 : intent.getData();
  9.         mUploadMessage.onReceiveValue(result);
  10.         mUploadMessage = null;
  11.     } else if (requestCode == FILECHOOSER_RESULTCODE_FOR_ANDROID_5) {
  12.         if (null == mUploadMessageForAndroid5)
  13.             return;
  14.         Uri result = (intent == null || resultCode != RESULT_OK) ? null
  15.                 : intent.getData();
  16.         if (result != null) {
  17.             mUploadMessageForAndroid5.onReceiveValue(new Uri[] { result });
  18.         } else {
  19.             mUploadMessageForAndroid5.onReceiveValue(new Uri[] {});
  20.         }
  21.         mUploadMessageForAndroid5 = null;
  22.     }
  23. }

搞定,最后就可以如愿以偿的完成文件上传了功能了