java—在android应用程序中实现线程时出现问题

sf6xfgos  于 2021-07-11  发布在  Java
关注(0)|答案(2)|浏览(382)

我在开发一个简单的应用程序,为了在android环境下练习线程,我得到了一个常见的错误,但我不知道为什么,可能是因为线程的行为,或者我不知道为什么。
主活动类

public class MainActivity extends AppCompatActivity {

    MiHiloLooper looper;
    ImageView iv1, iv2, iv3;
    URL url1, url2, url3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        iv1 = new ImageView(getApplicationContext());
        iv1.setImageResource(R.drawable.hilo1);
        iv2 = new ImageView(getApplicationContext());
        iv2.setImageResource(R.drawable.hilo2);
        iv3 = new ImageView(getApplicationContext());
        iv3.setImageResource(R.drawable.hilo3);

        try {
            url1 = new URL("https://www.dhresource.com/0x0/f2/albu/g8/M00/E6/A3/rBVaV15BFzGAdY2NAARdO9TdaIk347.jpg/20s-3-1500-yards-length-polyester-thread.jpg");
            url2 = new URL("https://images-na.ssl-images-amazon.com/images/I/714bmMviZEL._AC_SY450_.jpg");
            url3 = new URL("https://www.brildor.com/media/catalog/product/cache/1/image/9df78eab33525d08d6e5fb8d27136e95/h/p/hps025200.jpg");
        }catch (MalformedURLException e){
            Toast miToast = Toast.makeText(getApplicationContext(), "Se ha produido un error al cargar las imagenes", Toast.LENGTH_LONG);
            miToast.setGravity(Gravity.CENTER, 0, 0);
            miToast.show();
        }

        looper = new MiHiloLooper();
        looper.post(new Thread( new ImageLoader(url1, iv1) ));
        looper.post(new Thread( new ImageLoader(url2, iv2) ));
        looper.post(new Thread( new ImageLoader(url3, iv3) ));

        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

            // Lanzammos los hilos con el OnClick del Toolbar.

            }
        });
    }

    @Override
    protected void onDestroy() {
        looper.terminate();
        super.onDestroy();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

imageloader.class类

public class ImageLoader implements Runnable {

    ImageView iv;
    URL url;
    Button button;

    public ImageLoader(URL url, ImageView iv){
        this.iv = iv;
        this.url=url;
    }
    @Override
    public void run() {
        try {
            InputStream is = url.openStream();
            final Drawable drawable =
                    Drawable.createFromStream(is, "src");
            button.post(new Runnable() {
                @Override
                public void run() {
                    iv.setImageDrawable(drawable);
                }
            });
        } catch (IOException e) {
            Log.e("URL","Error downloading image "+
                    url.toString());
        }
    }

}

MIHILOOPER.类

public class MiHiloLooper extends Thread{

    Handler handler; //message handler
    public MiHiloLooper(){
        this.start();
    }
    @Override
    public void run(){
        try{
            Looper.prepare();
            handler = new Handler();
            Looper.loop();
        }catch(Throwable t){
            Log.e("Looper","Error: ", t);
        }
    }
    public void terminate(){
        handler.getLooper().quit();
    }
    public void post(Runnable runnable){
        handler.post(runnable);
    }
}

这是我的应用程序的结构,我得到的错误是:

2020-11-21 17:47:11.166 4078-4078/com.example.probandohilos E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.probandohilos, PID: 4078
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.probandohilos/com.example.probandohilos.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.os.Handler.post(java.lang.Runnable)' on a null object reference
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2778)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856)
        at android.app.ActivityThread.-wrap11(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
     Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.os.Handler.post(java.lang.Runnable)' on a null object reference
        at com.example.probandohilos.MiHiloLooper.post(MiHiloLooper.java:27)
        at com.example.probandohilos.MainActivity.onCreate(MainActivity.java:53)
        at android.app.Activity.performCreate(Activity.java:7009)
        at android.app.Activity.performCreate(Activity.java:7000)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1214)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2731)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856) 
        at android.app.ActivityThread.-wrap11(Unknown Source:0) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:164) 
        at android.app.ActivityThread.main(ActivityThread.java:6494) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) 

我不明白我怎么能得到一个 NullPointerException ,如果我已初始化 Looper 以及它需要的所有参数。
如果你告诉我哪里错了,提前谢谢!

pkmbmrz7

pkmbmrz71#

