Skip to content

钩子专属

  • 首页
  • 安卓技术
  • 安卓教程

All posts by oshook

  • 首页   /  
  • 作者: oshook
  • ( 页面7 )
安卓技术 6 月 21,2023

android启动页停留时间

现在绝大数应用都有启动页,多数形式是放一张图。
最好是网络请求过来的,下面只说本地默认图
关键是停留时间的控制
特别说明:几种时间控制有误差(系统时间的受内存的影响)
1、Timer 有时间误差
2、new Handler() 有时间误差
3、Rxjava 目测没有时间误差

new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {

        }
    },2000);  //延迟2s后执行

推荐使用方式

private int defaultSecond = 2;  //显示默认图时间2s

@SuppressLint("HandlerLeak")
private final Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        defaultSecond--;
        if (defaultSecond == 0) {
            //倒计时完跳进应用
            isToMain();

        } else {
            handler.sendEmptyMessageDelayed(1, 1000);
        }
    }
};

调用时机:

@Override
protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_start_up);

    mContext = this;

    //倒计2s
    handler.sendEmptyMessageDelayed(1, 1000);


}  

用RxJava方式

private void startCountDown() {
    int defaultCountDownTime = 1500;
    //加上onError处理,防止报错没捕捉而导致闪退
    Observable.timer(defaultCountDownTime, TimeUnit.MILLISECONDS)
            .compose(this.bindToLifecycle())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Consumer() {
                @Override
                public void accept(Long aLong) throws Exception {
                    //倒计时完做操作,有广告图交给广告图
                    //if (flAd.getVisibility() != View.VISIBLE) {
                        //toNext();
                    //}
                }

            }, new Consumer() {
                @Override
                public void accept(Throwable throwable) throws Exception {
                    //toNext();
                }

            });
}
作者 oshook
安卓技术 6 月 21,2023

android开发权限询问

现在基于信息安全问题,特别是版本是23以上权限越严格。
特别是拍照,读,写权限
一般权限允许过,下次就不用询问了的,所以很多应用都喜欢在首页或者启动页直接询问,不允许的就用不了
1、下面给出封装好的类,至于什么时候调看项目需要

