Skip to content

钩子专属

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

All posts by oshook

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

build.gradle分正式服,测试服配置请求链接常量

build.gradle分正式服,测试服配置请求链接常量

buildTypes {
    release {
        //删除无用的资源
        shrinkResources true
        //开启混淆文件规则
        minifyEnabled true
        //像Google Play强制要求开发者上传的应用必须是经过zipAlign的,zipAlign可以让安装包中的资源按4字节对齐,这样可以减少应用在运行时的内存消耗。
        zipAlignEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        buildConfigField "int", "VERSION_ID", "15"  //2018/8/31出包
        buildConfigField "String", "BASE_URL",'"xxx(url)"'
        buildConfigField "boolean", "LOG_SHOW", "false"//是否输出LOG信息
        
    }
    debug {
        //删除无用的资源
        //shrinkResources true
        minifyEnabled true
        //像Google Play强制要求开发者上传的应用必须是经过zipAlign的,zipAlign可以让安装包中的资源按4字节对齐,这样可以减少应用在运行时的内存消耗。
        zipAlignEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        buildConfigField "int", "VERSION_ID", "15"
        buildConfigField "String", "BASE_URL",'"xxx(url)"'
        buildConfigField "boolean", "LOG_SHOW", "true"//是否输出LOG信息
        
        
    }
    acceptance {
        //删除无用的资源
        shrinkResources true
        minifyEnabled true
        //像Google Play强制要求开发者上传的应用必须是经过zipAlign的,zipAlign可以让安装包中的资源按4字节对齐,这样可以减少应用在运行时的内存消耗。
        zipAlignEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        buildConfigField "int", "VERSION_ID", "15"
        buildConfigField "String", "BASE_URL",'"xxx(url)"'
        buildConfigField "boolean", "LOG_SHOW", "true"//是否输出LOG信息
        
    
    }
}

注意字符串的格式是'”xxx(url)”‘

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

WebView的加载进度,报错处理,配置

WebView的加载进度,报错处理,配置

xml布局代码(仅供参考):

    












进度条监听

wvMain.setWebChromeClient(new WebChromeClient() {

        @Override
        public void onProgressChanged(WebView view, int newProgress) {
            LogUtils.i(newProgress+"");
            if (null != npbBar) {
                npbBar.setProgress(newProgress);
                if (newProgress == 100) {
                    npbBar.setVisibility(View.GONE);
                    wvMain.setVisibility(View.VISIBLE);
                }
            }
            super.onProgressChanged(view, newProgress);
        }
    });

报错处理

wvMain.setWebViewClient(new WebViewClient() {

                        @Override
                        public boolean shouldOverrideUrlLoading(WebView view, String url) {
                            // 使用当前WebView处理跳转
                            view.loadUrl(url);
                            // true表示此事件在此处被处理,不需要再广播
                            return true;
                        }

                        @Override
                        public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
                            super.onReceivedError(view, request, error);
                            //错误布局切换代码
                            loadError();

                        }
                    });

webView配置

private void webViewSetting() {
    wvMain.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);//支持通过Javascript打开新窗口
    wvMain.getSettings().setJavaScriptEnabled(true);//设置WebView属性,能够执行Javascript脚本
    wvMain.getSettings().setDomStorageEnabled(true);//设置是否启用了DOM Storage API
    wvMain.getSettings().setDatabaseEnabled(true);//开启database storage API功能
    wvMain.getSettings().setUseWideViewPort(true);//将图片调整到适合webview的大小
    wvMain.getSettings().setLoadWithOverviewMode(true);// 缩放至屏幕的大小

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

LayoutInflater.from(this).inflate()详解

需求:经常需要从其他布局引入到当前布局中
inflate:英文翻译膨胀的,挺贴切的

解决问题:

  1. 为什么我布局没有显示出来
  2. 为什么我根布局明明设置了属性了啊,怎么没生效呢
  3. 返回给我的view是我的根布局还是父容器呢

问题的产生是infate()参数不同导致的结果

第一种

root != null, attachToRoot=true(默认也是true)

LinearLayout root = (LinearLayout) findViewById(R.id.layout);
View view1 = LayoutInflater.from(this).inflate(R.layout.item, root, true);
View view2 = LayoutInflater.from(this).inflate(R.layout.item, root);

结果:
返回的view是父容器(root)布局
item根布局的宽高属性生效
已经添加到父容器(root)中