原因是 mHandler 为空,因为 MiHiloLooper 线程与发布同步创建 Runnable/messages 使用its post 方法。
所以,当你试图 looper.post() 在线程创建结束之前(即 run() 结束),然后你将面对这个npe。
为了便于说明,要使代码在没有npe的情况下运行,可以使用 Thread.sleep() 为了确保 MiHiloLooper 线程是在向其循环程序发布任何runnable之前创建的。我在这里等了几秒钟,以确保 looper 线程已创建并 mHandler 不为空。
同样,这是为了说明的目的,当然不是一种生产方式。

looper = new MiHiloLooper();

try {
    Thread.sleep(2000); // delay of 2 sec
} catch (InterruptedException e) {
    e.printStackTrace();
}

looper.post(new Thread( new ImageLoader(url1, iv1) ));
looper.post(new Thread( new ImageLoader(url2, iv2) ));
looper.post(new Thread( new ImageLoader(url3, iv3) ));

因此,要解决您的问题,您需要使用调用 post() 方法。

looper = new MiHiloLooper();
looper.post(new Thread( new ImageLoader(url1, iv1) ));
looper.post(new Thread( new ImageLoader(url2, iv2) ));
looper.post(new Thread( new ImageLoader(url3, iv3) ));

你可以用一个监听器接口来解决这个问题,只要你的线程被创建,监听器接口就会被触发。
下面是自定义线程的新代码,我将侦听器作为构造函数参数传递;并调用了它的回调 onReady() 每当处理程序初始化时,我确信 mHandler 将不再为空。

public class MiHiloLooper extends Thread{

    Handler handler; //message handler

    private CreationListener mCreationListener; // Listener for thread creation

    public interface CreationListener {
        void onReady();
    }

    public MiHiloLooper(CreationListener listener){ 
        mCreationListener = listener;
        this.start();
    }

    @Override
    public void run(){
        try{
            Looper.prepare();
            handler = new Handler();
            mCreationListener.onReady(); // Here I am sure that handler is not null
            Looper.loop();

        }catch(Throwable t){
            Log.e("Looper","Error: ", t);
        }
    }
    public void terminate(){
        handler.getLooper().quit();
    }
    public void post(Runnable runnable){
        handler.post(runnable);
    }
}

然后在你的 MainActivity 添加一个侦听器作为构造函数参数。

looper = new MiHiloLooper(new MiHiloLooper.CreationListener() {
    @Override
    public void onReady() {
        looper.post(new Thread( new ImageLoader(url1, iv1) ));
        looper.post(new Thread( new ImageLoader(url2, iv2) ));
        looper.post(new Thread( new ImageLoader(url3, iv3) ));          
    }
});

旁注:您可以添加 synchronized 给你的 post() 方法以避免提交给可运行程序的任何争用条件 MiHiloLooper 线程。

b1zrtrql

b1zrtrql2#

根本原因
在线程上调用start()时,它不会立即执行,系统需要为该线程分配资源,然后在run()方法中运行代码,在该方法中初始化处理程序。这就解释了为什么在访问处理程序时会出现npe(nullpointerexception)。
解决方案
如果你想创建一个有活套的线程,android为你提供handlerthreadapi。因此,请将代码更改为:
mihilooper.java文件

class MiHiloLooper extends HandlerThread {

    private Handler handler;

    public MiHiloLooper() {
        this("MiHiloHandlerThread");
    }

    public MiHiloLooper(String name) {
        super(name);
        start();
    }

    public Handler getHandler() {
        if (handler == null) {
            handler = new Handler(getLooper());
        }
        return handler;
    }
}

主活动.java

looper = new MiHiloLooper();
looper.getHandler().post(new ImageLoader(url1, iv1));
looper.getHandler().post(new ImageLoader(url2, iv2));
looper.getHandler().post(new ImageLoader(url3, iv3));

顺便说一下,imageloader类中有一行

button.post(new Runnable() {
    @Override
    public void run() {
        iv.setImageDrawable(drawable);
    }
});

这个 button 变量未在任何地方初始化,因此它将使应用程序崩溃。在这种情况下,可以替换为 iv 变量。

iv.post(new Runnable() {
    @Override
    public void run() {
        iv.setImageDrawable(drawable);
    }
});

最后,您不应该创建新线程并将其传递给处理程序的post()方法。
不要

looper.post(new Thread(new ImageLoader(url1, iv1)));

looper.getHandler().post(new ImageLoader(url1, iv1));

相关问题