public class EasyPermissions {

private static final String TAG = "EasyPermissions";

public interface PermissionCallbacks extends
        ActivityCompat.OnRequestPermissionsResultCallback {

    void onPermissionsGranted(List perms);

    void onPermissionsDenied(List perms);

}

/**
 * Check if the calling context has a set of permissions.
 *
 * @param context the calling context.
 * @param perms   one ore more permissions, such as {@code android.Manifest.permission.CAMERA}.
 * @return true if all permissions are already granted, false if at least one permission
 * is not yet granted.
 */
public static boolean hasPermissions(Context context, String... perms) {
    for (String perm : perms) {
        boolean hasPerm = (ContextCompat.checkSelfPermission(context, perm) == PackageManager.PERMISSION_GRANTED);
        if (!hasPerm) {
            return false;
        }
    }

    return true;
}

/**
 * Request a set of permissions, showing rationale if the system requests it.
 *
 * @param object      Activity or Fragment requesting permissions. Should implement
 *                    {@link ActivityCompat.OnRequestPermissionsResultCallback}
 *                    or
 *                    {@link android.support.v13.app.FragmentCompat.OnRequestPermissionsResultCallback}
 * @param rationale   a message explaining why the application needs this set of permissions, will
 *                    be displayed if the user rejects the request the first time.
 * @param requestCode request code to track this request, must be ());
                        }
                    }
                }).create();
        dialog.show();
    } else {
        executePermissionsRequest(object, perms, requestCode);
    }
}

/**
 * Handle the result of a permission request, should be called from the calling Activity's
 * {@link ActivityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult(int, String[], int[])}
 * method.
 * 

* If any permissions were granted or denied, the Activity will receive the appropriate * callbacks through {@link PermissionCallbacks} and methods annotated with * {@link AfterPermissionGranted} will be run if appropriate. * * @param requestCode requestCode argument to permission result callback. * @param permissions permissions argument to permission result callback. * @param grantResults grantResults argument to permission result callback. * @param object the calling Activity or Fragment. * @throws IllegalArgumentException if the calling Activity does not implement * {@link PermissionCallbacks}. */ public static void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults, Object object) { checkCallingObjectSuitability(object); PermissionCallbacks callbacks = (PermissionCallbacks) object; // Make a collection of granted and denied permissions from the request. ArrayList granted = new ArrayList(); ArrayList denied = new ArrayList(); for (int i = 0; i 0) { throw new RuntimeException("Cannot execute non-void method " + method.getName()); } try { // Make method accessible if private if (!method.isAccessible()) { method.setAccessible(true); } method.invoke(object); } catch (IllegalAccessException e) { ILogger.e(TAG, "runDefaultMethod:IllegalAccessException", e); } catch (InvocationTargetException e) { ILogger.e(TAG, "runDefaultMethod:InvocationTargetException", e); } } } } } private static void checkCallingObjectSuitability(Object object) { // Make sure Object is an Activity or Fragment if (!((object instanceof Fragment) || (object instanceof Activity))) { throw new IllegalArgumentException("Caller must be an Activity or a Fragment."); } // Make sure Object implements callbacks if (!(object instanceof PermissionCallbacks)) { throw new IllegalArgumentException("Caller must implement PermissionCallbacks."); } }}

注:这个类不是我写的,分享给大家
2、调用示例(activity或fragment):(方法调用的时机随你)

private void requestGalleryPermission() {
    //权限参数可以添加很多,不固定参数的
    if (EasyPermissions.hasPermissions(this, Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
        getPhotos();//如果具有权限做的操作
    } else {
        // Ask for one permission
        EasyPermissions.requestPermissions(this, getString(R.string.permissions_tips_gallery),
                GalleryFinal.PERMISSIONS_CODE_GALLERY, Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE);
    }
}

3、拒绝和允许做的操作(activity或fragment):

@Override
public void onPermissionsGranted(List list) {
    //允许
    getPhotos();
}

@Override
public void onPermissionsDenied(List list) {
    //拒绝
    mTvEmptyView.setText(R.string.permissions_denied_tips);
    //mIvTakePhoto.setVisibility(View.GONE);
}
作者 oshook
安卓技术 6 月 21,2023

android自定义渐变色圆角View

先看效果图:

效果图.png

分析这是线性渐变色,圆弧半径刚好是高度的一半,文字居中处理,大小,颜色可调
首先开始自定义控件的基本步骤
1、style自定义属性



    
    
    
    
    
    
    
    
    

注:原本我是想还有点击效果的
2、继承view核心代码

public class MyGradientRoundButton extends View {
private int colorStart;
private int colorEnd;
private int colorPressStart;
private int colorPressEnd;
private int colorS;
private int colorE;
private String text;
private int textColor;
private float textSize;
private float round;
private boolean clickEffect;
private RectF mBackGroundRect;
private LinearGradient backGradient;

//默认画笔
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint mPaintText = new Paint();

public MyGradientRoundButton(Context context, AttributeSet attrs) {
    super(context, attrs);

    //获取自定义属性
    if (attrs != null) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyGradientRoundButton);
        colorStart = typedArray.getColor(R.styleable.MyGradientRoundButton_colorStart, getResources().getColor(R.color.btn_color_start));
        colorEnd = typedArray.getColor(R.styleable.MyGradientRoundButton_colorEnd, getResources().getColor(R.color.btn_color_end));
        round = typedArray.getDimension(R.styleable.MyGradientRoundButton_round, Utils.dip2px(context, 10));
        clickEffect = typedArray.getBoolean(R.styleable.MyGradientRoundButton_clickEffect, false);
        colorPressStart = typedArray.getColor(R.styleable.MyGradientRoundButton_colorPressStart, getResources().getColor(R.color.btn_color_PStart));
        colorPressEnd = typedArray.getColor(R.styleable.MyGradientRoundButton_colorPressEnd, getResources().getColor(R.color.btn_color_PEnd));
        text = typedArray.getString(R.styleable.MyGradientRoundButton_btnText);
        textColor = typedArray.getColor(R.styleable.MyGradientRoundButton_btnTextColor, getResources().getColor(R.color.black));
        textSize = typedArray.getDimension(R.styleable.MyGradientRoundButton_btnTextSize, 16);
        colorS = colorStart;
        colorE = colorEnd;
        typedArray.recycle();
    }
    //必须加,否则onTouchEvent只响应ACTION_DOWN
    setClickable(true);
    //设置抗锯齿
    mPaint.setAntiAlias(true);
    //设置防抖动
    mPaint.setDither(true);
    mPaint.setStyle(Paint.Style.FILL);

    mPaintText.setAntiAlias(true);
    mPaintText.setDither(true);
}


@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    mBackGroundRect = new RectF(0, 0, w, h);
    backGradient = new LinearGradient(0, 0, w, 0, new int[]{colorS, colorE}, null, Shader.TileMode.CLAMP);
}


