可扩展列表视图在android操作系统中非常缓慢

可扩展列表视图在android操作系统中非常缓慢,android,android-studio,scrollbar,expandablelistview,Android,Android Studio,Scrollbar,Expandablelistview,我的可扩展列表视图滚动速度非常慢,当我单击父类别时需要一段时间,直到我看到子视图 组列表活动: public class GroupsListActivity extends Activity { String loggedUserId = Model.getInstance().getLoggedUserId(); List<String> groupsList; static ExpandableListView expandableListView;

我的可扩展
列表视图
滚动速度非常慢,当我单击父类别时需要一段时间,直到我看到
子视图

组列表活动:

public class GroupsListActivity extends Activity {

    String loggedUserId = Model.getInstance().getLoggedUserId();

    List<String> groupsList;
    static ExpandableListView expandableListView;
    HashMap<String, List<Group>> groupCategories = new HashMap<String, List<Group>>();
    static ProgressBar spinner;
    static TextView textLoading;
    ImageButton createCategoryButton;
    static Adapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Set layout for this activity
        setContentView(R.layout.expandable_list);

        // Set actionbar title
        getActionBar().show();
        getActionBar().setTitle(Html.fromHtml("<font color='#fffffff'>Groups</font>"));

        if (loggedUserId != null)
            Log.d("TAG", "My Groups for user ID: " + loggedUserId);

        // Connect between buttons to layout id
        expandableListView = (ExpandableListView) findViewById(R.id.exp_list);
        spinner = (ProgressBar) findViewById(R.id.spinner);
        createCategoryButton = (ImageButton) findViewById(R.id.createCategory);
        textLoading = (TextView) findViewById(R.id.textLoading);

        // Loading data to expandable group list asynchronously
        AsyncTask<String, String, HashMap<String, List<Group>>> task = new AsyncTask<String, String, HashMap<String, List<Group>>>() {
            @Override
            protected HashMap<String, List<Group>> doInBackground(String... params) {
                return DataProvider.getInfo();
            }

            @Override
            protected void onPostExecute(HashMap<String, List<Group>> listHashMap) {
                super.onPostExecute(listHashMap);

                // Setting adapter and creating group list
                groupCategories = listHashMap;
                groupsList = new ArrayList<String>(groupCategories.keySet());
                adapter = new Adapter(GroupsListActivity.this, groupCategories, groupsList, GroupsListActivity.this);
                expandableListView.setAdapter(adapter);

                // Hide spinner after loading
                spinner.setVisibility(View.GONE);
                textLoading.setVisibility(View.GONE);
            }
        };
        task.execute();

        // Setting listener for group click
        expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
            @Override
            public boolean onChildClick(ExpandableListView parent, View v, int parentPosition, int childPosition, long id) {
                // After selecting a group on row - open contacts list for this group
                expandableListView.setEnabled(false);
                openContactListForGroup(groupCategories.get(groupsList.get(parentPosition)).get(childPosition).getGroupID());
                return true;
            }
        });

        // Setting listener for create group click
        createCategoryButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                createCategoryButton.setEnabled(false);
                onCategoryCreate(GroupsListActivity.this, createCategoryButton);
            }
        });
    }

    @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_actionbar_groups, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_create:
                onCreate();
                return true;

            case R.id.action_search:
                onSearch();
                return true;

            case R.id.action_favorites:
                onFavorites();
                return true;

            case R.id.action_settings:
                onSettings();
                return true;

            default:
                return super.onOptionsItemSelected(item);
        }
    }

    // Menu methods
    private void onCreate() {
        Log.d("TAG", "Create button was pressed");
        Intent i = new
                Intent(getApplicationContext(),
                CreateGroupActivity.class);

        startActivity(i);
        overridePendingTransition(R.animator.slide_out_right, R.animator.slide_in_right);
    }

    private void onSearch() {
        Log.d("TAG", "Search button was pressed");
        Intent i = new
                Intent(getApplicationContext(),
                SearchActivity.class);

        startActivity(i);
        overridePendingTransition(R.animator.slide_out_right, R.animator.slide_in_right);
    }

    private void onFavorites() {
        Log.d("TAG", "Favorites button was pressed");
        Intent i = new
                Intent(getApplicationContext(),
                FavoritesListActivity.class);

        startActivity(i);
        overridePendingTransition(R.animator.slide_out_right, R.animator.slide_in_right);
    }

    private void onSettings() {
        Log.d("TAG", "Settings button was pressed");

        // Settings activity
        Intent i = new
                Intent(getApplicationContext(),
                SettingsActivity.class);

        startActivity(i);
        overridePendingTransition(R.animator.slide_out_right, R.animator.slide_in_right);
    }

    // Methods to handle action buttons
    private void onCategoryCreate(final Activity activity, final ImageButton createCategoryButton) {
        final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
        createCategoryButton.setEnabled(true);

        final String title = "Create a new category";
        String message = "Type a name for your new category";

        // Set dialog edit_text
        final EditText categoryNameTextView = new EditText(activity);
        categoryNameTextView.setHint("Type your category name");
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                LinearLayout.LayoutParams.MATCH_PARENT);
        categoryNameTextView.setLayoutParams(lp);
        builder.setView(categoryNameTextView);

        // Set dialog title and message
        if (title != null)
            builder.setTitle(Html.fromHtml("<font color='#dc1c1c'>" + title + "</font>")).setMessage(message);

        // Set dialog buttons
        builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                final String newCategoryName = categoryNameTextView.getText().toString();

                // Check if contains only spaces
                if (!(newCategoryName.trim().length() > 0))
                    Toast.makeText(activity, "Type at least 1 letter to create the category", Toast.LENGTH_LONG).show();

                    // Check if category name already exists
                else if (groupsList.contains(newCategoryName))
                    Toast.makeText(activity, newCategoryName + " already exist. Please type another category name", Toast.LENGTH_LONG).show();

                else {
                    // Create a new category in server and add user to a sample group
                    adapter.getCategoriesList().add(newCategoryName);
                    adapter.getGroupsList().put(newCategoryName, Collections.<Group>emptyList());

                    // Update adapter and show toast to user
                    GroupsListActivity.updateAdapter();
                    Toast.makeText(activity, "You created " + newCategoryName + " category", Toast.LENGTH_LONG).show();
                }
            }
        });
        builder.setNegativeButton(
                "Cancel",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                });

        builder.setIcon(R.mipmap.edit);
        builder.show();
    }

    public void openContactListForGroup(String groupId) {
        // Contacts List activity
        Intent i = new
                Intent(getApplicationContext(),
                ContactsListActivity.class);

        // Pass to details activity the logged group id and start activity
        Bundle b = new Bundle();
        b.putString("groupId", groupId);
        i.putExtras(b);

        startActivity(i);
        overridePendingTransition(R.animator.slide_out_right, R.animator.slide_in_right);
    }

    // Static methods to use from other activities
    public static void updateAdapter() {
        spinner.setVisibility(View.VISIBLE);
        adapter.notifyDataSetChanged();

        // Hide spinner after adapter finish the update
        expandableListView.post(new Runnable() {
            @Override
            public void run() {
                spinner.setVisibility(View.GONE);
            }
        });
    }

    public static void addGroupToList(String groupId) {
        Model.getInstance().getGroup(groupId, new Model.groupReturnedListener() {
            @Override
            public void addGroupToLocal(Group group) {
                // Add group to category Others in Group List Activity
                if (adapter.getGroupsList().get("Others").size() == 0) {
                    // Add group to empty list
                    List<Group> list = new LinkedList<Group>();
                    list.add(group);
                    adapter.getGroupsList().put("Others", list);
                    adapter.notifyDataSetChanged();
                } else {
                    // Add group to an existing list
                    adapter.getGroupsList().get("Others").add(group);
                    adapter.notifyDataSetChanged();
                }
            }
        });
    }

    public static void removeGroupFromList(String groupId) {
        int position = -1;

        // Get category position
        String oldCategoryName = Model.getInstance().getCategoryNameByGroupId(groupId);
        List<Group> data = adapter.getGroupsList().get(oldCategoryName);

        // Search for group position
        for (Group group : data) {
            if (group.getGroupID().equals(groupId)) {
                position = data.indexOf(group);
                break;
            }
        }

        // Groups was found
        if (position != -1) {
            data.remove(position);
            adapter.notifyDataSetChanged();
        }
    }

    public static void updateGroupFromList(Group group) {
        int position = -1;

        // Get category position
        String oldCategoryName = Model.getInstance().getCategoryNameByGroupId(group.getGroupID());
        List<Group> data = adapter.getGroupsList().get(oldCategoryName);

        // Search for group position
        for (Group groupIterator : data) {
            if (groupIterator.getGroupID().equals(group.getGroupID())) {
                position = data.indexOf(groupIterator);
                break;
            }
        }

        // Groups was found
        if (position != -1) {
            data.remove(position);
            data.add(group);
            adapter.notifyDataSetChanged();
        }
    }

    // Other methods
    @Override
    protected void onResume() {
        super.onResume();
        expandableListView.setEnabled(true);
    }

    @Override
    public void onBackPressed() {
        ExitDialog exitDialog = new ExitDialog(GroupsListActivity.this);
        exitDialog.show();
    }
}
加载图像的方法:

public void getGroupImage(final String imageName, final LoadImageListener listener) {
        AsyncTask<String, String, Bitmap> task = new AsyncTask<String, String, Bitmap>() {
            @Override
            protected Bitmap doInBackground(String... params) {
                Bitmap bmp = loadImageFromFile(imageName);              //first try to find the image on the device
                //  Bitmap bmp = null;
                if (bmp == null) {                                      //if image not found - try downloading it from parse
                    bmp = modelParse.getGroupImage(imageName);
                    if (bmp != null)
                        saveImageToFile(bmp, imageName);    //save the image locally for next time *****
                }
                Bitmap scaledBitmap = scaleDown(bmp, 200, true);
                return scaledBitmap;
            }

            @Override
            protected void onPostExecute(Bitmap result) {
                listener.onResult(result);
            }
        };
        task.execute();
    }

 private void saveImageToFile(Bitmap imageBitmap, String imageFileName) {
        FileOutputStream fos;
        OutputStream out = null;
        try {
            File dir = context.getExternalFilesDir(null);
            out = new FileOutputStream(new File(dir, imageFileName + ".jpg"));
            imageBitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private Bitmap loadImageFromFile(String fileName) {
        Bitmap bitmap = null;
        try {
            File dir = context.getExternalFilesDir(null);
            InputStream inputStream = new FileInputStream(new File(dir, fileName + ".jpg"));
            bitmap = BitmapFactory.decodeStream(inputStream);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return bitmap;
    }
public void getGroupImage(最终字符串imageName,最终加载ImageListener侦听器){
AsyncTask任务=新建AsyncTask(){
@凌驾
受保护位图doInBackground(字符串…参数){
位图bmp=loadImageFromFile(imageName);//首先尝试在设备上查找图像
//位图bmp=null;
如果(bmp==null){//如果找不到图像-请尝试从解析下载它
bmp=modelParse.getGroupImage(imageName);
如果(bmp!=null)
saveImageToFile(bmp,imageName);//在本地保存图像以备下次使用*****
}
位图缩放位图=缩放向下(bmp,200,真);
返回缩放位图;
}
@凌驾
受保护的void onPostExecute(位图结果){
onResult(result);
}
};
task.execute();
}
私有void saveImageToFile(位图图像位图、字符串图像文件名){
文件输出流;
OutputStream out=null;
试一试{
File dir=context.getExternalFilesDir(null);
out=newfileoutputstream(新文件(dir,imageFileName+“.jpg”);
imageBitmap.compress(Bitmap.CompressFormat.PNG,100,out);
out.close();
}catch(filenotfounde异常){
e、 printStackTrace();
}捕获(IOE异常){
e、 printStackTrace();
}
}
专用位图loadImageFromFile(字符串文件名){
位图=空;
试一试{
File dir=context.getExternalFilesDir(null);
InputStream InputStream=新文件InputStream(新文件(dir,fileName+“.jpg”);
位图=BitmapFactory.decodeStream(inputStream);
}catch(filenotfounde异常){
e、 printStackTrace();
}
返回位图;
}

我发现性能方面存在两个主要问题

首先,到处使用findViewById。您正在遍历整个视图树。这就是人们使用ViewHolder模式或自定义视图模式的原因。如果在应用程序的生命周期内,每个视图多次使用findViewById,那么你就错了


第二,每次用户滚动时都分配新对象。不要。使用ViewHolder或vustom视图模式,以便可以对所有滚动事件重复使用相同的OnClickListener,每行仅创建一次,并根据需要更新值。如果在getView中创建对象(convertView为null时除外),则效率非常低。

我将尝试给出有关ListView和适配器的一般(抽象)概念,这将帮助您自己找出错误的部分

适配器的全部用途是显示相应列表项的正确数据,并尽可能少地执行其他工作。在此过程中,任何与数据操作相关的操作都需要cpu周期,这将导致延迟和缓慢滚动

具体来说,android应用程序应该以每秒60帧的平滑速度运行,并且每个帧的渲染时间不应该超过16.6毫秒,以达到60FPS的速度。因此,如果您正在为CPU创建额外负载,则可能是由于帧渲染造成的,并且从这里到渲染滞后的路径很短


我想说的是——适配器中可能有一些方法在现场同步地处理数据,这会让cpu负担过重。适配器应该表示已经准备好显示的数据,并且只在正确的视图中显示它。性能问题的一个例子可能和每次对每个视图使用String.replace()方法一样简单,或者另一个糟糕的例子是同步加载图像而不是异步加载图像。

我刚才对此很感兴趣,你能提供一个有效的教程吗?。如果我做对了,我应该在第一次加载时初始化按钮(findviewbyd),然后使用我在滚动之前得到的“旧”信息??这是最基本的。与任何侦听器类相同-在第一次加载时创建它们并在scroll上重用它们(根据需要更改任何内部状态)。看我的问题,我在getView方法中添加了viewHolder,但仍然很慢。看我的问题,我在代码中添加了viewHolder,但仍然很慢。我试着在一个简单的列表视图中查看1000个对象,它正在工作。但在可扩展的列表上仍然很慢。我可以做些什么来改进(我提到了使用viewHolder中的collection来保存图像。现在我第一次从服务器加载并在本地保存,然后从文件中获取图像,但正如您所看到的,没有异步任务)现在,我使用了所有同步从模型接收数据的方法。我刚刚开始,我计划在将来使所有的方法都是异步的。你是说在我改变这些方法后,我会得到一个平滑的卷轴??因为如果我在适配器中初始化一个包含1000个对象的列表,而不从模型和服务器获取数据,它看起来很棒!!!如果你的回答是肯定的,你能告诉我是否还有其他问题吗??因为我使用了@Gabe Sechan告诉我的视图持有者,但我不知道我是否正确。您能告诉我如何使用viewHolder加载图像吗?顺便说一句,我来自阿什杜德:)啊,很高兴在这里见面:)。是的,这就是我所说的-网络调用绝对不应该发生在适配器中,它应该发生在之前,下载的内容应该在您将模型传递给适配器之前在模型中。第二,不是所有的方法都应该是异步的,但是像加载图像这样的繁重的方法应该是异步的。你能看一下吗?我添加了活动代码和加载
public void getGroupImage(final String imageName, final LoadImageListener listener) {
        AsyncTask<String, String, Bitmap> task = new AsyncTask<String, String, Bitmap>() {
            @Override
            protected Bitmap doInBackground(String... params) {
                Bitmap bmp = loadImageFromFile(imageName);              //first try to find the image on the device
                //  Bitmap bmp = null;
                if (bmp == null) {                                      //if image not found - try downloading it from parse
                    bmp = modelParse.getGroupImage(imageName);
                    if (bmp != null)
                        saveImageToFile(bmp, imageName);    //save the image locally for next time *****
                }
                Bitmap scaledBitmap = scaleDown(bmp, 200, true);
                return scaledBitmap;
            }

            @Override
            protected void onPostExecute(Bitmap result) {
                listener.onResult(result);
            }
        };
        task.execute();
    }

 private void saveImageToFile(Bitmap imageBitmap, String imageFileName) {
        FileOutputStream fos;
        OutputStream out = null;
        try {
            File dir = context.getExternalFilesDir(null);
            out = new FileOutputStream(new File(dir, imageFileName + ".jpg"));
            imageBitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private Bitmap loadImageFromFile(String fileName) {
        Bitmap bitmap = null;
        try {
            File dir = context.getExternalFilesDir(null);
            InputStream inputStream = new FileInputStream(new File(dir, fileName + ".jpg"));
            bitmap = BitmapFactory.decodeStream(inputStream);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return bitmap;
    }