第二种

root != null, attachToRoot=false

LinearLayout root = (LinearLayout) findViewById(R.id.layout);
View view1 = LayoutInflater.from(this).inflate(R.layout.item, root, false);

结果:
返回item根布局
根布局的宽高属性生效
未添加到父容器(root)中

第三种

root == null,attachToRoot=false/true

View view = LayoutInflater.from(this).inflate(R.layout.item, null, false);
View view = LayoutInflater.from(this).inflate(R.layout.item, null, true);
View view = LayoutInflater.from(this).inflate(R.layout.item, null);

结果:
返回item根布局
根布局的宽高属性失效
未添加到父容器(root)中

一般经验来说:LayoutInflater.from(this).inflate(R.layout.item, null)用的最多
但是碰到要添加到列表的记得需要root,root一般就是列表控件,要不然宽高属性失效,你就纳闷怎么没显示的呢

如果恰好解决你的问题或者学到了新知识,就点个赞

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

TextView漏的知识点

/**
     * 设置图片大小
     */
    Drawable[] drawables = tvName.getCompoundDrawables();
    drawables[0].setBounds(0,0,80,80);
    tvName.setCompoundDrawables(drawables[0],null,null,null);

    /**
     * 使用Html
     */
    String sMCHtml = ""+"他是谁"+"";
    tvName.setText(Html.fromHtml(sMCHtml));
    tvName.setMovementMethod(LinkMovementMethod.getInstance());

    /**
     * SpannableString字体多样化
     * 还有点击效果:ClickableSpan
     */
    SpannableString span = new SpannableString("我是谁,我在哪?");
    span.setSpan(new ForegroundColorSpan(context.getResources().getColor(R.color.colorAccent)),0,span.length(),0);
    tvName.setText(span);

跑马灯效果

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

LinearLayout分割线

LinearLayout

原来LinearLayout还有设置分割线功能

知识点

  • android:divider设置作为分割线的图片
  • android:showDividers设置分割线的位置,none(无),beginning(开始),end(结束),middle(每两个组件间)
  • dividerPadding设置分割线的Padding

应用场景

一般设置界面,关于我们界面

效果图

LinearLayout.png

关键代码

activity_ll










divider_line.xml

















如果把上面黄色换成白色,是不是就是你想要的呢

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

Android通知栏权限是否开启

通知栏权限是否开启

需要分系统版本来操作
4.4版本一下不处理,4.4到8.0,8.0以上

@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static boolean isNotificationEnabled(Context context) {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        //8.0手机以上
        if (((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)).getImportance() == NotificationManager.IMPORTANCE_NONE) {
            return false;
        }
    }

    String CHECK_OP_NO_THROW = "checkOpNoThrow";
    String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION";

    AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
    ApplicationInfo appInfo = context.getApplicationInfo();
    String pkg = context.getApplicationContext().getPackageName();
    int uid = appInfo.uid;

    Class appOpsClass = null;

    try {
        appOpsClass = Class.forName(AppOpsManager.class.getName());
        Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE,
                String.class);
        Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION);

        int value = (Integer) opPostNotificationValue.get(Integer.class);
        return ((Integer) checkOpNoThrowMethod.invoke(mAppOps, value, uid, pkg) == AppOpsManager.MODE_ALLOWED);

    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}

跳转到手机设置界面:

fun toSetting(activity: Activity, requestCode: Int) {
    val intent = Intent()
    intent.action = "android.settings.APPLICATION_DETAILS_SETTINGS"
    intent.data = Uri.fromParts("package", activity.packageName, null)
    activity.startActivityForResult(intent, requestCode)
}

设置界面返回处理:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    if (requestCode == 101) {
        if (PermissionsUtil.isNotificationEnabled(mActivity!!) ) {
            ViewUtils.visibility(card_view_notification_status,false)

        }
    }
}

如果解决问题,是不是要打赏一波呢

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

融云通知栏push和onReceived重复解决方案

业务需求

根据系统推送过来的信息内容跳到特定页面

吐槽,吐槽,吐槽

