The
ImageView
we saw
here
displayed an image that was stored in the app’s
app/res/drawable
directory.
The
ImageView
in this app is downloaded from the web while the app is running.
The UI thread (a.k.a. the main thread) must never try to do anything that would take more than 5 seconds, such as downloading a file from the web. See Keeping Your App Responsive. To do a long job, the main thread must spawn a worker thread. But the user interface (the UI) can be accessed only by the main thread.
MainActivity.java
.
The
MainActivity
creates a
Runnable
.
When the
Runnable
is finished,
its last action is to create another
Runnable
.
The
image
is from
Wikipedia.
activity_main.xml
strings.xml
AndroidManifest.xml
uses-permission
android.permission.INTERNET
.
build.gradle
(Module: app)
strings.xml
.
<string name="url">https://upload.wikimedia.org/wikipedia/commons/thumb/8/8d/President_Barack_Obama.jpg/440px-President_Barack_Obama.jpg</string>
URL url = new URL(getString(R.string.url));
onCreate
to the following.
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); InputStream inputStream; try { URL url = new URL(getString(R.string.url)); inputStream = url.openStream(); } catch (Exception exception) { Log.e("myTag", "thread threw exception", exception); return; } Bitmap bitmap = BitmapFactory.decodeStream(inputStream); if (bitmap == null) { Log.e("myTag", "image data in downlaoded file could not be decoded"); return; } ImageView imageView = (ImageView)findViewById(R.id.imageView); imageView.setImageBitmap(bitmap); }The LogCat output shows that
openStream
would throw an
android.os.NetworkOnMainThreadException
.
ImageView
by calling
setImageBitmap
?
We will discover that the second thread is not allowed to make any change
to the UI,
not even by displaying a
Toast
.
That’s why we’re reporting errors with the
Log.e
we saw in
Text.
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Runnable runnable = new Runnable() { @Override public void run() { //This run method is executed by the second thread. InputStream inputStream; try { URL url = new URL(getString(R.string.url)); inputStream = url.openStream(); } catch (Exception exception) { Log.e("myTag", "thread threw exception", exception); return; } Bitmap bitmap = BitmapFactory.decodeStream(inputStream); if (bitmap == null) { Log.e("myTag", "image data in downlaoded file could not be decoded"); return; } ImageView imageView = (ImageView)findViewById(R.id.imageView); imageView.setImageBitmap(bitmap); } }; Thread thread = new Thread(runnable); thread.start(); }
Process: edu.nyu.scps.downloadfile, PID: 6709 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
.zip
file with the
Thread
and the outermost
Runnable
changed to anonymous temporary objects.
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new Thread(new Runnable() { @Override public void run() { //This run method is executed by the second thread. InputStream inputStream; try { URL url = new URL(getString(R.string.url)); inputStream = url.openStream(); } catch (Exception exception) { Log.e("myTag", "thread threw exception", exception); return; } final Bitmap bitmap = BitmapFactory.decodeStream(inputStream); if (bitmap == null) { Log.e("myTag", "image data in downlaoded file could not be decoded"); return; } final ImageView imageView = (ImageView)findViewById(R.id.imageView); imageView.post(new Runnable() { @Override public void runsetImageBitmap(bitmap); } }); } }).start(); }