安卓上的Jersey客户端 - 空指针异常

17

我正在尝试创建一个简单的Android客户端,用于访问我构建的Web服务,但在运行时出现了一些问题。

这是客户端代码:

public class MyClient extends AsyncTask<EditText, Integer, String> {

private EditText text;

protected String doInBackground(EditText... strings) {

    RuntimeDelegate
            .setInstance(new com.sun.ws.rs.ext.RuntimeDelegateImpl());
    WebResource wbr;
    Client client = Client.create();
    wbr = client
            .resource("http://my.ip:8080/MazeService/rest/service/hello");
    String result = wbr.queryParam("number", "10")
            .accept(MediaType.APPLICATION_JSON).get(String.class);
    text = strings[0];
    return result;
}

protected void onProgressUpdate(Integer... progress) {
    // setProgressPercent(progress[0]);
}

protected void onPostExecute(String result) {
    if (result != null)
        this.text.setText(result);
    else
        Log.d("MyApp", "No Result");
}

和服务:

@Path("/service")
public class AndroidService {

@GET
@Produces(MediaType.APPLICATION_JSON )
@Path("/hello")
public String getJSON(@QueryParam("number") String nr)
{

    String s = "Hi there! The number is: "+nr;
    return s;
}
}

我在LogCat中得到了以下堆栈跟踪:

02-18 16:26:06.446: E/AndroidRuntime(1381): FATAL EXCEPTION: AsyncTask #1
02-18 16:26:06.446: E/AndroidRuntime(1381): java.lang.RuntimeException: An error occured while executing doInBackground()
02-18 16:26:06.446: E/AndroidRuntime(1381):     at android.os.AsyncTask$3.done(AsyncTask.java:278)
02-18 16:26:06.446: E/AndroidRuntime(1381):     at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:273)
02-18 16:26:06.446: E/AndroidRuntime(1381):     at java.util.concurrent.FutureTask.setException(FutureTask.java:124)
02-18 16:26:06.446: E/AndroidRuntime(1381):     at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:307)
02-18 16:26:06.446: E/AndroidRuntime(1381):     at java.util.concurrent.FutureTask.run(FutureTask.java:137)
02-18 16:26:06.446: E/AndroidRuntime(1381):     at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:208)
02-18 16:26:06.446: E/AndroidRuntime(1381):     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
02-18 16:26:06.446: E/AndroidRuntime(1381):     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
02-18 16:26:06.446: E/AndroidRuntime(1381):     at java.lang.Thread.run(Thread.java:856)
02-18 16:26:06.446: E/AndroidRuntime(1381): Caused by: java.lang.NullPointerException
02-18 16:26:06.446: E/AndroidRuntime(1381):     at javax.ws.rs.core.MediaType.valueOf(MediaType.java:119)
02-18 16:26:06.446: E/AndroidRuntime(1381):     at com.sun.jersey.api.client.ClientResponse.getType(ClientResponse.java:615)
02-18 16:26:06.446: E/AndroidRuntime(1381):     at com.sun.jersey.api.client.ClientResponse.getEntity(ClientResponse.java:532)
02-18 16:26:06.446: E/AndroidRuntime(1381):     at com.sun.jersey.api.client.ClientResponse.getEntity(ClientResponse.java:506)
02-18 16:26:06.446: E/AndroidRuntime(1381):     at com.sun.jersey.api.client.WebResource.handle(WebResource.java:674)
02-18 16:26:06.446: E/AndroidRuntime(1381):     at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74)
02-18 16:26:06.446: E/AndroidRuntime(1381):     at com.sun.jersey.api.client.WebResource$Builder.get(WebResource.java:503)
02-18 16:26:06.446: E/AndroidRuntime(1381):     at com.maze.client.MyClient.doInBackground(MyClient.java:25)
02-18 16:26:06.446: E/AndroidRuntime(1381):     at com.maze.client.MyClient.doInBackground(MyClient.java:1)
02-18 16:26:06.446: E/AndroidRuntime(1381):     at android.os.AsyncTask$2.call(AsyncTask.java:264)
02-18 16:26:06.446: E/AndroidRuntime(1381):     at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
02-18 16:26:06.446: E/AndroidRuntime(1381):     ... 5 more

我不明白问题在哪里,因为类似的客户端在PC上可以正常工作。IP地址也没有问题,因为我可以从Android虚拟设备访问服务器。

Android版本是4.0.3 API版本15。客户端使用了Jersey 1.11。

编辑

我还尝试过删除EditText的整个部分,因为有人告诉我它没有按照应该的方式使用。然而,我仍然得到了相同的错误。我根本不明白我错在哪里。如果需要显示任何其他额外的信息,请告诉我。同时,任何想法都好。我是Android的新手,可能犯了一些愚蠢的错误。


你的问题是一个空指针,与String result = wbr.queryParam("number", "10").accept(MediaType.APPLICATION_JSON).get(String.class);有关。 - Blundell
@Blundell 是的,我也觉得这可能是问题所在,但我不明白为什么会出现这个问题,因为包含那段代码的简单程序运行得很完美。 - Dragos
@L7ColWinters 我不太明白我应该怎么做。请提供一些代码。 - Dragos
2个回答

19

保罗的建议是正确的。我对这个主题进行了更深入的研究,并找到了一个原因。