1.通知栏的显示可以有两个通道内容来控制
2.点完push的信息,进应用后onReceived还会再收到一条同样的内容,如果这时要处理你自己的跳转,就坑了。就算不点进来,你直接点桌面的应用,也会同样的问题
3.onNotificationMessageClicked()方法中的message内容,push的内容和onReceived的内容还不一样,但是它点击都经过这方法。message.getPushData()内容是不一样的,控制跳转内容又存在getPushData()里面
4.onNotificationMessageArrived()不是每条push信息来都调用,还分手机的。照道理通知栏内容你都能封装上去。这里就没法弄?一定要开启自启动权限…
5.每次问问题都叫我看demo,能看出来就不用问你了啊

暂时的解决方案

基本知识:return true 就不会弹通知栏
基本思路:
1、push过的信息onReceived就不处理
2、刚启动应用onReceived不处理信息
3、用SP来存储数据,最好结合接口给唯一值

部分代码

SealNotificationReceiver

@Override
public boolean onNotificationMessageArrived(Context context, PushNotificationMessage message) {
    
    String transactionId;
    if (null != message.getPushData() && Utils.isJson(message.getPushData())) {
        try {
            JSONObject object = new JSONObject(message.getPushData());
            
            transactionId = object.isNull("transactionId") ? "" : object.getString("transactionId");
            if(!TextUtils.isEmpty(transactionId)){
                 //保存push过来的id,唯一值,在onRecevied会用到
                SPInfo.saveNodeNotificationId(context,transactionId);
                
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }

    }

    return false;
}


@Override
public boolean onNotificationMessageClicked(Context context, PushNotificationMessage message) {
    LogUtils.i("pushExtra", "onNotificationMessageClicked" + "t" + message.getTargetId() + "t" + message.getExtra() + "t" + message.getPushContent() + "t" + message.getPushData() +
            "t"+message.getPushId());
    
    String transactionId;
    
    if (null != message.getPushData() && Utils.isJson(message.getPushData())) {
        try {
            JSONObject object = new JSONObject(message.getPushData());
            
            transactionId = object.isNull("transactionId") ? "" : object.getString("transactionId");
            //点击中也添加,防止onNotificationMessageArrived没调
            if(&& !TextUtils.isEmpty(transactionId)){
                SPInfo.saveNodeNotificationId(context,transactionId);
                
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }

    }

在onReceived()中
jump为false会走融云默认处理方式,为true自己处理

//设置跳转
                        if((null != SPInfo.getNodeNotificationIds(mContext) && !SPInfo.getNodeNotificationIds(mContext).isEmpty()
                                && SPInfo.getNodeNotificationIds(mContext).contains(transactionId)) || SPUtils.getBoolean(mContext,SPKey.START_APP_FLAG,false)){
                            LogUtils.i("spNodes",SPInfo.getNodeNotificationIds(mContext).toString());
                            //通知栏通过push已经有,返回true不处理,消除本地记录id
                            //或者刚启动应用,返回true不处理
                            jump = true;
                            SPInfo.cleanNodeNotificationId(mContext,transactionId);
                            SPUtils.saveBoolean(mContext, SPKey.START_APP_FLAG,false);
                        } else {
                            jump = setJump(app_node,tm.getContent());
                        }

差不多就解决了,只是暂时的解决方案。主要是思路,思路,思路

有问题或者更好的解决方案,加我Q:893151960

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

字符串一旦赋值就不能改变

String s = “123”;
s = “3210”;
输出s是3210…
是不是突然懵逼了,说好的一旦赋值就不能改变了呢。

正常解析:字符串是final的,引用数据类型传递的是引用,s是个变量,“123”才是字符串,s = “3210”是指向了新的字符串,原来字符串“123”没有改变的,等待系统回收。

奇葩解析:你老婆本身无法变成另外的人,但你换了个老婆!!!

题外话:
String s = “123”;
s.replace(“2”, “856”);
System.out.println(s);

猜输出的是什么?

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

获取Activity的最外层布局

需求:获取每个布局最外层布局,首先想到的是findViedId(),但是很大可能每个布局定义的id是不一样的
普及:每个Activity只有有布局就有DecorView,DecorView包括标题栏和内容栏,在高版本中DecorView还可以设置状态栏,比如设置状态栏透明

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
                activity.getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
                
            }

重点是内容栏,setContentView()就是设置内容栏,所以要获取要在set后面;
还有内容栏是一定存在的,并且内容来具体固定的id,那就是“content”,它的完整id是android.R.id.content。这id是系统固定的。然后我们自己写的布局就在这个content里面
示例代码:
内容栏view:

(ViewGroup)findViewById(android.R.id.content)

我们自己写的布局最外层布局:

((ViewGroup)findViewById(android.R.id.content)).getChildAt(0)

使用场合:在BaseActivity基类中可以统一控件app的布局

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

Android版本下载更新

几乎每个应该都必备的功能
准备工作:请求版本更新获取能下载apk的链接,
比如:”http://yibanyue.oss-cn-hangzhou.xxx.com/apk/yiyue.apk”
流程:请求版本更新链接–>获取到链接,弹框询问是否更新–>在本地建apk文件夹–>更新用DownloadManager下载apk,路径放在前面建好的文件夹中–>BroadcastReceiver监听是否下载好,下载完要激活安装
关键代码
弹框询问以及建文件夹,下载(无关代码自行删除):
DOWNLOAD_FOLDER_NAME:apk文件夹名字

DialogUtils.okAndCancel(mContext, pdesc, new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //更新APP
            if (!TextUtils.isEmpty(url)) {
                downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
                if (isForce) {
                    Dialog dialog = DialogUtils.progress(getContext(), new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            downloadManager.remove(SharedPreferencesUtils.getLong(getContext(), SPKey.APK_DOWN_ID, 0));
                            ActivityUtils.closeAll();
                        }
                    });
                }
                DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
                File fileDir = Environment.getExternalStoragePublicDirectory(DOWNLOAD_FOLDER_NAME);
                if (fileDir.exists() && fileDir.isDirectory()) {
                    request.setDestinationInExternalPublicDir(DOWNLOAD_FOLDER_NAME, url.substring(url.lastIndexOf("/") + 1));
                } else {
                    fileDir.mkdirs();
                    request.setDestinationInExternalPublicDir(DOWNLOAD_FOLDER_NAME, url.substring(url.lastIndexOf("/") + 1));
                }
                long downloadId = downloadManager.enqueue(request);
                downId = downloadId;
                SharedPreferencesUtils.saveLong(getContext(), SPKey.APK_DOWN_ID, downloadId);
            } else {
                toast(R.string.isEmptyUrl);
            }

        }
    }, new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (isForce) {
                ActivityUtils.closeAll();
            }
        }
    });

