WebView内存泄露终结解决方案

但凡是做Android开发的相信都对webview不会陌生,而且也对系统自带的webview本身存在的问题也是怨念很久了,一方面是本身对js的支持不是很好另外一方面就是经常被人诟病的内存泄露了。

不知道各位遇到同样问题的朋友是怎么解决的,网上也有很多解析和方案但至少在我的项目中是没任何效果的,今天我就分享一下我最终是怎么解决这些问题的(其实是很蠢的一个办法)。

需求背景:
需要一个带有加载进度条的webview来正常的显示合作方和自己的web页面。

1、解决webview对一些js的支持:

1
2
用JsBridge代替系统原生的webview,
github地址:https://github.com/lzyzsd/JsBridge

2、解决webview内存泄露:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Bind(R.id.pb)
ProgressBar pb;
@Bind(R.id.mWebView)
BridgeWebView mWebView;

pb.setMax(100);
mWebView.setWebChromeClient(new WebViewClient());
mWebView.loadUrl(strWebsite);

private class WebViewClient extends WebChromeClient {
@Override
public void onProgressChanged(WebView view, int newProgress) {
try {
pb.setProgress(newProgress);
if (newProgress == 100) {
pb.setVisibility(View.GONE);
}
} catch (Exception e) {
e.printStackTrace();
}
super.onProgressChanged(view, newProgress);
}
}

@Override
protected void onDestroy() {
super.onDestroy();
try {
if (mWebView != null) {
mWebView.removeAllViews();
mWebView.destroy();
mWebView = null;
}
} catch (Exception e) {
e.printStackTrace();
}
}

最后介绍大家一个用来检测应用内存泄露的工具:leakcanary

github地址:https://github.com/square/leakcanary


FullStackEngineer的公众号,更多分享

Share Comments

Activity启动后EditText是否自动弹出输入法虚拟键盘的问题

在开发过程中,我们经常会遇到Activity中包含EditText控件时会自动弹出虚拟键盘的情况,这是由于EditText自动获得焦点的缘故,只要让EditText失去焦点就行了,解决办法如下:

1、在Manifest.xml文件中相应的Activity下添加如下代码:

1
android:windowSoftInputMode="stateHidden"

2、让EditText失去焦点,用EditText的clearFocus:

1
2
EditText edt = (EditText)findViewById(R.id.edt);
edt.clearFocus();

3、强制隐藏Android输入法窗口:

1
2
3
EditText edt = (EditText)findViewById(R.id.edt);  
InputMethodManager imm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(edt.getWindowToken(), 0);

4、要求EditText始终不弹出虚拟键盘:

1
2
EditText edt = (EditText)findViewById(R.id.edt);  
edt.setInputType(InputType.TYPE_NULL);

但有时我们确实是想让EditText自动获得焦点并弹出软键盘,在设置了EditText自动获得焦点后,软件盘不会弹出。
注意:此时是由于刚跳到一个新的界面,界面未加载完全而无法弹出软键盘。此时应该适当的延迟弹出软键盘,如500毫秒(保证界面的数据加载完成,如果500毫秒仍未弹出,则延长至1000毫秒)。

1、可以在EditText后面加上一段代码:

1
2
3
4
5
6
7
8
9
Timer timer = new Timer();  
timer.schedule(new TimerTask() {

public void run() {
InputMethodManager inputManager = (InputMethodManager) editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.showSoftInput(editText, 0);
}

}, 500);

2、给activity配置加入属性:

1
android:windowSoftInputMode="adjustResize"

FullStackEngineer的公众号,更多分享

Share Comments

Android持续集成技术实践

背景

随着业务需求的演进,工程的复杂度会逐渐增加,自动化的践行日益强烈。事实上,工程的自动化一直是我们努力追求的目标,能有效提高我们的生产效率,最大化减少人为出错的概率,实现一些复杂的业务需求应变。

以我现在的公司为例,我们有22个渠道包,而且分为测试环境和生产环境,新的迭代开始除去要经常给测试人员直接烧测试版APP偶尔还会被商务和运营打断要求新增一个渠道包。尤其临近发版的一周,几乎每天都要新版本。这样的话,有两方面的影响:第一,打断了开发人员的开发进度;第二,开发人员打包效率低下。

要解决这个问题,必须实现移动端应用的自动化构建。具体说来就是,使用持续集成(CI)系统jenkins,自动检测并拉取Git上的最新代码,自动打包成不同的渠道apk,自动上传到内测分发平台蒲公英上和自建的FTP服务器上。(接下来,测试人员只要打开一个(或多个)固定的网址,扫描一下二维码,就能下载最新的版本了…)

环境