Android apk 打包工具(sdklib.jar 中的 APKBuilder 类)在创建包时会忽略不同的文件夹(来源)。其中一个文件夹是 META-INF,无论它是在附加库中还是在项目的源文件夹中都会被忽略。

对于 Jersey 的 1.8 版本(可能也适用于其他版本,但我没有进行测试),解决方法是提供自定义实现(默认实现查找 META-INF/services/,但在 Android apk 中不存在)ServiceFinder$ServiceIteratorProvider。我的实现基于 Lucas 提出的此实现

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import android.util.Log;

import com.sun.jersey.spi.service.ServiceFinder.ServiceIteratorProvider;

public class AndroidServiceIteratorProvider<T> extends ServiceIteratorProvider<T> {

    private static final String TAG = AndroidServiceIteratorProvider.class.getSimpleName();
    private static final String MESSAGE = "Unable to load provider";

    private static final HashMap<String, String[]> SERVICES = new HashMap<String, String[]>();

    private static final String[] com_sun_jersey_spi_HeaderDelegateProvider = {
            "com.sun.jersey.core.impl.provider.header.MediaTypeProvider",
            "com.sun.jersey.core.impl.provider.header.StringProvider"
    };

    private static final String[] com_sun_jersey_spi_inject_InjectableProvider = { 
    };

    private static final String[] javax_ws_rs_ext_MessageBodyReader = {
            "com.sun.jersey.core.impl.provider.entity.StringProvider",
            "com.sun.jersey.core.impl.provider.entity.ReaderProvider"
    };

    private static final String[] javax_ws_rs_ext_MessageBodyWriter = {
            "com.sun.jersey.core.impl.provider.entity.StringProvider",
            "com.sun.jersey.core.impl.provider.entity.ReaderProvider"
    };

    static {
        SERVICES.put("com.sun.jersey.spi.HeaderDelegateProvider",
                com_sun_jersey_spi_HeaderDelegateProvider);
        SERVICES.put("com.sun.jersey.spi.inject.InjectableProvider",
                com_sun_jersey_spi_inject_InjectableProvider);
        SERVICES.put("javax.ws.rs.ext.MessageBodyReader",
                javax_ws_rs_ext_MessageBodyReader);
        SERVICES.put("javax.ws.rs.ext.MessageBodyWriter",
                javax_ws_rs_ext_MessageBodyWriter);
        SERVICES.put("jersey-client-components", new String[]{});
        SERVICES.put("com.sun.jersey.client.proxy.ViewProxyProvider", new String[]{});
    }

    @SuppressWarnings("unchecked")
    @Override
    public Iterator<Class<T>> createClassIterator(Class<T> service,
            String serviceName, ClassLoader loader,
            boolean ignoreOnClassNotFound) {

        String[] classesNames = SERVICES.get(serviceName);
        int length = classesNames.length;
        ArrayList<Class<T>> classes = new ArrayList<Class<T>>(length);
        for (int i = 0; i < length; i++) {
            try {
                classes.add((Class<T>) Class.forName(classesNames[i]));
            } catch (ClassNotFoundException e) {
                Log.v(TAG, MESSAGE,e);
            }
        }
        return classes.iterator();
    }

    @Override
    public Iterator<T> createIterator(Class<T> service, String serviceName,
            ClassLoader loader, boolean ignoreOnClassNotFound) {

        String[] classesNames = SERVICES.get(serviceName);
        int length = classesNames.length;
        ArrayList<T> classes = new ArrayList<T>(length);
            for (int i = 0; i &lt; length; i++) {
            try {
                classes.add(service.cast(Class.forName(classesNames[i])
                        .newInstance()));
            } catch (IllegalAccessException e) {
                Log.v(TAG, MESSAGE,e);
            } catch (InstantiationException e) {
                Log.v(TAG, MESSAGE,e);
            } catch (ClassNotFoundException e) {
                Log.v(TAG, MESSAGE,e);
            }
        }

        return classes.iterator();
    }
}

由于我没有使用大部分类别,而且它们还在dalvik vm上引起了验证错误,因此我删除了大部分类别...

注意:这意味着使用此实现方式,您只能使用Jersey支持的一部分标题/类型(我仅需要字符串)。要支持其他类型,您必须添加位于jersey-client.jar的META-INF / services文件夹中的提供程序(或来自Lucas的实现),并检查它们是否会在Android上引起验证错误。

以上代码应按以下方式使用:

ServiceFinder.setIteratorProvider(new AndroidServiceIteratorProvider());
Client client = Client.create();

编辑:

看起来这个问题已经在android-maven-plugin中得到了修复

mosa...@gmail.com写道:

包含修复的拉请求已合并到主分支,将会在3.2.1版本发布时发布:


2

根据以下代码:

com/sun/jersey/api/client/ClientResponse.getType()

613    public MediaType getType() {
614        String ct = getHeaders().getFirst("Content-Type");
615        return (ct != null) ? MediaType.valueOf(ct) : null;
616    }

我会检查MediaType使用的委托是否为null

javax/ws/rs/core/MediaType.valueOf(java.lang.String)

118    public static MediaType valueOf(String type) throws IllegalArgumentException {
119        return delegate.fromString(type);
120    }

看起来可能是一个客户端(Android)的delegate为空,而另一个客户端不为空。

查看这个以了解代理如何初始化的想法。它可能有助于您的调查。

javax/ws/rs/ext/RuntimeDelegate.java


1
谢谢,我会看看能做到什么。 - Dragos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接