how to avoid lag when displaying a large String? - android

In one of my apps I have to display the Terms of Service which is a kind of a huge HTML-formatted String. Displaying this String in a TextView causes a noticeable lag on mid- to high-end devices (Nexus 5, SGS 4) and an up to 4 seconds freeze on low-end devices.
I managed to reduce the frame drop by about 60% with help of a simple task to move HTML parsing off the main Thread:
public class FromHTMLTask extends AsyncTask<String, Void, Spanned> {
private OnHTMLParseCompleteListener listener;
public FromHTMLTask(OnHTMLParseCompleteListener listener) {
this.listener = listener;
}
public interface OnHTMLParseCompleteListener {
void onHtmlParsed(Spanned text);
}
#Override
protected Spanned doInBackground(String... params) {
return Html.fromHtml(params[0]);
}
#Override
protected void onPostExecute(Spanned result) {
super.onPostExecute(result);
if (listener != null)
listener.onHtmlParsed(result);
}
}
However, the setText() method itself causes a noticeable delay when displaying the processed text and the Choreographer still reports about 35 dropped frames on low-end devices.
Since I cannot call setText() from a background Thread, is there any way to avoid this lag except of splitting the String into multiple parts and performing a lazy load?

TextView was not designed to show large texts. I suppose WebView should work much faster especially if you need to show html.
WebView webView;
String html;
webView.loadDataWithBaseURL("", html, "text/html", "UTF-8", "");

In case you insist using a TextView , you can place it inside a NestedScrollView for a better performance .
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="#+id/details"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/about"
/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

Related

Invisible Progress Bar not showing up through code