因为公司内网的服务器都是Windows操作系统,所以下面的操作都是以Windows为例,无论是哪个操作系统,jenkins的配置是一样的。

安装Jenkins

官网地址: http://jenkins-ci.org/,具体安装过程就不详写了跟平常装软件没什么区别。
默认访问 http://localhost:8080/ , 可进入jenkins配置页面。

安装Jenkins相关插件

点击系统管理>管理插件>可选插件,可搜索以下插件安装

git插件(GIT plugin)

ssh插件(SSH Credentials Plugin)

Gradle插件(Gradle plugin) - android专用

注:

  1. 这里要用VPN或者修改系统的hosts文件才可以搜索到插件;
  2. 还有就是Windows中要装好JDK、Git、Gradle的环境。

装好后的效果图:

新建Job

主页面,新建 -> 构建一个自由风格的软件项目即可。

配置git仓库

如果安装了git插件,在源码管理会出现Git,选中之后:

配置自动拉取最新代码

在构建触发器中,有两种自动拉取代码并编译的策略:

  1. 设置Poll SCM,设置定时器,定时检查代码更新,有更新则编译,否则不编译。
  2. 也可以设置Build periodically,周期性的执行编译任务。

配置gradle

如果安装gradle插件成功的话,应该会出现下图的Invoke Gradle script,配置一下:

这样,就能自动在project下的app的build/outputs/apk下生成相应的apk.

因为要区分测试环境和生产环境,所以我建了两个任务分别对应git上的主分支和子分支:

FTP服务器搭建:

如果不会利用IIS搭建FTP的同学可以自行百度这里就不详细介绍了,记得把FTP根路径指向编译结果的目录:D:\Jenkins\jobs\XXForAndroidTest\workspace\app\build\outputs\apk

如果编译失败,请检查以下问题:

  1. 确保gradle、git、jdk的环境变量都配好
  2. 找不到local.properties中sdk定义,因为一般来说local.properties不会添加到版本库。
  3. 还有就是子项目中build.gradle的签名秘钥的路径问题

关于local.properties的定义:

sdk.dir=xx/xx/android-sdk

再编译一般就会编译成功,当然当那些第三方库需要重新下载的话,编译可能会很慢。

总结一下

经过以上的折腾,以后终于可以彻底解放开发人员的双手去专心写代码了,我们在以后的工作中也要尽量去把精力放在业务上面提高工作效率。


FullStackEngineer的公众号,更多分享

Share Comments

