在android开发中,listview是比较常用的一个组件,在listview的数据需要更新的时候,一般会用notifyDataSetChanged()这个函数,但是它会更新listview中所有可视范围内的item,这样对性能肯定会有影响。比较常见的情景是android应用商店中的下载列表,当我们下载一款游戏的时候,只需要更新这款游戏对应的进度就可以了。本文就来模拟android应用商店的游戏下载,实现对listview的局部刷新,只实现一个简单的demo,不去真的下载文件。
1. 首先来创建代表应用商店中的app文件的类:AppFile.java,包含了一些基本的属性,源码:
package com.alexzhou.downloadfile; /** * author:alexzhou * email :[email protected] * date :2013-1-27 * * 游戏列表中的app文件 **/ public class AppFile { public int id; public String name; // app的大小 public int size; // 已下载大小 public int downloadSize; // 下载状态:正常,正在下载,暂停,等待,已下载 public int downloadState; }
2. 由于实际开发时,AppFile的属性比较多,这里创建一个辅助类:DownloadFile.java,代表下载中的文件,源码:
package com.alexzhou.downloadfile; /** * author:alexzhou * email :[email protected] * date :2013-1-27 * * 下载的文件 **/ public class DownloadFile { public int downloadID; public int downloadSize; public int totalSize; public int downloadState; }
3. 接下来需要一个下载管理类:DownloadManager.java,它管理所有下载任务。当同时下载很多任务的时候,界面会卡,所以指定只能同时下载3个任务,每个任务会启动一个线程,这里使用了ExecutorService线程池。当提交了超过三个下载任务时,只执行前3个任务,第四个任务会等到前面有一个下载完成后再下载,以此类推。这里还用到了android提供的一个工具类SparseArray,它是用来替代HashMap的,性能比HashMap要好。下面看源码:
import java.util.ArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import android.os.Handler; import android.os.Message; import android.util.Log; import android.util.SparseArray; /** author:alexzhou email :[email protected] date :2013-1-27 下载管理 **/ public class DownloadManager { // 下载状态:正常,暂停,下载中,已下载,排队中 public static final int DOWNLOAD_STATE_NORMAL = 0x00; public static final int DOWNLOAD_STATE_PAUSE = 0x01; public static final int DOWNLOAD_STATE_DOWNLOADING = 0x02; public static final int DOWNLOAD_STATE_FINISH = 0x03; public static final int DOWNLOAD_STATE_WAITING = 0x04; // SparseArray是android中替代Hashmap的类,可以提高效率 private SparseArray<DownloadFile> downloadFiles = new SparseArray<DownloadFile>(); // 用来管理所有下载任务 private ArrayList<DownloadTask> taskList = new ArrayList<DownloadTask>(); private Handler mHandler; private final static Object syncObj = new Object(); private static DownloadManager instance; private ExecutorService executorService; private DownloadManager() { // 最多只能同时下载3个任务,其余的任务排队等待 executorService = Executors.newFixedThreadPool(3); } public static DownloadManager getInstance() { if(null == instance) { synchronized(syncObj) { instance = new DownloadManager(); } return instance; } return instance; } public void setHandler(Handler handler) { this.mHandler = handler; } // 开始下载,创建一个下载线程 public void startDownload(DownloadFile file) { downloadFiles.put(file.downloadID, file); DownloadTask task = new DownloadTask(file.downloadID); taskList.add(task); executorService.submit(task); } public void stopAllDownloadTask() { while(taskList.size() != 0) { DownloadTask task = taskList.remove(0); // 可以在这里做其他的处理 task.stopTask(); } // 会停止正在进行的任务和拒绝接受新的任务 executorService.shutdownNow(); } // 下载任务 class DownloadTask implements Runnable { private boolean isWorking = false; private int downloadId; public DownloadTask(int id) { this.isWorking = true; this.downloadId = id; } public void stopTask() { this.isWorking = false; } // 更新listview中对应的item public void update(DownloadFile downloadFile) { Message msg = mHandler.obtainMessage(); if(downloadFile.totalSize == downloadFile.downloadSize) downloadFile.downloadState = DOWNLOAD_STATE_FINISH; msg.obj = downloadFile; msg.sendToTarget(); } public void run() { // 更新下载文件的状态 DownloadFile downloadFile = downloadFiles.get(downloadId); downloadFile.downloadState = DOWNLOAD_STATE_DOWNLOADING; while(isWorking) { // 检测是否下载完成 if(downloadFile.downloadState != DOWNLOAD_STATE_DOWNLOADING) { downloadFiles.remove(downloadFile.downloadID); taskList.remove(this); isWorking = false; break; } //Log.e("", "downloadSize="+downloadFile.downloadSize+"; size="+downloadFile.totalSize); // 这里只是模拟了下载,每一秒更新一次item的下载状态 if(downloadFile.downloadSize <= downloadFile.totalSize) { this.update(downloadFile); } if(downloadFile.downloadSize < downloadFile.totalSize) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); downloadFile.downloadState = DOWNLOAD_STATE_PAUSE; this.update(downloadFile); downloadFiles.remove(downloadId); isWorking = false; break; } ++ downloadFile.downloadSize; } } } } }
4. 接下来就需要实现listview的adapter了,这里比较重要的一个函数是updateView,这是实现listview局部刷新的关键,通过索引index得到listview中对应位置的子view,然后再更新该view的数据。源码:
package com.alexzhou.downloadfile; import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Message; import android.util.Log; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; /** author:alexzhou email :[email protected] date :2013-1-27 app列表的数据适配器 **/ public class AppListAdapter extends BaseAdapter { private SparseArray<AppFile> dataList = null; private LayoutInflater inflater = null; private Context mContext; private DownloadManager downloadManager; private ListView listView; public AppListAdapter(Context context, SparseArray<AppFile> dataList) { this.inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); this.dataList = dataList; this.mContext = context; this.downloadManager = DownloadManager.getInstance(); this.downloadManager.setHandler(mHandler); } public void setListView(ListView view) { this.listView = view; } @Override public int getCount() { return dataList.size(); } @Override public Object getItem(int position) { return dataList.get(position); } @Override public long getItemId(int position) { return position; } // 改变下载按钮的样式 private void changeBtnStyle(Button btn, boolean enable) { if(enable) { btn.setBackgroundResource(R.drawable.btn_download_norm); } else { btn.setBackgroundResource(R.drawable.btn_download_disable); } btn.setEnabled(enable); } @Override public View getView(int position, View convertView, ViewGroup parent) { final ViewHolder holder; if (null == convertView) { holder = new ViewHolder(); convertView = inflater.inflate(R.layout.listitem_app, null); holder.layout = (LinearLayout) convertView .findViewById(R.id.gamelist_item_layout); holder.icon = (ImageView) convertView .findViewById(R.id.app_icon); holder.name = (TextView) convertView .findViewById(R.id.app_name); holder.size = (TextView) convertView .findViewById(R.id.app_size); holder.btn = (Button) convertView .findViewById(R.id.download_btn); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } // 这里position和app.id的值是相等的 final AppFile app = dataList.get(position); //Log.e("", "id="+app.id+", name="+app.name); holder.name.setText(app.name); holder.size.setText((app.downloadSize * 100.0f / app.size) + "%"); Drawable drawable = mContext.getResources().getDrawable(R.drawable.app_icon); holder.icon.setImageDrawable(drawable); switch(app.downloadState) { case DownloadManager.DOWNLOAD_STATE_NORMAL: holder.btn.setText("下载"); this.changeBtnStyle(holder.btn, true); break; case DownloadManager.DOWNLOAD_STATE_DOWNLOADING: holder.btn.setText("下载中"); this.changeBtnStyle(holder.btn, false); break; case DownloadManager.DOWNLOAD_STATE_FINISH: holder.btn.setText("已下载"); this.changeBtnStyle(holder.btn, false); break; case DownloadManager.DOWNLOAD_STATE_WAITING: holder.btn.setText("排队中"); this.changeBtnStyle(holder.btn, false); break; } holder.btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { DownloadFile downloadFile = new DownloadFile(); downloadFile.downloadID = app.id; downloadFile.downloadState = DownloadManager.DOWNLOAD_STATE_WAITING; app.downloadState = DownloadManager.DOWNLOAD_STATE_WAITING; downloadFile.downloadSize = app.downloadSize; downloadFile.totalSize = app.size; holder.btn.setText("排队中"); changeBtnStyle(holder.btn, false); downloadManager.startDownload(downloadFile); } }); return convertView; } static class ViewHolder { LinearLayout layout; ImageView icon; TextView name; TextView size; Button btn; } private Handler mHandler = new Handler() { public void handleMessage(Message msg) { DownloadFile downloadFile = (DownloadFile)msg.obj; AppFile appFile = dataList.get(downloadFile.downloadID); appFile.downloadSize = downloadFile.downloadSize; appFile.downloadState = downloadFile.downloadState; // notifyDataSetChanged会执行getView函数,更新所有可视item的数据 //notifyDataSetChanged(); // 只更新指定item的数据,提高了性能 updateView(appFile.id); } }; // 更新指定item的数据 private void updateView(int index) { int visiblePos = listView.getFirstVisiblePosition(); int offset = index - visiblePos; //Log.e("", "index="+index+"visiblePos="+visiblePos+"offset="+offset); // 只有在可见区域才更新 if(offset < 0) return; View view = listView.getChildAt(offset); final AppFile app = dataList.get(index); ViewHolder holder = (ViewHolder)view.getTag(); //Log.e("", "id="+app.id+", name="+app.name); holder.name.setText(app.name); holder.size.setText((app.downloadSize * 100.0f / app.size) + "%"); Drawable drawable = mContext.getResources().getDrawable(R.drawable.app_icon); holder.icon.setImageDrawable(drawable); switch(app.downloadState) { case DownloadManager.DOWNLOAD_STATE_DOWNLOADING: holder.btn.setText("下载中"); this.changeBtnStyle(holder.btn, false); break; case DownloadManager.DOWNLOAD_STATE_FINISH: holder.btn.setText("已下载"); this.changeBtnStyle(holder.btn, false); break; } }
3. 接下来需要一个下载管理类:DownloadManager.java,它管理所有下载任务。当同时下载很多任务的时候,界面会卡,所以指定只能同时下载3个任务,每个任务会启动一个线程,这里使用了ExecutorService线程池。当提交了超过三个下载任务时,只执行前3个任务,第四个任务会等到前面有一个下载完成后再下载,以此类推。这里还用到了android提供的一个工具类SparseArray,它是用来替代HashMap的,性能比HashMap要好。下面看源码:
package com.alexzhou.downloadfile; import java.util.ArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import android.os.Handler; import android.os.Message; import android.util.Log; import android.util.SparseArray; /** author:alexzhou email :[email protected] date :2013-1-27 下载管理 **/ public class DownloadManager { // 下载状态:正常,暂停,下载中,已下载,排队中 public static final int DOWNLOAD_STATE_NORMAL = 0x00; public static final int DOWNLOAD_STATE_PAUSE = 0x01; public static final int DOWNLOAD_STATE_DOWNLOADING = 0x02; public static final int DOWNLOAD_STATE_FINISH = 0x03; public static final int DOWNLOAD_STATE_WAITING = 0x04; // SparseArray是android中替代Hashmap的类,可以提高效率 private SparseArray<DownloadFile> downloadFiles = new SparseArray<DownloadFile>(); // 用来管理所有下载任务 private ArrayList<DownloadTask> taskList = new ArrayList<DownloadTask>(); private Handler mHandler; private final static Object syncObj = new Object(); private static DownloadManager instance; private ExecutorService executorService; private DownloadManager() { // 最多只能同时下载3个任务,其余的任务排队等待 executorService = Executors.newFixedThreadPool(3); } public static DownloadManager getInstance() { if(null == instance) { synchronized(syncObj) { instance = new DownloadManager(); } return instance; } return instance; } public void setHandler(Handler handler) { this.mHandler = handler; } // 开始下载,创建一个下载线程 public void startDownload(DownloadFile file) { downloadFiles.put(file.downloadID, file); DownloadTask task = new DownloadTask(file.downloadID); taskList.add(task); executorService.submit(task); } public void stopAllDownloadTask() { while(taskList.size() != 0) { DownloadTask task = taskList.remove(0); // 可以在这里做其他的处理 task.stopTask(); } // 会停止正在进行的任务和拒绝接受新的任务 executorService.shutdownNow(); } // 下载任务 class DownloadTask implements Runnable { private boolean isWorking = false; private int downloadId; public DownloadTask(int id) { this.isWorking = true; this.downloadId = id; } public void stopTask() { this.isWorking = false; } // 更新listview中对应的item public void update(DownloadFile downloadFile) { Message msg = mHandler.obtainMessage(); if(downloadFile.totalSize == downloadFile.downloadSize) downloadFile.downloadState = DOWNLOAD_STATE_FINISH; msg.obj = downloadFile; msg.sendToTarget(); } public void run() { // 更新下载文件的状态 DownloadFile downloadFile = downloadFiles.get(downloadId); downloadFile.downloadState = DOWNLOAD_STATE_DOWNLOADING; while(isWorking) { // 检测是否下载完成 if(downloadFile.downloadState != DOWNLOAD_STATE_DOWNLOADING) { downloadFiles.remove(downloadFile.downloadID); taskList.remove(this); isWorking = false; break; } //Log.e("", "downloadSize="+downloadFile.downloadSize+"; size="+downloadFile.totalSize); // 这里只是模拟了下载,每一秒更新一次item的下载状态 if(downloadFile.downloadSize <= downloadFile.totalSize) { this.update(downloadFile); } if(downloadFile.downloadSize < downloadFile.totalSize) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); downloadFile.downloadState = DOWNLOAD_STATE_PAUSE; this.update(downloadFile); downloadFiles.remove(downloadId); isWorking = false; break; } ++ downloadFile.downloadSize; } } } } }
4. 接下来就需要实现listview的adapter了,这里比较重要的一个函数是updateView,这是实现listview局部刷新的关键,通过索引index得到listview中对应位置的子view,然后再更新该view的数据。源码:
package com.alexzhou.downloadfile; import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Message; import android.util.Log; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; /** author:alexzhou email :[email protected] date :2013-1-27 app列表的数据适配器 **/ public class AppListAdapter extends BaseAdapter { private SparseArray<AppFile> dataList = null; private LayoutInflater inflater = null; private Context mContext; private DownloadManager downloadManager; private ListView listView; public AppListAdapter(Context context, SparseArray<AppFile> dataList) { this.inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); this.dataList = dataList; this.mContext = context; this.downloadManager = DownloadManager.getInstance(); this.downloadManager.setHandler(mHandler); } public void setListView(ListView view) { this.listView = view; } @Override public int getCount() { return dataList.size(); } @Override public Object getItem(int position) { return dataList.get(position); } @Override public long getItemId(int position) { return position; } // 改变下载按钮的样式 private void changeBtnStyle(Button btn, boolean enable) { if(enable) { btn.setBackgroundResource(R.drawable.btn_download_norm); } else { btn.setBackgroundResource(R.drawable.btn_download_disable); } btn.setEnabled(enable); } @Override public View getView(int position, View convertView, ViewGroup parent) { final ViewHolder holder; if (null == convertView) { holder = new ViewHolder(); convertView = inflater.inflate(R.layout.listitem_app, null); holder.layout = (LinearLayout) convertView .findViewById(R.id.gamelist_item_layout); holder.icon = (ImageView) convertView .findViewById(R.id.app_icon); holder.name = (TextView) convertView .findViewById(R.id.app_name); holder.size = (TextView) convertView .findViewById(R.id.app_size); holder.btn = (Button) convertView .findViewById(R.id.download_btn); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } // 这里position和app.id的值是相等的 final AppFile app = dataList.get(position); //Log.e("", "id="+app.id+", name="+app.name); holder.name.setText(app.name); holder.size.setText((app.downloadSize * 100.0f / app.size) + "%"); Drawable drawable = mContext.getResources().getDrawable(R.drawable.app_icon); holder.icon.setImageDrawable(drawable); switch(app.downloadState) { case DownloadManager.DOWNLOAD_STATE_NORMAL: holder.btn.setText("下载"); this.changeBtnStyle(holder.btn, true); break; case DownloadManager.DOWNLOAD_STATE_DOWNLOADING: holder.btn.setText("下载中"); this.changeBtnStyle(holder.btn, false); break; case DownloadManager.DOWNLOAD_STATE_FINISH: holder.btn.setText("已下载"); this.changeBtnStyle(holder.btn, false); break; case DownloadManager.DOWNLOAD_STATE_WAITING: holder.btn.setText("排队中"); this.changeBtnStyle(holder.btn, false); break; } holder.btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { DownloadFile downloadFile = new DownloadFile(); downloadFile.downloadID = app.id; downloadFile.downloadState = DownloadManager.DOWNLOAD_STATE_WAITING; app.downloadState = DownloadManager.DOWNLOAD_STATE_WAITING; downloadFile.downloadSize = app.downloadSize; downloadFile.totalSize = app.size; holder.btn.setText("排队中"); changeBtnStyle(holder.btn, false); downloadManager.startDownload(downloadFile); } }); return convertView; } static class ViewHolder { LinearLayout layout; ImageView icon; TextView name; TextView size; Button btn; } private Handler mHandler = new Handler() { public void handleMessage(Message msg) { DownloadFile downloadFile = (DownloadFile)msg.obj; AppFile appFile = dataList.get(downloadFile.downloadID); appFile.downloadSize = downloadFile.downloadSize; appFile.downloadState = downloadFile.downloadState; // notifyDataSetChanged会执行getView函数,更新所有可视item的数据 //notifyDataSetChanged(); // 只更新指定item的数据,提高了性能 updateView(appFile.id); } }; // 更新指定item的数据 private void updateView(int index) { int visiblePos = listView.getFirstVisiblePosition(); int offset = index - visiblePos; //Log.e("", "index="+index+"visiblePos="+visiblePos+"offset="+offset); // 只有在可见区域才更新 if(offset < 0) return; View view = listView.getChildAt(offset); final AppFile app = dataList.get(index); ViewHolder holder = (ViewHolder)view.getTag(); //Log.e("", "id="+app.id+", name="+app.name); holder.name.setText(app.name); holder.size.setText((app.downloadSize * 100.0f / app.size) + "%"); Drawable drawable = mContext.getResources().getDrawable(R.drawable.app_icon); holder.icon.setImageDrawable(drawable); switch(app.downloadState) { case DownloadManager.DOWNLOAD_STATE_DOWNLOADING: holder.btn.setText("下载中"); this.changeBtnStyle(holder.btn, false); break; case DownloadManager.DOWNLOAD_STATE_FINISH: holder.btn.setText("已下载"); this.changeBtnStyle(holder.btn, false); break; } } }
布局文件listitem_app.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/gamelist_item_layout" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center_vertical" android:background="@drawable/style_listitem_background" android:paddingBottom="5dp" android:paddingTop="5dp" > <ImageView android:id="@+id/app_icon" android:layout_width="53dip" android:layout_height="53dip" android:layout_marginLeft="5dip" android:adjustViewBounds="false" android:padding="5dp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="60dp" android:layout_marginLeft="5dp" android:layout_weight="1" android:gravity="center_vertical" android:orientation="vertical" > <TextView android:id="@+id/app_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:singleLine="true" android:text="" android:textColor="#000000" android:textSize="13sp" /> <TextView android:id="@+id/app_size" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#000000" android:textSize="10sp" /> </LinearLayout> <Button android:id="@+id/download_btn" android:layout_width="55dip" android:layout_height="30dip" android:layout_marginRight="10dip" android:background="@drawable/style_btn_download" android:focusable="false" android:text="@string/download" android:textColor="#ffffffff" android:textSize="12sp" /> </LinearLayout>
listview中item样式文件style_listitem_background.xml:
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 没有焦点时的背景颜色 --> <item android:state_window_focused="false" > <shape> <gradient android:startColor="#ffffff" android:endColor="#E3E3E3" android:angle="-90" /> </shape> </item> <!-- 非触摸模式下获得焦点并单击时的背景颜色 --> <item android:state_focused="true" android:state_pressed="true" android:drawable="@drawable/bg_listview_item_selected" /> <!--触摸模式下单击时的背景颜色 --> <item android:state_focused="false" android:state_pressed="true" android:drawable="@drawable/bg_listview_item_selected" /> <!--选中时的背景颜色 --> <item android:state_selected="true" android:drawable="@drawable/bg_listview_item_selected" /> <!--获得焦点时的背景 颜色--> <item android:state_focused="true" android:drawable="@drawable/bg_listview_item_selected" /> </selector>
item中的button样式文件style_btn_download.xml:
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true" android:drawable="@drawable/btn_download_pressed" /> <item android:drawable="@drawable/btn_download_norm" /> </selector>
字符文件strings.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">AndroidDownloadFile</string> <string name="download">下载</string> </resources>
5. 最后创建MainActivity.java,源码:
package com.alexzhou.downloadfile; import android.app.Activity; import android.os.Bundle; import android.util.SparseArray; import android.widget.ListView; public class MainActivity extends Activity { private SparseArray<AppFile> appList = new SparseArray<AppFile>(); private ListView listView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initData(); initUI(); } private void initData() { for(int i =0; i<20; i++) { AppFile app = new AppFile(); app.name = "快玩游戏--" + (i+1); app.size = 100; app.id = i; app.downloadState = DownloadManager.DOWNLOAD_STATE_NORMAL; app.downloadSize = 0; appList.put(app.id, app); } } private void initUI() { listView = (ListView)this.findViewById(R.id.listview); AppListAdapter adapter = new AppListAdapter(this, appList); adapter.setListView(listView); listView.setAdapter(adapter); } @Override protected void onDestroy() { super.onDestroy(); DownloadManager.getInstance().stopAllDownloadTask(); } }
布局文件activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ListView android:id="@+id/listview" android:layout_width="fill_parent" android:layout_height="fill_parent" android:fastScrollEnabled="true" /> </LinearLayout>
到此为止,代码部分已经全部完成了,下面来看看最终效果图:
这里对比一下分别使用updateView和notifyDataSetChanged时,有什么不一样,看看打印日志:
(1)使用notifyDataSetChanged时,listview可视范围内的所有子项都更新了。
(2)使用updateView时,只更新了指定的子项。
实例源码地址:http://pan.baidu.com/share/link?shareid=229182&uk=167811495,