@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    mPaint.setShader(backGradient);
    //绘制背景 圆角矩形
    if (mBackGroundRect != null) {
        canvas.drawRoundRect(mBackGroundRect, round, round, mPaint);
    }
    //绘制文字
    mPaintText.setTextSize(textSize);
    mPaintText.setColor(textColor);
    mPaintText.setTextAlign(Paint.Align.CENTER);
    Paint.FontMetricsInt fontMetrics = mPaintText.getFontMetricsInt();
    float baseline = mBackGroundRect.top + (mBackGroundRect.bottom - mBackGroundRect.top - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top;
    canvas.drawText(text, canvas.getWidth() / 2, baseline, mPaintText);

}

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (clickEffect) {
        //刷新
        invalidate();
        //判断点击操作
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                colorS = colorPressStart;
                colorE = colorPressEnd;
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                colorS = colorStart;
                colorE = colorEnd;
                break;
            case MotionEvent.ACTION_CANCEL:
                break;
        }
    }

    return super.onTouchEvent(event);
}


public void setText(String text) {
    if (!TextUtils.isEmpty(text)) {
        this.text = text;
        invalidate();
    }
}}

关键代码讲解:
onDraw():绘制图形过程,canvas.drawXXX()就是在绘制一层层图层,所以drawRoundRect要在drawText之前
LinearGradient:线性渐变色
RectF:正方形,配合drawRoundRect就可以画出带圆角的图形
onSizeChanged():发生在onDraw()之前,一般可以做些不变的参数设定,调用onDraw(),它也不调用的
invalidate():每调一次,也就调用了一次onDraw()方法,这样就可以做点击效果了

3、引用列子


这个自定义控件有个缺陷宽高不能用wrap_content属性。

作者 oshook
安卓技术 6 月 21,2023

android开发修改状态栏背景色和图标颜色

修改状态栏背景色和图标颜色

默认是黑底白字的,现在要改为白底黑字的
先看下效果图:

Screenshot_2018-01-03-19-20-27-797_com.yiban1314..png

1、状态栏背景是白色: 在style中设置

@color/white

2、写修改状态栏图标的颜色(暂时只知道黑色和白色)

public class StatusBarUtil {
/**
 * 修改状态栏为全透明
 * @param activity
 */
@TargetApi(19)
public static void transparencyBar(Activity activity){
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        Window window = activity.getWindow();
        window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
        window.setStatusBarColor(Color.TRANSPARENT);

    } else
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        Window window =activity.getWindow();
        window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
                WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
    }



}

/**
 * 修改状态栏颜色,支持4.4以上版本
 * @param activity
 * @param colorId
 */
public static void setStatusBarColor(Activity activity,int colorId) {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        Window window = activity.getWindow();
         //window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
        //window.getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
        window.setStatusBarColor(activity.getResources().getColor(colorId));

    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        //使用SystemBarTint库使4.4版本状态栏变色,需要先将状态栏设置为透明
        transparencyBar(activity);
        SystemBarTintManager tintManager = new SystemBarTintManager(activity);
        tintManager.setStatusBarTintEnabled(true);
        tintManager.setStatusBarTintResource(colorId);
    }
}