Android热修复技术实践

  1. 背景
    我所在的公司是一家互联网金融领域的初创型公司,这类公司向来都是奉行快速迭代敏捷开发,所以基本我们的APP两周一个迭代有时候工期比较紧可能一周一个迭代,在缺乏专业QA人员的情况下单凭团队内其他做产品和运营的小伙伴的用最原始的人工点击测试下难免会遗漏一些潜在bug,于是当我们的应用发布之后,经过十多万用户的随机点击和产生的随机数据突然发现了一个严重bug需要进行紧急修复的时候公司各方就会忙得焦头烂额:还原bug、修复、重新打包App、测试、向各个应用市场和渠道换包、提示用户升级、用户下载、覆盖安装。有时候仅仅是为了修改了一行代码,也要付出巨大的成本进行换包和重新发布。 这时候就产生了一个问题:有没有办法以补丁的方式动态修复紧急Bug,不再需要重新发布App,不再需要用户重新下载,覆盖安装? 虽然Android系统并没有提供这个技术,但是很幸运的告诉大家,答案是:可以的!而且在最初由QQ空间团队提出的热补丁动态修复技术方案后各大公司也都纷纷效仿,网上也出现了很多开源的解决方案。
  2. 实际案例
    让我们先来看一下网上都有哪些解决方案:
    Xposed
    dexposed
    AndFix
    DroidFix
    DynamicAPK
    Nuwa
    根据其描述,原理都来自:Android dex分包方案(http://codecloud.net/android-hot-load-6575.html)。这里就不对这些框架做过多对比了,因为原理都一致,实现的代码可能有些差异并不是特别大。
    这里我就先讲讲我是怎么去对热修复框架进行选型的吧,大家也看到了能实现这个功能的开源框架最近出了很多但并不是每个都适合我们现在的应用,Xposed它因为需要手机获得root权限才能生效所以首先pass。
    dexposed和AndFix是阿里基于Xposed的思路沉淀出的两套热修复解决方案,但是经过实际测试后发现它由于缺少动态库so文件所以在大部分机型上都不能正常运行:

    在其官方github上有类似很多的问题所以也只能放弃虽然它的补丁生成器做的很完善但是然并卵……
    DynamicAPK这个是携程最新公布的一个解决方案,但是由于它功能实在过于庞大而且其主要功能是用来做多apk动态加载的所以对热修复这块并不是重点实现。
    最后我把注意力放在了Nuwa上,但是经过实际部署测试后发现总是编译出错于是翻看了github上的issue列表发现有很多人也遇到了类似问题最后通过排除掉Nuwa.init所在的类后就fix好了,原来是Nuwa所在的class文件是无法进行修复的,所以在这个文件中尽量只写一些初始化代码和配置保证不会出问题。然后接下来遇到的就是补丁包生成不了的窘境了……继续google:
    https://github.com/jasonross/Nuwa/issues/23在这篇文章中找到了答案。
    好了接下来就是考虑到我们有20个渠道并且可能出现不同版本的补丁包的情况了,于是我申请了一台ftp服务器专门用来做为每次补丁包的存放路径,在这个路径下:

    这样在客户端通过每次拉取config配置文件的api进行判断当前版本是否需要打补丁,如果需要则根据当前渠道号从对应的ftp服务器下载,当用户第二次打开应用的时候就会load这个patch,这样就在用户毫不知情的情况下完成了问题修复。

FullStackEngineer的公众号,更多分享

Share Comments

Android实现APP开屏广告

代码详见:https://github.com/logan62334/StartupAdPage

功能点:

  1. 实现打开应用从moblieapi获取广告信息并存储在本地;
  2. 每次APP打开显示上一次缓存下来的广告;
  3. 可以控制广告停留时间和跳转页面
  4. 很容易扩展

FullStackEngineer的公众号,更多分享

Share Comments

Android实现PopupWindow背景半透明(兼容方案)

大家想必对PopupWindow不会很陌生吧,我们在开发中经常会遇到要求使其背景半透明的需求,但网上的很多解决方案只能是在大部分机型上满足要求,像华为这样的机型就会发现我们原来设置的背景变暗效果的代码并没有起效果。
这里我贴出最终的兼容方案:

View contentView;
    LayoutInflater mLayoutInflater = LayoutInflater.from(activity);
    contentView = mLayoutInflater.inflate(R.layout.layout_popupwindow,
            null);
    pop = new PopupWindow(contentView,
            ViewGroup.LayoutParams.MATCH_PARENT, (int) context.getResources().getDimension(R.dimen.y568));
    TextView tvTitle = (TextView) contentView.findViewById(R.id.text);
    tvTitle.setText(strTitle);
    ListView listView = (ListView) contentView.findViewById(R.id.list);
    // 产生背景变暗效果
    WindowManager.LayoutParams lp = activity.getWindow()
            .getAttributes();
    lp.alpha = 0.4f;
    activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
    activity.getWindow().setAttributes(lp);
    pop.setTouchable(true);
    pop.setFocusable(true);
    pop.setBackgroundDrawable(new BitmapDrawable());
    pop.setOutsideTouchable(true);
    pop.showAtLocation(contentView, Gravity.BOTTOM, 0, 0);
    pop.update();
    pop.setOnDismissListener(new PopupWindow.OnDismissListener() {

        // 在dismiss中恢复透明度
        public void onDismiss() {
            WindowManager.LayoutParams lp = activity.getWindow()
                    .getAttributes();
            lp.alpha = 1f;
            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
            activity.getWindow().setAttributes(lp);
        }
    });
    listView.setOnItemClickListener(onItemClickListener);
    listView.setAdapter(adapter);

注:特别是下面几行代码

// 产生背景变暗效果
    WindowManager.LayoutParams lp = activity.getWindow()
            .getAttributes();
    lp.alpha = 0.4f;
    activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
    activity.getWindow().setAttributes(lp);
    pop.setTouchable(true);
    pop.setFocusable(true);
    pop.setBackgroundDrawable(new BitmapDrawable());
    pop.setOutsideTouchable(true);
    pop.showAtLocation(contentView, Gravity.BOTTOM, 0, 0);
    pop.update();
    pop.setOnDismissListener(new PopupWindow.OnDismissListener() {

        // 在dismiss中恢复透明度
        public void onDismiss() {
            WindowManager.LayoutParams lp = activity.getWindow()
                    .getAttributes();
            lp.alpha = 1f;
              activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
            activity.getWindow().setAttributes(lp);
        }
    });

网上很多方案都要求加下面这两行代码,但其实加上反而会影响华为这种机型的显示效果

ColorDrawable dw = new ColorDrawable(-00000);
popupWindow.setBackgroundDrawable(dw);
***

![FullStackEngineer的公众号,更多分享](https://github.com/logan62334/ImageArchive/raw/master/weixin/weixin.jpg)
Share Comments