I'm making a project Android application that takes an image URL, downloads the image and displays the image. In case of an image of bigger size i want to show the user an indeterminate progress that the image is being downloading.
Java Code:
public class MainActivity extends AppCompatActivity {
ImageView downloadedImg;
ProgressBar progressBar;
Handler handler;
public void downloadImage(View view){
progressBar.setVisibility(View.VISIBLE);
ImageDownloader task = new ImageDownloader();
Bitmap myimage;
try {
myimage = task.execute("http://wallpaperswide.com/download/high_tech_earth-wallpaper-2880x1800.jpg").get();
downloadedImg.setImageBitmap(myimage);
}
catch (Exception e) {
e.printStackTrace();
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
downloadedImg = (ImageView)findViewById(R.id.imageView);
progressBar = (ProgressBar)findViewById(R.id.pbar);
handler = new Handler();
}
public class ImageDownloader extends AsyncTask<String,Void,Bitmap>{
protected void onPreExecute(){
super.onPreExecute();
//progressBar.setVisibility(View.VISIBLE);
}
#Override
protected Bitmap doInBackground(String... urls) {
try {
URL url = new URL(urls[0]);
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.connect();
InputStream inputStream = connection.getInputStream();
Bitmap mybitmap = BitmapFactory.decodeStream(inputStream);
return mybitmap;
}
catch (Exception e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
handler.post(new Runnable() {
#Override
public void run() {
progressBar.setVisibility(View.GONE);
downloadedImg.setVisibility(View.VISIBLE);
}
});
}
}
public void reset(View view){
downloadedImg.setVisibility(View.GONE);
progressBar.setVisibility(View.INVISIBLE);
}
}
XML code:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="match_parent"
tools:context="com.example.syeddanish.downloadingimages.MainActivity">
<Button
android:id="#+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:onClick="downloadImage"
android:text="Download Image" />
<Button
android:id="#+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/button"
android:onClick="reset"
android:text="Reset" />
<ProgressBar
android:id="#+id/pbar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:indeterminate="true"
android:visibility="invisible" />
<ImageView
android:id="#+id/imageView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:visibility="gone"
android:layout_below="#+id/button1" />
</RelativeLayout>
The issue i'm facing is that i want the progress bar to become visible when the download starts(i.e when "Download Image button is pressed"). I'm trying to do this in two ways i.e
By using progressbar.setVisibility(View.VISIBLE); in the start of
the onClick method of "Download Image" button.
or
By using progressbar.setVisibility(View.VISIBLE); in theonPreExecute() method of the ASyncTask but the progress bardoes not shows up using any of the above mentioned ways.
Can anyone please point out what i am doing wrong?
Does this code compile and run without NetworkOnMainThreadException?
Your problem is the usage of get()
In this part:
Bitmap myimage;
try {
myimage = task.execute("http://wallpaperswide.com/download/high_tech_earth-wallpaper-2880x1800.jpg").get();
downloadedImg.setImageBitmap(myimage);
}
you try to get an image from task.execute(...), but task.get() as per docu:
[...]Waits if necessary for the computation to complete, and then retrieves its result.[...]
So you are waiting for your "task" to execute on the main thread and blocking it, until done. Because of that, your progress
is never visible, because the UI-Thread is blocked. And once your task finishes,
the progress is set back to be invisible.
Moreover, do not reinvent the weel. Use one of the libraries available out there
for image downloading and caching.
For example: Picasso, Glide
Both also provide the functionality to use a (1) fallback and (2) loading image.
If you still like to try it on your own, then do not do the Pokémon- "gotta catch'em all" way of catching your exceptions, but instead, handle specific Exceptions that might occur and display a message to the user, send it your crash tracker, etc. Only catch exceptions that you expect to be thrown, otherwise...let it crash.
I do not see, why you should catch an exception there.
AsyncTask, Activities and memory leaks
Next is, that AsyncTasks are not tidily coupled to the Activities
life cycle. When you run your task and it executes in background, but
your activity finishes, this task will still be alive and leaks a reference to your activity. This causes the memory leaks, because the GC can't properly do it's job, to clean after you.
Make your AsyncTask at least a static class and stop/kill the task, once your activity finishes.
Multiple Tasks
Next thing, check if you already download the image, once the user clicked the button, or you're going to create multiple tasks.
So, make your ImageDownloader a member of your activity and check if it is already executing or done. (Take it out of your method and put it below the activity class head). When your activity calls onPause() or onDestroy(), kill the task with fire.
Be aware of orientation changes, too.
Android Task API instead of AsyncTask
I highly recommend to use the android task api. (com.google.android.gms.tasks)
It works very well for tasks, both running on the Main- or Workerthreads. Include continuations, provides Future like functionality and can be coupled with Activities.
References: gms Task API Doc
try put downloadedImg.setImageBitmap(myimage); inside onPostExecute(Bitmap bitmap)handler and change:
myimage = task.execute("http://wallpaperswide.com/download/high_tech_earth-wallpaper-2880x1800.jpg").get();
to:
task.execute("http://wallpaperswide.com/download/high_tech_earth-wallpaper-2880x1800.jpg");
also put Bitmap myimage; as global variable on your Asynctask class and change:
Bitmap mybitmap = BitmapFactory.decodeStream(inputStream);
return mybitmap;
to:
myimage= BitmapFactory.decodeStream(inputStream);
on doInBackground
Consider using pBar.setAlpha(1f);
It helped when I'm stuck on a similar problem.

setAnchorView not working with Recycler view

I am trying to build a music player.
I did everything what is required and the working is smooth but however i can't seem to see my MediaController Controls.
Declaration:private MusicController musicController;
public class MusicController extends MediaController {
public MusicController(Context context) {
super(context);
}
#Override
public void hide() {
}
}
MainActivity.java
musicController.setMediaPlayer(this);
musicController.setAnchorView(findViewById(R.id.my_recycler_view));
musicController.setEnabled(true);
mainactivity.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:background="#FF330000"
android:layout_height="match_parent"
android:id="#+id/recycler_parent_view"
>
<android.support.v7.widget.RecyclerView
android:id="#+id/my_recycler_view"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
I have added a screenshot.
What could be the problem here?
MainActivity.java
MusicService.java
MusicController.java
MyAdaptor.java
I have included every file that might be important. Please help me look out the problem. Thank You.
I see musicController.show(0); being called only after some click events.
Should you call it also soon after having set the anchorView?
From documentation
void show (int timeout)
Show the controller on screen. It will go away automatically after 'timeout' milliseconds of inactivity.
Try just musicController.show();
EDIT
I saw you overrode the MusicController.hide() method so it shouldn't be removed after the timeout period, but still it seems to me you don't show it when you set the anchorView
UPDATE
musicController.setMediaPlayer(this);
musicController.setAnchorView(findViewById(R.id.my_recycler_view));
musicController.setEnabled(true);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
musicController.show();
}
}, 5000);
I ran your code and I was able to show the controller after applying a delay. Maybe you should show it after your service is connected...not sure about this, but at least you could try this code and then improve it.