/**
 *状态栏亮色模式,设置状态栏黑色文字、图标,
 * 适配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android
 * @param activity
 * @return 1:MIUUI 2:Flyme 3:android6.0
 */
public static int statusBarLightMode(Activity activity){
    int result=0;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        if(MIUISetStatusBarLightMode(activity, true)){
            result=1;
        }else if(FlymeSetStatusBarLightMode(activity.getWindow(), true)){
            result=2;
        }else  {
            activity.getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
            result=3;
        }
    }
    return result;
}


/**
 * 设置状态栏图标为深色和魅族特定的文字风格
 * 可以用来判断是否为Flyme用户
 * @param window 需要设置的窗口
 * @param dark 是否把状态栏文字及图标颜色设置为深色
 * @return  boolean 成功执行返回true
 *
 */
public static boolean FlymeSetStatusBarLightMode(Window window, boolean dark) {
    boolean result = false;
    if (window != null) {
        try {
            WindowManager.LayoutParams lp = window.getAttributes();
            Field darkFlag = WindowManager.LayoutParams.class
                    .getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
            Field meizuFlags = WindowManager.LayoutParams.class
                    .getDeclaredField("meizuFlags");
            darkFlag.setAccessible(true);
            meizuFlags.setAccessible(true);
            int bit = darkFlag.getInt(null);
            int value = meizuFlags.getInt(lp);
            if (dark) {
                value |= bit;
            } else {
                value &= ~bit;
            }
            meizuFlags.setInt(lp, value);
            window.setAttributes(lp);
            result = true;
        } catch (Exception e) {

        }
    }
    return result;
}

/**
 * 需要MIUIV6以上
 * @param activity
 * @param dark 是否把状态栏文字及图标颜色设置为深色
 * @return  boolean 成功执行返回true
 *
 */
public static boolean MIUISetStatusBarLightMode(Activity activity, boolean dark) {
    boolean result = false;
    Window window=activity.getWindow();
    if (window != null) {
        Class clazz = window.getClass();
        try {
            int darkModeFlag = 0;
            Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
            Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
            darkModeFlag = field.getInt(layoutParams);
            Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
            if(dark){
                extraFlagField.invoke(window,darkModeFlag,darkModeFlag);//状态栏透明且黑色字体
            }else{
                extraFlagField.invoke(window, 0, darkModeFlag);//清除黑色字体
            }
            result=true;

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                //开发版 7.7.13 及以后版本采用了系统API,旧方法无效但不会报错,所以两个方式都要加上
                if(dark){
                    activity.getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN| View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
                }else {
                    activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
                }
            }
        }catch (Exception e){

        }
    }
    return result;
}}

上面代码是https://www.jianshu.com/p/7f5a9969be53上找到,具体可以去看看
3、具体引用列子
在BaseActivity中

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ActivityUtils.add(this, getClass());

    mContext = this;
    StatusBarUtil.statusBarLightMode(this);
}

4、正常状态栏已经改变
状态栏是改变了,但你会看到整个activity布局都会上移充满整个屏幕
解决方法1:在style中的AppTheme添加

true

如果添加上面代码布局下移了且不会影响到其他的东西。那就不用往下看了

android:fitsSystemWindows很坑,很多弹框的样式都有问题

解决方法2:自己为每个布局添加paddingTop
LibUtils:

/**
 * 获取状态栏高度
 * @return
 */
public static int getStatusBarHeight(Context context) {
    int result = 0;
    int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId > 0) {
        result = context.getResources().getDimensionPixelSize(resourceId);
    }
    return result;
}

//设置布局距离状态栏高度
public static void setLayoutPadding(Activity activity, View contentLayout) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        contentLayout
                .setPadding(contentLayout.getPaddingLeft(), getStatusBarHeight(activity) + contentLayout.getPaddingTop(),
                        contentLayout.getPaddingRight(), contentLayout.getPaddingBottom());
    }

}

引用地方:

protected void onCreate(@NonNull Bundle savedInstanceState, int resId, int titleId) {
    super.onCreate(savedInstanceState);
    
    
    mContext = this;
    
    setContentView(R.layout.activity_base);
    StatusBarUtil.statusBarLightMode(this);
    LibUtils.setLayoutPadding(this,((ViewGroup)findViewById(android.R.id.content)).getChildAt(0));}

注:LibUtils.setLayoutPadding调用要做setContentView后面,android.R.id.content是获取每个布局的根布局,不理解自行百度

还要考虑android版本的问题,一般5.0下的系统还是用默认的
有问题请留言

作者 oshook
安卓技术 6 月 21,2023

利用泛型判断List是否有值

利用泛型判断List是否有值

之前写判断list总是要写list!=null && list.size()>0;
项目一大,就会看见有很多这样的判断,
有点low
下面我们来建一个工具类:

 /**
 * 判断ArrayList是否为空
 * @param arrayList
 * @return
 */
public static boolean isArrNotEmpty(ArrayList arrayList){
    return  arrayList != null && arrayList.size() > 0;
}

/**
 * 判断list是否为空
 * @param list
 * @return
 */
public static boolean isListNotEmpty(List list){
    return  list != null && list.size() > 0;
}
作者 oshook
安卓技术 6 月 21,2023

Eclipse打开报错“java was started but returned exit code=13”

先看报错信息:

图是截取网上的,我的错误图改正后就不打算复现了
看红色圈它要求的是jdk1.6。只能用jdk1.6。我电脑装的是jdk1.9
重下jdk1.6默认安装就解决问题了
还有问题请加群:142739277

作者 oshook
安卓技术 6 月 21,2023

Android开发高效去重

利用HashSet高效去重

开发中经常用到去重操作,一般第一时间都是想到for循环加判断去重,后来发现这执行效率是最低的而且代码很冗余

下面介绍用HashSet来做去重

HashSet:键值对唯一,已存在的后面的不能添加且返回false

private Set setSum = new HashSet();

private ArrayList rMList(ArrayList al) {

    ArrayList newList = new ArrayList();
    if(al != null){
        for (Iterator iter = al.iterator(); iter.hasNext(); ) {
            Resume element = (Resume) iter.next();
            if (setSum.add(element.getId()))
                newList.add(element);
        }
    }

    return newList;

}

代码讲解:上面并没有用setSum里面的数据,只是用它来筛数据,筛完之后把符合的数据返回

作者 oshook
安卓技术 6 月 21,2023

Android开发动态二维数组赋值

动态二维数组赋值

一开始是不是觉得很简单的事情,但是你是不是来百度了,是不是。。
这也没什么的,程序员天天在解决bug,哪有时间去学习基本知识

需求:根据json解析过来的数据,赋值给二维数据

