Android应用程序从Google Play下载时崩溃但在本地开发中效果很好 - 为什么?

时间:2015-10-23 15:25:14

标签: android google-play

我不确定是否有人可以帮忙解决这个问题,但这真的很奇怪。我构建了一个Android应用程序并在本地测试它,一切都很好。然后,我将它发布到应用程序商店,它一直在崩溃。

google play上的堆栈跟踪显示:

java.lang.RuntimeException: An error occured while executing doInBackground()
    at android.os.AsyncTask$3.done(AsyncTask.java:300)
    at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:355)
    at java.util.concurrent.FutureTask.setException(FutureTask.java:222)
    at java.util.concurrent.FutureTask.run(FutureTask.java:242)
    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
    at java.lang.Thread.run(Thread.java:818)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.length()' on a null object reference
    at libcore.net.UriCodec.encode(UriCodec.java:132)
    at java.net.URLEncoder.encode(URLEncoder.java:57)
    at com.xxxx.yyyy.bbbb.SignedRequestsHelper.percentEncodeRfc3986(SignedRequestsHelper.java:120)
    at com.xxxx.yyyy.bbbb.SignedRequestsHelper.sign(SignedRequestsHelper.java:63)
    at com.xxxx.yyyy.SearchFragment$SearchAsyncTask.doInBackground(SearchFragment.java:385)
    at com.xxxx.yyyy.SearchFragment$SearchAsyncTask.doInBackground(SearchFragment.java:338)
    at android.os.AsyncTask$2.call(AsyncTask.java:288)
    at java.util.concurrent.FutureTask.run(FutureTask.java:237)
    ... 4 more

所以看起来我的SignedRequestsHelper.percentEncodeRfc3986()方法有些问题。字符串为null。但请注意,在开发应用程序并在不同设备上测试时,不会发生此错误。只有在将其上传到谷歌播放并从那里下载后才会出现此错误。

所以我查看了当时的SignedRequestHelper类:

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
import java.util.TimeZone;
import java.util.TreeMap;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

public class SignedRequestsHelper {
    private static final String UTF8_CHARSET = "UTF-8";
    private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
    private static final String REQUEST_URI = "/onca/xml";
    private static final String REQUEST_METHOD = "GET";

    // use xml-uk.amznxslt.com for xslt requests, or ecs.amazonaws.co.uk for others
    private String endpoint = "webservices.amazon.com"; // must be lowercase

    // change this so reads from properties file
    private String awsAccessKeyId = "xxxx";
    private String awsSecretKey = "xxx";

    private SecretKeySpec secretKeySpec = null;
    private Mac mac = null;

    public SignedRequestsHelper() throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
        byte[] secretyKeyBytes = awsSecretKey.getBytes(UTF8_CHARSET);
        secretKeySpec =
                new SecretKeySpec(secretyKeyBytes, HMAC_SHA256_ALGORITHM);
        mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
        mac.init(secretKeySpec);
    }

    public String sign(Map<String, String> params) {
        params.put("AWSAccessKeyId", awsAccessKeyId);
        params.put("Timestamp", timestamp());

        SortedMap<String, String> sortedParamMap =
                new TreeMap<String, String>(params);
        String canonicalQS = canonicalize(sortedParamMap);
        String toSign =
                REQUEST_METHOD + "\n"
                        + endpoint + "\n"
                        + REQUEST_URI + "\n"
                        + canonicalQS;

        String hmac = hmac(toSign);
        String sig = percentEncodeRfc3986(hmac);
        String url = "http://" + endpoint + REQUEST_URI + "?" +
                canonicalQS + "&Signature=" + sig;

        return url;
    }

    private String hmac(String stringToSign) {
        String signature = null;
        byte[] data;
        byte[] rawHmac;
        try {
            data = stringToSign.getBytes(UTF8_CHARSET);
            rawHmac = mac.doFinal(data);
            Base64 encoder = new Base64();
            signature = new String(encoder.encode(rawHmac));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(UTF8_CHARSET + " is unsupported!", e);
        }
        return signature;
    }

    private String timestamp() {
        String timestamp = null;
        Calendar cal = Calendar.getInstance();
        DateFormat dfm = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        dfm.setTimeZone(TimeZone.getTimeZone("GMT"));
        timestamp = dfm.format(cal.getTime());
        return timestamp;
    }

    private String canonicalize(SortedMap<String, String> sortedParamMap)
    {
        if (sortedParamMap.isEmpty()) {
            return "";
        }

        StringBuffer buffer = new StringBuffer();
        Iterator<Map.Entry<String, String>> iter =
                sortedParamMap.entrySet().iterator();

        while (iter.hasNext()) {
            Map.Entry<String, String> kvpair = iter.next();
            buffer.append(percentEncodeRfc3986(kvpair.getKey()));
            buffer.append("=");
            buffer.append(percentEncodeRfc3986(kvpair.getValue()));
            if (iter.hasNext()) {
                buffer.append("&");
            }
        }
        String cannoical = buffer.toString();
        return cannoical;
    }

    private String percentEncodeRfc3986(String s) {
        String out;
        try {
            out = URLEncoder.encode(s, UTF8_CHARSET)
                    .replace("+", "%20")
                    .replace("*", "%2A")
                    .replace("%7E", "~");
        } catch (UnsupportedEncodingException e) {
            out = s;
        }
        return out;
    }
}

