Android:使用 ListView 和 Service 从应用小部件开始活动

Posted

技术标签:

【中文标题】Android:使用 ListView 和 Service 从应用小部件开始活动【英文标题】:Android: Start activity from app widget with ListView and Service 【发布时间】:2014-02-07 07:44:46 【问题描述】:

我在 App Widget 中使用 ListView 来显示来自 RSS 提要的一些项目。如果单击 ListView 中的项目,我想转到我的应用程序的活动。

我现在可以显示带有单击位置的 Toast。我想去。但我不知道如何开始活动。

我需要将我的 RSSFeed 和位置放入启动活动的意图中。

我有一个 WidgetProvider.java,它在 onUpdate() 方法中调用 RemoteFetchService.java。在此服务的 OnStartCommand() 中,我启动了一个 AsyncTask 并解析我的 RSS 提要。在 OnPostExecute() 中,我向成功获取数据的 WidgetProvider.java 广播。然后在 WidgetProvider.java 中,我通过实现 RemoteViewsFactory 的 ListProvider.java 类设置了一个远程适配器。那么我可以在哪里以及如何开始我的活动?

WidgetProvider.java

public class WidgetProvider extends AppWidgetProvider 

    // String to be sent on Broadcast as soon as Data is Fetched
    // should be included on WidgetProvider manifest intent action
    // to be recognized by this WidgetProvider to receive broadcast
    public static final String DATA_FETCHED = "mypackage.RSS.DATA_FETCHED";
    public static final String EXTRA_LIST_VIEW_ROW_NUMBER = "mypackage.EXTRA_LIST_VIEW_ROW_NUMBER";

    /*
     * this method is called every 30 mins as specified on widgetinfo.xml this
     * method is also called on every phone reboot from this method nothing is
     * updated right now but instead RetmoteFetchService class is called this
     * service will fetch data,and send broadcast to WidgetProvider this
     * broadcast will be received by WidgetProvider onReceive which in turn
     * updates the widget
     */
    @Override
    public void onUpdate(Context ctxt, AppWidgetManager appWidgetManager,
            int[] appWidgetIds) 

        Log.d("Widget", "Hello WidgetProvider onUpdate");

        /*
         * int[] appWidgetIds holds ids of multiple instance of your widget
         * meaning you are placing more than one widgets on your homescreen
         */
        for (int i = 0; i < appWidgetIds.length; ++i) 

            Intent serviceIntent = new Intent(ctxt, RemoteFetchService.class);
            serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                    appWidgetIds[i]);
            ctxt.startService(serviceIntent);
        
        super.onUpdate(ctxt, appWidgetManager, appWidgetIds);
    

    /*
     * It receives the broadcast as per the action set on intent filters on
     * Manifest.xml once data is fetched from RemoteFetchService,it sends
     * broadcast and WidgetProvider notifies to change the data the data change
     * right now happens on ListProvider as it takes RemoteFetchService
     * listItemList as data
     */
    @SuppressWarnings("deprecation")
    @Override
    public void onReceive(Context context, Intent intent) 
        super.onReceive(context, intent);

        Log.d("Widget", "Hello WidgetProvider onReceive");

        //if RSS Feed was parsed in RemoteFetchService.java
        if (intent.getAction().equals(DATA_FETCHED)) 

            Log.d("Widget", "Data fetched in Widget Provider");

            int appWidgetId = intent.getIntExtra(
                    AppWidgetManager.EXTRA_APPWIDGET_ID,
                    AppWidgetManager.INVALID_APPWIDGET_ID);
            AppWidgetManager appWidgetManager = AppWidgetManager
                    .getInstance(context);

            // which layout to show on widget
            RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
                    R.layout.widget_layout);

            // RemoteViews Service needed to provide adapter for ListView
            Intent svcIntent = new Intent(context, WidgetService.class);
            // passing app widget id to that RemoteViews Service
            svcIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 
                    appWidgetId);
            // setting a unique Uri to the intent
            svcIntent.setData(Uri.parse(svcIntent
                    .toUri(Intent.URI_INTENT_SCHEME)));
            // setting adapter to listview of the widget
            remoteViews.setRemoteAdapter(appWidgetId, R.id.listViewWidget,
                    svcIntent);
            // setting an empty view in case of no data
            remoteViews.setEmptyView(R.id.listViewWidget, R.id.empty_view);

            // onclick item listview

            // This section makes it possible for items to have individualized
            // behavior.
            // It does this by setting up a pending intent template. Individuals
            // items of a collection
            // cannot set up their own pending intents. Instead, the collection
            // as a whole sets
            // up a pending intent template, and the individual items set a
            // fillInIntent
            // to create unique behavior on an item-by-item basis.
            Intent toastIntent = new Intent(context, WidgetProvider.class);
            // Set the action for the intent.
            // When the user touches a particular view, it will have the effect
            // of
            // broadcasting TOAST_ACTION.
            toastIntent.setAction(WidgetProvider.EXTRA_LIST_VIEW_ROW_NUMBER);
            toastIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                    appWidgetId);
            intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
            PendingIntent toastPendingIntent = PendingIntent.getBroadcast(
                    context, 0, toastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
            remoteViews.setPendingIntentTemplate(R.id.listViewWidget,
                    toastPendingIntent);

            appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
        
        //if item on list was clicked
        if (intent.getAction().equals(EXTRA_LIST_VIEW_ROW_NUMBER)) 
            int appWidgetId = intent.getIntExtra(
                    AppWidgetManager.EXTRA_APPWIDGET_ID,
                    AppWidgetManager.INVALID_APPWIDGET_ID);

            AppWidgetManager appWidgetManager = AppWidgetManager
                    .getInstance(context);

            //get position on listview which was clicked
            int viewIndex = intent.getIntExtra(EXTRA_LIST_VIEW_ROW_NUMBER, 0);

            //get RSSFeed
            RSSFeed feed = ListItem.Feed;

            Toast.makeText(context, "Clicked on position :" + viewIndex,
                    Toast.LENGTH_SHORT).show();
         else 
            Log.d("Widget", "No Data fetched in Widget Provider");
        

    

RemoteFetchService.java

public class RemoteFetchService extends Service 

    private int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
    RSSFeed feed;
    public Date pDate;

    public static ArrayList<ListItem> listItemList;

    @Override
    public IBinder onBind(Intent arg0) 
        return null;
    

    /*
     * Retrieve appwidget id from intent it is needed to update widget later
     * start Async Task
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) 

        Log.d("Widget", "Hello RemoteFetchService onStartCommand");

        if (intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID))
            appWidgetId = intent.getIntExtra(
                    AppWidgetManager.EXTRA_APPWIDGET_ID,
                    AppWidgetManager.INVALID_APPWIDGET_ID);

        //fetchDataFromWeb();
        new AsyncLoadXMLFeed().execute();

        return super.onStartCommand(intent, flags, startId);
    

    /**
     * AsyncTask which parses the xml and post it
     **/
    public class AsyncLoadXMLFeed extends AsyncTask<Void, Void, RSSFeed> 

        protected RSSFeed doInBackground(Void... params) 
            try 

                Log.d("Widget", "Starte Parsing von URL in Widget");
                // Obtain feed
                DOMParser myParser = new DOMParser();
                feed = myParser.parseXml("http://www.test.de/feed");
                Log.d("Widget","ItemCount Parser in Widget: "+feed.getItemCount());

                return feed;

             catch (Exception e) 
                Log.d("Widget","Exception beim Parsen: "+e.toString());
                return null;
            
        

        @Override
        protected void onPostExecute(RSSFeed parsed_feed) 
            // super.onPostExecute(result);
            Log.d("Widget", "Async Parse fertig");
            listItemList = new ArrayList<ListItem>();
            if(feed!=null)
                try 
                    int length = feed.getItemCount();
                    for (int i = 0; i < length; i++) 

                        String date = calc_date_difference(feed, i);

                        ListItem listItem = new ListItem();
                        listItem.Feed = feed;
                        listItem.heading = feed.getItem(i).getTitle();
                        listItem.pubDate = date;
                        listItem.linkUrl = feed.getItem(i).getLink();
                        Log.d("Widget Heading", feed.getItem(i).getTitle());
                        Log.d("Widget PubDate", date);
                        Log.d("Widget linkUrl", feed.getItem(i).getLink());
                        listItemList.add(listItem);
                    

                 catch (Exception e) 
                    Log.d("Widget","Exception onPostExecute: "+e.toString());
                
            else
                Log.d("Widget", "Feed in onPostExecute ist null");
            

            //start intent and broadcast WidgetProvider, that data is fetched
            Intent widgetUpdateIntent = new Intent();
            widgetUpdateIntent.setAction(WidgetProvider.DATA_FETCHED);
            widgetUpdateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                    appWidgetId);
            sendBroadcast(widgetUpdateIntent);

            //stop service
            stopSelf();
        
    

    /**
     * Method to calculate the time difference
     **/
    public String calc_date_difference (RSSFeed feed, int pos)
        // calculate the time difference to the actual system time
        String pubDate = feed.getItem(pos).getDate();
        SimpleDateFormat df = new SimpleDateFormat(
                "EEE, dd MMM yyyy HH:mm:ss",Locale.ENGLISH);
        try 
            try 
                pDate = df.parse(pubDate);
             catch (java.text.ParseException e) 
                // TODO Auto-generated catch block
                e.printStackTrace();
            
            pubDate = "Vor "
                    + DateUtils.getDateDifference(pDate);
            return pubDate;
         catch (ParseException e) 
            Log.e("DATE PARSING", "Error parsing date..");
            return null;
        
    