private String[][] citys;  
citys = new String[jsonArray.length()][];  
for (int i = 0; i 

原理:二维数组是由一维数组组成的

上面代码讲解:
1.脑海先有二维数组的形式,比如:int[][] a = {{1,2,3},{4,5}}
2.动态二维数组也需要先知道行数的(new String[jsonArray.length()][]),jsonArray.length()就是行数,是接口返回的也算是动态了。citys[i]是每一行的数据

csdn也有上面的代码,但没讲解,不过都是我写的
还有问题请加群:142739277

作者 oshook
安卓技术 6 月 21,2023

Android开发galleryfinal裁剪png图片问题

galleryfinal裁剪png图片问题##

galleryfinal是第三方开源框架,相册浏览,裁剪,相机,多图选择功能,很好用的一个框架。
之前没留意感觉一直都正常。后来发现总有些图片在裁剪处有问题,一开始是以为图片大小的问题,测试后又不是。后来看log以及观察图片格式后是png图片格式都有问题。

错误log:

解决方法有三个,依次以成功率高到低

1.cropImageActivity修改###

在cn.finalteam.galleryfinal.widget.crop.cropImageActivity类中的saveOutput方法有这样的一段代码,会判断图片后缀是png还是jpg,如果是png,format就会是Bitmap.CompressFormat.PNG,然后就无法把数据写进去那个crop文件,导致文件大小为0,具体原因要看源代码.我把if判断去掉,无论png还是jpg,format都是JPG,然后就没问题了。

2.png图片转jpg图片###

思路把png图片转为jpg图片再上传,上传完后再帮用户删除jpg图片。幸好裁剪的一张图片,中间操作时间几乎没什么影响,多了就不好说了。

转化过程png–>bitmap–>jpg

    String ext = FilenameUtils.getExtension(photoPath);
    File pngToJpgFile = null;  
    String pngToJpgPath = null;  
    if(ext.equals("png")){  
        Bitmap bitmap = BitmapFactory.decodeFile(photoPath);  
        if(bitmap != null){  
            Log.d("fileBitmap",bitmap+"");  
        }  
        String[] strings = photoPath.split("\.");  
        if(strings.length>0 && bitmap != null){  

            //存放在本地jpg图片  
            pngToJpgPath = strings[0] +"_png"+".jpg";  
            pngToJpgFile = new File(pngToJpgPath);  
            FileOutputStream fOut = null;  
            try {  
                fOut = new FileOutputStream(pngToJpgFile);  
            } catch (FileNotFoundException e) {  
                e.printStackTrace();  
            }  
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fOut);  
            try {  
                fOut.flush();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
            try {  
                fOut.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
            if(pngToJpgFile.exists()){  
                photoPath = pngToJpgPath;  
                MyApplication.setPngToJpgPath(photoPath);  
            }  
        }  

    } 

上面有可能有些值为空,最好加多点判空处理,免得引起其他问题,setPngToJpgPath()方法是存储多生成的jpg图片路径。记得还需要在编辑完去删除喔,这里没给出代码

3.升级裁剪功能依赖

在budle文件中添加com.android.support:exifinterface:25.1.0
注:这我没尝试成功,但有人成功过,可能galleryfinal框架不一样,但都可以试,万一成功了呢

作者 oshook
安卓技术 6 月 21,2023

Android的webView加载双向认证的https网址

WebView加载自签名认证的https网址

加载https网址安全性提高很多,但是官方认证签名是需要钱的。所以自签名认证就出来了,WebView要添加证书认证后也是能显示的

双向认证签名

必须证书:cer,p12
存放路径:main下面的assets文件夹下,没有的自己新建

1.重写WebViewClient

public class SslPinningWebViewClient extends WebViewClient {

//private LoadedListener listener;
private SSLContext sslContext;
private int url_type;

public SslPinningWebViewClient() throws IOException {
    //this.listener = listener;

    // 添加cer证书
    List certificates = new ArrayList();

    List certs_data = NetConfig.getCertificatesData();

    // 将字节数组转为数组输入流

    if (certs_data != null && !certs_data.isEmpty()) {

        for (byte[] bytes : certs_data) {

            certificates.add(new ByteArrayInputStream(bytes));

        }

    }

    prepareSslPinning(certificates);
}

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    LogUtils.i("url_type", url_type + "a");
    view.loadUrl(url);
    if (url_type == 2) {
        return false;
    }
    return true;
}

public SslPinningWebViewClient(int url_type) throws IOException {
    //this.listener = listener;
    this.url_type = url_type;
    // 添加cer证书
    List certificates = new ArrayList();

    List certs_data = NetConfig.getCertificatesData();

    // 将字节数组转为数组输入流

    if (certs_data != null && !certs_data.isEmpty()) {

        for (byte[] bytes : certs_data) {

            certificates.add(new ByteArrayInputStream(bytes));

        }

    }

    prepareSslPinning(certificates);
}

@Override
public WebResourceResponse shouldInterceptRequest(final WebView view, String url) {
    LogUtils.i("shouldInterceptRequest", "shouldInterceptRequest1");
    return processRequest(url);

}

private WebResourceResponse processRequest(String webUrl) {
    LogUtils.i("SSL_PINNING_WEBVIEWS", "GET: " + webUrl.toString());

    try {
        // Setup connection
        URL url = new URL(webUrl);
        HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();

        // Set SSL Socket Factory for this request
        urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
        //很重要,验证证书
        urlConnection.setHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });

        // Get content, contentType and encoding
        InputStream is = urlConnection.getInputStream();
        String contentType = urlConnection.getContentType();
        String encoding = urlConnection.getContentEncoding();

        // If got a contentType header
        if (contentType != null) {

            String mimeType = contentType;

            // Parse mime type from contenttype string
            if (contentType.contains(";")) {
                mimeType = contentType.split(";")[0].trim();
            }

            LogUtils.i("SSL_PINNING_WEBVIEWS", "Mime: " + mimeType);

            //listener.Loaded(webUrl.toString());

            // Return the response
            return new WebResourceResponse(mimeType, encoding, is);
        }

    } catch (Exception e) {
        e.printStackTrace();
        if (isCause(CertPathValidatorException.class, e)) {
            //listener.PinningPreventedLoading(webUrl);
        }
        LogUtils.i("SSL_PINNING_WEBVIEWS", e.getLocalizedMessage());
    }

    // Return empty response for this request
    return new WebResourceResponse(null, null, null);
}