我将percentEncodeRfc3986()方法更改为这样。只需删除最后两个替换标记:

private String percentEncodeRfc3986(String s) {
        String out;
        try {
            out = URLEncoder.encode(s, UTF8_CHARSET)
                    .replace("+", "%20");
        } catch (UnsupportedEncodingException e) {
            out = s;
        }
        return out;
    }

然后在本地进行测试:它有效。然后将其发布到谷歌播放并下载它:它的工作原理。

所以一切都很好,但是我现在在这里摸不着为什么现在正在运作

这也让我感到紧张,因为代码完全适用于我的模拟器和测试设备,当应用程序上传到谷歌播放时突然无法正常工作。这似乎是一件小事......一些额外的替换导致了这个错误。

有人可以帮助我了解这里发生了什么吗?或者至少为什么有时发布到谷歌播放的应用上的代码可能无法正常工作或执行,就像在本地开发中一样?

编辑: 我的build.gradle文件:

android {
    compileSdkVersion 22
    buildToolsVersion "22.0.1"

    defaultConfig {
        applicationId "com.xxx.yyy"
        minSdkVersion 10
        targetSdkVersion 22
        versionCode 8
        versionName "1.2"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

2 个答案:

答案 0 :(得分:0)

如果您遗失了签名证书,只需要发布版本,这就是您在调试时没有遇到过的原因。

要对您的Android应用进行签名,您必须生成密钥库并将其存储在应用程序中。然后,您必须在build.gradle文件中添加几行代码,告诉它您的密钥库名称是什么,以及它的密码。最终结果将如下所示:

SigningConfigs {
        release {
            storeFile file("../keystore.jks")
            storePassword "7uSFX****"
            keyAlias "app"
            keyPassword "GQ****"
        }
    }

然后在buildTypes中指定这行代码将正确签署您的应用程序。

signingConfig signingConfigs.release

请注意,最佳做法还包括调试buildType,因此在调试应用程序时,您无需对其进行签名。这样的事情(注意它缺少signingConfig signingConfigs.release):

debug {
            minifyEnabled false
            applicationIdSuffix ".debug"
            versionNameSuffix ".debug"
}

请按照指南为您的应用生成密钥库,这相对容易。我将编辑这篇文章,详细介绍如何在以后这样做。

http://developer.android.com/tools/publishing/app-signing.html

答案 1 :(得分:0)

在做了一些测试后,我确定我收到该错误的原因是因为我在最初发布我的应用程序时没有在Android Studio中更新我的SDK和其他更新。因为在我更新之后,我将我的代码恢复到崩溃时的状态,一切正常 - 这很有意义,因为我真的没有理由认为我的代码没有工作。

所以,这里的教训是确保在签署应用发布版本之前,所有SDK工具和Android Studio都是最新的!