WidgetService.java

/**
 * 
 * Just consider this class as the class which tells the ListView of appwidget
 * to take what type of data. By data meaning what ListViewsFactory. To make it
 * more simple,if you have done ListView population,this class defines the
 * Adapter for the ListView. Let us name ListViewsService as WidgetService.java
 * 
 **/
public class WidgetService extends RemoteViewsService 
    /*
     * So pretty simple just defining the Adapter of the listview here Adapter
     * is ListViewsFactory
     */

    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) 
        int appWidgetId = intent.getIntExtra(
        AppWidgetManager.EXTRA_APPWIDGET_ID,
        AppWidgetManager.INVALID_APPWIDGET_ID);
        return (new ListProvider(this.getApplicationContext(), intent));
    

ListItem.java

public class ListItem 
    public String heading,pubDate,linkUrl;
    public static RSSFeed Feed;


ListProvider.java

/**
 * If you are familiar with Adapter of ListView,this is the same as adapter with
 * few changes
 * 
 */
public class ListProvider implements RemoteViewsFactory 

    private RemoteViews views;
    private Context ctxt = null;
    private int appWidgetId;
    private ArrayList<ListItem> listItemList = new ArrayList<ListItem>();

    public ListProvider(Context ctxt, Intent intent) 
        this.ctxt = ctxt;
        appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID);

        if (RemoteFetchService.listItemList != null)
            listItemList = (ArrayList<ListItem>) RemoteFetchService.listItemList
                    .clone();
        else
            listItemList = new ArrayList<ListItem>();
    

    @Override
    public void onCreate() 
        // no-op
        Log.d("Widget", "Hello ListProvider onCreate");
    

    @Override
    public void onDestroy() 
        // no-op
    

    @Override
    public int getCount() 
        return listItemList.size();
    

    /*
     * Similar to getView of Adapter where instead of Viewwe return RemoteViews
     */
    @Override
    public RemoteViews getViewAt(int position) 

        final RemoteViews remoteView = new RemoteViews(ctxt.getPackageName(),
                R.layout.row);
        ListItem listItem = listItemList.get(position);
        remoteView.setTextViewText(R.id.heading, listItem.heading);
        remoteView.setTextViewText(R.id.pubDate, listItem.pubDate);

        //onclick item listview
        Intent fillInIntent = new Intent();
        fillInIntent.putExtra(WidgetProvider.EXTRA_LIST_VIEW_ROW_NUMBER, position);
        remoteView.setOnClickFillInIntent(R.id.heading, fillInIntent);

        return remoteView;
    

    @Override
    public RemoteViews getLoadingView() 
        return (null);
    

    @Override
    public int getViewTypeCount() 
        return (1);
    

    @Override
    public long getItemId(int position) 
        return (position);
    

    @Override
    public boolean hasStableIds() 
        return (true);
    

    @Override
    public void onDataSetChanged() 
        // no-op
    