Run Method OnCallback In Dedicated Thread

TLDR; Run a method that updates the UI on a dedicated thread from a OnCallBack function that is updated every few milliseconds!!
I'm getting a live stream video using Vitamio library and displaying it on a SurfaceView This is done by the MediaPlayer of the library.
However, I need to split the screen for VR experience. If MediaPlayer had a method to display the video on 2 SurfaceViews then I would not have had such a problem. Unfortunately, it doesn't.
Therefore, I figured that the live stream video runs on the SurfaceView in first half of the screen and I display each frame image Bitmap to an ImageView which occupies the second half of the screen.
This is working but not good... The problem is that copying the image is too much work and that's why the ImageView is laggy.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="#+id/surfaceview_livestream"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginBottom="#dimen/activity_vertical_margin"
android:layout_marginTop="#dimen/activity_vertical_margin"
android:layout_weight="1" />
<ImageView
android:id="#+id/imageview_livestream"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginBottom="#dimen/activity_vertical_margin"
android:layout_marginTop="#dimen/activity_vertical_margin"
android:layout_weight="1"
android:contentDescription="#string/live_stream" />
</LinearLayout>
public class LiveStreamManager implements MediaPlayer.OnBufferingUpdate {
// some irrelevant code
public void initLiveStream() {
io.vov.vitamio.MediaPlayer mediaPlayer = new io.vov.vitamio.MediaPlayer();
mediaPlayer.setDisplay(mainActivity.getSurfaceView());
// other irrelevant code...
}
#Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
Bitmap frame = mp.getCurrentFrame();
if (frame != null) {
mainActivity.setFrame(frame);
}
}
}
public MainActivity extends Activity {
// some code...
// I want this to run on a dedicated thread
public void setFrame(Bitmap frame) {
imageviewLiveStream.setImageBitmap(frame);
}
}
try using rxJava:
Observable.interval(100, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.handlerThread())
.map(t -> {
// do rendering here (on single render thread
return 'rendered images'
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe( t -> {
// set bitmaps here
});
Rendering will be one on separate thread (create scheduler in advance ) every 10 milliseconds, then results will be processed on main thread.

Android MultiThreading with TextWatcher

I'm fairly new to Android development and am currently working on an app that allows people to type text into a box and the text is displayed on a Bitmap on screen.
I have a text box in the activity that listens for changes. Each time you type text it calls functions to figure out how to best format the text on the image, then updates the image with the new text. It works fine for just a little bit of text, but when there's a lot, the calculations become slower and the device begins to freeze up, making typing nearly impossible.
Is there a way I can run the calculations in the background so that typing in the box is unaffected by the image updating? I'm thinking that using multiple threads would be useful, but don't know enough about threading to get it functioning.
Here's my code:
public class PicTextWatcher implements TextWatcher {
...
public void afterTextChanged(Editable editable) {
pic.setText(editable.toString()); //This function is relatively slow
pic.writeText(); //This function is very slow
imageView.setImageBitmap(pic.getImage()); //This doesn't need to happen every keystroke, only when the other functions have completely finished
}
}
Thanks in advance,
MrGrinst
You can use AsyncTask to do things in the background
public void afterTextChanged(final Editable editable) {
new AsyncTask<Void, Void, Void>() {
protected Void doInBackground(Void... params) {
pic.setText(editable.toString());
pic.writeText();
return null;
}
protected void onPostExecute(Void result) {
imageView.setImageBitmap(pic.getImage());
}
}.execute();
}
You have to remember that now pic.setText(editable.toString());pic.writeText(); may be called simultaneously multiple times if the user typing fast and writetext is slow.

Getting rid of the black screen

This question has probably been asked a lot many times.. Yes! I am facing the so called "black screen problem"..
My first activity is which the main activity, in which login is done, and this activity is followed by the next one which is a simple summary report of the user's account.
Now, for this report, i fetch the dataset, parse it and use it to build the table dynamically.
The villan black screen appears here!! The report, as in the table is rendered properly, but upon that screen, this black tranluscent layer keeps appearing.
I tried everything, using handlers, asynctask, translucent theme but nothing helps!! The villan still appears when my summary report loads. It goes away if i press the "Back" button, and the screen appears normal, as it is expected to be when it loads the first time.. I cant figure out what exactly is going wrong, whether, its my coding approach(dynamically generating the table) or it is an emulator problem or what.
My emulator details are as follows:
CPU:ARM
Target: Android 2.3.3
skin: WVGA800
sd card:1024M
Someone please rescue me!!
EDIT:
In my Second activity i do the following:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_summary_report);
ad = new AlertDialog.Builder(this).create();
boolean is = IsConnectedToNetwork();
if (is == true){
Handler execWS = new Handler();
execWS.post(callWS);//Fetch Data via web service and parse it
if(result != null){
Handler genUI = new Handler();
genUI.post(createUI);// generate the table based on "result". ie. in a for loop i create textviews and append it to the table layout
}
}
else{
Error = "Connection Failed. Try Again!";
}
if(Error.length() != 0){
ad.setMessage(Error);
ad.setTitle("Error..");
ad.show();
}
}
My Xml layout for the second activity..
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false"
android:gravity="center"
android:orientation="vertical">
<TableLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dip">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:contentDescription="#string/Logo"
android:src="#drawable/complogo"
android:gravity="top"/>
<TextView
android:id="#+id/lblLoginInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_horizontal"/>
</TableLayout>
<TableLayout
android:id="#+id/tblSummRep"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="4"
android:padding="5dip">
</TableLayout>
<TableLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:padding="5dip">
<TextView
android:id="#+id/AppName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="#string/AppName"
android:textColor="#ff6600" />
<TextView
android:id="#+id/AppVersion"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="#string/AppVersion"
android:textColor="#ff6600" />
<TextView
android:id="#+id/Disclaimer"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/Disclaimer"
android:textColor="#ff6600" />
</TableLayout>
</LinearLayout>
I append the textview to "tblSummRep"..
UPDATE
my asynctask class..
private class ShareWork extends AsyncTask<Void, Void, Boolean>{
ProgressDialog pDlg;
//String[][] result = null;
protected void onPreExecute(){
pDlg.setMessage("Please wait while the Report Loads...");
pDlg.show();
}
#Override
protected Boolean doInBackground(Void... params) {
boolean RetVal = false;
//my fetch code
return RetVal;
}
protected void onPostExecute(Boolean value){
if(value == true){
pDlg.setMessage("Please try again later!!");
pDlg.show();
}
else{
createUI.run();
}
}
}
I do not know what do you refer to by "black screen problem", but you state that you are fetching data (I guess from a remote server) and that happens. That sounds to me about blocking the UI thread. In which method of the activity are you fetching the data (maybe in the onCreate)? You should not perform time-consuming actions on the graphical thread, such as querying a remote server (which could take some seconds). In these cases always an AsyncTask should be used. The user should receive some feedback about what is going on in the background, so using a ProgressBar is normally recommendable (or at least a spinner).
You can take a look about how to solve a similar problem with an AsyncTask here.
Update
You have just posted your code. The problem lies (as I presumed) in this line of code:
execWS.post(callWS);//Fetch Data via web service and parse it
I guess this is a WS call. You are blocking here the UI thread. Create an AsyncTask the same way as the one of the link I provided, and upon completion of the task execute the rest of the code (display error or the dynamic table with the data).
Your AsyncTask could look like this (I have not tried it):
private class LoadTableTask extends AsyncTask<URL, Integer, Object> {
protected Object doInBackground(URL... urls) {
Handler execWS = new Handler();
execWS.post(urls[0]);//Fetch Data via web service and parse it
return result;
}
protected void onProgressUpdate(Integer... progress) {
//Not used in your case. It would be a good idea to create an undefined progress dialog in your case
}
protected void onPostExecute(Object result) {
if(result != null){
Handler genUI = new Handler();
genUI.post(createUI);// generate the table based on "result". ie. in a for loop i create textviews and append it to the table layout
}
}
}
Your onCreate() method would be replaced by:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_summary_report);
ad = new AlertDialog.Builder(this).create();
boolean is = IsConnectedToNetwork();
if (is == true){
new LoadTableTask().execute(callWS);
}
else{
Error = "Connection Failed. Try Again!";
}
if(Error.length() != 0){
ad.setMessage(Error);
ad.setTitle("Error..");
ad.show();
}
}

Categories

Resources