下载完激活下载的apk:

public class DownReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
    DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
    if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
        DownloadManager.Query query = new DownloadManager.Query();
        //在广播中取出下载任务的id
        long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0);
        query.setFilterById(id);
        Cursor c = manager.query(query);
        if (c.moveToFirst()) {
            //获取文件下载路径
            //Android 7.0以上的方式:请求获取写入权限,这一步报错


            int fileUriIdx = c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI);
            String fileUri = c.getString(fileUriIdx);
            String fileName = null;
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
                if (fileUri != null) {
                    fileName = Uri.parse(fileUri).getPath();
                }
            } else {
                //过时的方式:DownloadManager.COLUMN_LOCAL_FILENAME
                int fileNameIdx = c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME);
                fileName = c.getString(fileNameIdx);
            }


            if (id == SharedPreferencesUtils.getLong(context, SPKey.APK_DOWN_ID, 0) && !TextUtils.isEmpty(fileName)) {
                //打开APK安装
                installApk(context, fileName);
            } else {
                //如果文件名不为空,说明已经存在了,拿到文件名想干嘛都好
                if (fileName != null) {
                    UIUtils.toast(context, R.string.down_success);
                }
            }
        }
    } else if (DownloadManager.ACTION_NOTIFICATION_CLICKED.equals(intent.getAction())) {
        long[] ids = intent.getLongArrayExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS);
        //点击通知栏取消下载
        manager.remove(ids);
        UIUtils.toast(context, R.string.down_cancel);
    }
}

/**
 * 安装APK文件
 */
public void installApk(Context context, String fileName) {
    ActivityUtils.closeAll();
    File apkfile = new File(fileName);
    if (!apkfile.exists()) {
        return;
    }
    // 通过Intent安装APK文件
    Intent i = new Intent(Intent.ACTION_VIEW);
    i.setDataAndType(Uri.parse("file://" + apkfile.toString()), "application/vnd.android.package-archive");
    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(i);
}}

记得在AndroidMainfest清单中添加:


        
            
            
        
    
作者 oshook

上一 1 … 5 6 7 下一个

Copyright © 2023 | 粤ICP备14006518号-3