【问题讨论】:

【参考方案1】:

如果您可以正确获取提要和位置

Toast.makeText(context, "Clicked on position :" + viewIndex, Toast.LENGTH_SHORT).show();

你应该可以添加一行

context.startActivity(INTENT HERE);

您可能希望将 RSS 提要作为字符串传递,或者可能实现 Parcelable。

【讨论】:

我试过并添加了这行:Intent detailIntent = new Intent(context, ItemDetailActivity.class); detailIntent.putExtra("pos", viewIndex); detailIntent.putExtra("feed", feed); context.startActivity(detailIntent);,但我收到了由context.startActivity(detailIntent);行引起的错误Unable to start receiver WidgetProvider: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want? 我已经添加了detailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);这一行,现在它可以工作了。非常感谢您的帮助!【参考方案2】:

您需要做的是为每个列表视图项设置一个 PendingIntent。因此,当用户单击该项目时,它的待处理意图将被广播,并且活动将根据您填充待处理意图的额外数据启动。 您甚至不需要用于显示 toast 的广播接收器。只需以待定意图开始活动。 要了解如何将待处理的 Intent 模板设置为远程列表视图并为每个列表视图项填充它,请参阅以下 android 文档中的“向单个项目添加行为”部分:

https://developer.android.com/guide/topics/appwidgets/index.html

【讨论】:

以上是关于Android:使用 ListView 和 Service 从应用小部件开始活动的主要内容,如果未能解决你的问题,请参考以下文章

Android中ListView的使用步骤

android如何改变listview的每行高度

《Android开发艺术探索》之Android性能优化ListView和RecyclerView(十七)

ListView 和 DB Android

android中每行中带有添加和删除按钮的ListView

在 listview Android 1.6 中同时使用 onClickListener 和 onLongClickListener