private void prepareSslPinning(List certificates) throws IOException {

    try {


        KeyManagerFactory keyManagerFactory = KeyManagerFactory
                .getInstance("X509");

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");

        KeyStore keyStore = KeyStore.getInstance("PKCS12");

        KeyStore keyStore2 = KeyStore.getInstance(KeyStore.getDefaultType());
        //读取证书
        InputStream ksIn = getResources().getAssets().open("mykey.p12");

        //加载证书
        keyStore2.load(null);
        keyStore.load(ksIn, "yibanyiban".toCharArray());

        ksIn.close();
        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
        try {
            for (int i = 0, size = certificates.size(); i  expected,
        Throwable exc
) {
    return expected.isInstance(exc) || (
            exc != null && isCause(expected, exc.getCause())
    );
}

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

注:上面很重要部分真的很重要

2.NetConfig读取cer证书

public class NetConfig {
// 证书数据
private static List CERTIFICATES_DATA = new ArrayList();
/**
 * 添加https证书
 * @param inputStream
 */
public synchronized static void addCertificate(InputStream inputStream) {

    if (inputStream != null) {
        try {
            int ava = 0;// 数据当次可读长度
            int len = 0;// 数据总长度
            ArrayList data = new ArrayList();
            while ((ava = inputStream.available()) > 0) {
                byte[] buffer = new byte[ava];
                inputStream.read(buffer);
                data.add(buffer);
                len += ava;
            }

            byte[] buff = new byte[len];
            int dstPos = 0;
            for (byte[] bytes:data) {
                int length = bytes.length;
                System.arraycopy(bytes, 0, buff, dstPos, length);
                dstPos += length;
            }
            CERTIFICATES_DATA.add(buff);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

/**
 * https证书
 * @return
 */
public static List getCertificatesData() {
    return CERTIFICATES_DATA;
}}

3.MyApplication读cer###

/**
 * 读取cer证书
 */
private void readCer() {
    // 添加https证书
    try {
        InputStream is = getAssets().open("tomcat.cer");
        NetConfig.addCertificate(is); // 这里将证书读取出来,,放在配置中byte[]里

    } catch (IOException ioe) {
        ioe.printStackTrace();
    }
}

写这个方法后,在OnCreate()中调用

4.webview的使用

SslPinningWebViewClient webViewClient = null;
            try {
                webViewClient = new SslPinningWebViewClient();
            } catch (IOException e) {
                e.printStackTrace();
            }
            //采用自定义WebViewClient
            wvMain.setWebViewClient(webViewClient);

在做之前可以先在电脑浏览器上试证书是否是能用的,至于方法百度。如果证书能用,基本通过上面步骤是没问题的。
这篇文章在csdn上面也有。
但都是我的,哈哈
还有问题可私信我

作者 oshook

上一 1 … 6 7

Copyright © 2023 | 粤ICP备14006518号-3