5

当用户按下购买按钮时,他们会被带到安卓播放屏幕以购买应用内商品。但是当他们回击时,他们得到了关于无法启动计费服务的异常:

java.lang.RuntimeException: Unable to start service com.problemio.BillingService@405704f0 with Intent { act=com.android.vending.billing.RESPONSE_CODE cmp=com.problemio/.BillingService (has extras) }: java.lang.NullPointerException
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2387)
at android.app.ActivityThread.access$2800(ActivityThread.java:132)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1111)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:150)
at android.app.ActivityThread.main(ActivityThread.java:4293)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
at com.problemio.ExtraHelpActivity.prependLogEntry(ExtraHelpActivity.java:561)
at com.problemio.ExtraHelpActivity.logProductActivity(ExtraHelpActivity.java:569)
at com.problemio.ExtraHelpActivity.access$4(ExtraHelpActivity.java:565)
at com.problemio.ExtraHelpActivity$ExtraHelpPurchaseObserver.onRequestPurchaseResponse(ExtraHelpActivity.java:187)
at com.problemio.ResponseHandler.responseCodeReceived(ResponseHandler.java:137)
at com.problemio.BillingService$RequestPurchase.responseCodeReceived(BillingService.java:285)
at com.problemio.BillingService.checkResponseCode(BillingService.java:582)
at com.problemio.BillingService.handleCommand(BillingService.java:427)
at com.problemio.BillingService.onStart(BillingService.java:398)
at android.app.Service.onStartCommand(Service.java:428)
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2370)
... 10 more
java.lang.NullPointerException
at com.problemio.ExtraHelpActivity.prependLogEntry(ExtraHelpActivity.java:561)
at com.problemio.ExtraHelpActivity.logProductActivity(ExtraHelpActivity.java:569)
at com.problemio.ExtraHelpActivity.access$4(ExtraHelpActivity.java:565)
at com.problemio.ExtraHelpActivity$ExtraHelpPurchaseObserver.onRequestPurchaseResponse(ExtraHelpActivity.java:187)
at com.problemio.ResponseHandler.responseCodeReceived(ResponseHandler.java:137)
at com.problemio.BillingService$RequestPurchase.responseCodeReceived(BillingService.java:285)
at com.problemio.BillingService.checkResponseCode(BillingService.java:582)
at com.problemio.BillingService.handleCommand(BillingService.java:427)
at com.problemio.BillingService.onStart(BillingService.java:398)
at android.app.Service.onStartCommand(Service.java:428)
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2370)
at android.app.ActivityThread.access$2800(ActivityThread.java:132)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1111)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:150)
at android.app.ActivityThread.main(ActivityThread.java:4293)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
at dalvik.system.NativeStart.main(Native Method)

它在我的课堂上特别提到了这些行:

造成的:

java.lang.NullPointerException
at com.problemio.ExtraHelpActivity.prependLogEntry(ExtraHelpActivity.java:561)
at com.problemio.ExtraHelpActivity.logProductActivity(ExtraHelpActivity.java:569)
at com.problemio.ExtraHelpActivity.access$4(ExtraHelpActivity.java:565)
at com.problemio.ExtraHelpActivity$ExtraHelpPurchaseObserver.onRequestPurchaseResponse(ExtraHelpActivity.java:187)

在第 561 行我有这个代码:

private void prependLogEntry(CharSequence cs) {
    SpannableStringBuilder contents = new SpannableStringBuilder(cs);
    contents.append('\n');
    contents.append(mLogTextView.getText());   // Line 561
    mLogTextView.setText(contents);
}

在第 569 行,我有以下代码:

private void logProductActivity(String product, String activity) {
    SpannableStringBuilder contents = new SpannableStringBuilder();
    contents.append(Html.fromHtml("<b>" + product + "</b>: "));
    contents.append(activity);
    prependLogEntry(contents); // Line 569
}

顺便问一下,这是哪个日志?真的需要吗?也许值得注释掉这些方法?

在任何情况下,错误指向的另一行是 187,这里是代码:

@Override
public void onRequestPurchaseResponse(RequestPurchase request,
        ResponseCode responseCode) {
    if (Consts.DEBUG) {
        Log.d(TAG, request.mProductId + ": " + responseCode);
    }
    if (responseCode == ResponseCode.RESULT_OK) {
        if (Consts.DEBUG) {
            Log.i(TAG, "purchase was successfully sent to server");
        }
        logProductActivity(request.mProductId, "sending purchase request");
    } else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
        if (Consts.DEBUG) {
            Log.i(TAG, "user canceled purchase");
        }
        logProductActivity(request.mProductId, "dismissed purchase dialog");  
        // This is line 187 right above this comment.
    } else {
        if (Consts.DEBUG) {
            Log.i(TAG, "purchase failed");
        }
        logProductActivity(request.mProductId, "request purchase returned " + responseCode);
    }
}

所以你看,它在这个日志记录的事情上一直有问题。这是什么日志?我在哪里可以看到它?我怎样才能修复它以使其不会出错?

我不确定,但也许更大的问题是请求为空。为什么这里的请求可能为空?

这是整个 ExtraHelpActivity 类:

public class ExtraHelpActivity extends BaseActivity implements ServiceConnection 
{
    // , OnClickListener 
    private static final String TAG = "Pay";

    String issueProductIdWebMarketing = "1";    
    String issueProductIdDonate = "2";  
    String issueProductIdPsych = "3";   

    /**
    * The developer payload that is sent with subsequent
    * purchase requests.
    */
    private String payloadContents = null;

    /**
     * Used for storing the log text.
     */
    private static final String LOG_TEXT_KEY = "DUNGEONS_LOG_TEXT";

    /**
     * The SharedPreferences key for recording whether we initialized the
     * database.  If false, then we perform a RestoreTransactions request
     * to get all the purchases for this user.
     */
    private static final String DB_INITIALIZED = "db_initialized";

    private ExtraHelpPurchaseObserver mExtraHelpPurchaseObserver;
    private Handler mHandler;
    private Handler handler;


    private BillingService mBillingService;
    private Button mBuyButton;
    private Button mEditPayloadButton;
    private Button mEditSubscriptionsButton;
    private TextView mLogTextView;
    private Spinner mSelectItemSpinner;
    private ListView mOwnedItemsTable;
    private SimpleCursorAdapter mOwnedItemsAdapter;
    private PurchaseDatabase mPurchaseDatabase;
    private Cursor mOwnedItemsCursor;
    private Set<String> mOwnedItems = new HashSet<String>();

    /**
     * The developer payload that is sent with subsequent
     * purchase requests.
     */
    private String mPayloadContents = null;

    private static final int DIALOG_CANNOT_CONNECT_ID = 1;
    private static final int DIALOG_BILLING_NOT_SUPPORTED_ID = 2;
    private static final int DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID = 3;

    /**
     * Each product in the catalog can be MANAGED, UNMANAGED, or SUBSCRIPTION.  MANAGED
     * means that the product can be purchased only once per user (such as a new
     * level in a game). The purchase is remembered by Android Market and
     * can be restored if this application is uninstalled and then
     * re-installed. UNMANAGED is used for products that can be used up and
     * purchased multiple times (such as poker chips). It is up to the
     * application to keep track of UNMANAGED products for the user.
     * SUBSCRIPTION is just like MANAGED except that the user gets charged monthly
     * or yearly.
     */
    private enum Managed { MANAGED, UNMANAGED, SUBSCRIPTION }

    /**
     * A {@link PurchaseObserver} is used to get callbacks when Android Market sends
     * messages to this application so that we can update the UI.
     */
    private class ExtraHelpPurchaseObserver extends PurchaseObserver {
        public ExtraHelpPurchaseObserver(Handler handler) {
            super(ExtraHelpActivity.this, handler);
        }

        @Override
        public void onBillingSupported(boolean supported, String type) {
            if (Consts.DEBUG) {
                Log.i(TAG, "supported: " + supported);
            }
            if (type == null || type.equals(Consts.ITEM_TYPE_INAPP)) {
                if (supported) {
                    restoreDatabase();
                    mBuyButton.setEnabled(true);
                    mEditPayloadButton.setEnabled(true);
                } else {
                    showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
                }
            } else if (type.equals(Consts.ITEM_TYPE_SUBSCRIPTION)) {
                mCatalogAdapter.setSubscriptionsSupported(supported);
            } else {
                showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID);
            }
        }

        @Override
        public void onPurchaseStateChange(PurchaseState purchaseState, String itemId,
                int quantity, long purchaseTime, String developerPayload) {
            if (Consts.DEBUG) {
                Log.i(TAG, "onPurchaseStateChange() itemId: " + itemId + " " + purchaseState);
            }

            if (developerPayload == null) {
                logProductActivity(itemId, purchaseState.toString());
            } else {
                logProductActivity(itemId, purchaseState + "\n\t" + developerPayload);
            }

            if (purchaseState == PurchaseState.PURCHASED) {
                mOwnedItems.add(itemId);

                // If this is a subscription, then enable the "Edit
                // Subscriptions" button.
                for (CatalogEntry e : CATALOG) {
                    if (e.sku.equals(itemId) &&
                            e.managed.equals(Managed.SUBSCRIPTION)) {
                        mEditSubscriptionsButton.setVisibility(View.VISIBLE);
                    }
                }
            }
            mCatalogAdapter.setOwnedItems(mOwnedItems);
            mOwnedItemsCursor.requery();
        }

        @Override
        public void onRequestPurchaseResponse(RequestPurchase request,
                ResponseCode responseCode) {
            if (Consts.DEBUG) {
                Log.d(TAG, request.mProductId + ": " + responseCode);
            }
            if (responseCode == ResponseCode.RESULT_OK) {
                if (Consts.DEBUG) {
                    Log.i(TAG, "purchase was successfully sent to server");
                }
                logProductActivity(request.mProductId, "sending purchase request");
            } else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
                if (Consts.DEBUG) {
                    Log.i(TAG, "user canceled purchase");
                }
                logProductActivity(request.mProductId, "dismissed purchase dialog");
            } else {
                if (Consts.DEBUG) {
                    Log.i(TAG, "purchase failed");
                }
                logProductActivity(request.mProductId, "request purchase returned " + responseCode);
            }
        }

        @Override
        public void onRestoreTransactionsResponse(RestoreTransactions request,
                ResponseCode responseCode) {
            if (responseCode == ResponseCode.RESULT_OK) {
                if (Consts.DEBUG) {
                    Log.d(TAG, "completed RestoreTransactions request");
                }
                // Update the shared preferences so that we don't perform
                // a RestoreTransactions again.
                SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE);
                SharedPreferences.Editor edit = prefs.edit();
                edit.putBoolean(DB_INITIALIZED, true);
                edit.commit();
            } else {
                if (Consts.DEBUG) {
                    Log.d(TAG, "RestoreTransactions error: " + responseCode);
                }
            }
        }
    }

    private static class CatalogEntry {
        public String sku;
        public int nameId;
        public Managed managed;

        public CatalogEntry(String sku, int nameId, Managed managed) {
            this.sku = sku;
            this.nameId = nameId;
            this.managed = managed;
        }
    }

    /** An array of product list entries for the products that can be purchased. */
    private static final CatalogEntry[] CATALOG = new CatalogEntry[] {
        new CatalogEntry("marketing_001", 1 , Managed.MANAGED),
        new CatalogEntry("potion_001", 2 , Managed.UNMANAGED),
        new CatalogEntry("subscription_monthly", 3,
                Managed.SUBSCRIPTION),
        new CatalogEntry("subscription_yearly", 4 ,
                Managed.SUBSCRIPTION)
    };  


    private String mItemName;
    private String mSku;
    private Managed mManagedType;
    private CatalogAdapter mCatalogAdapter; 


  //outside onCreate() Within class
//    public Handler mTransactionHandler = new Handler(){
//       public void handleMessage(android.os.Message msg) {
//           Log.i(TAG, "Transaction complete");
//           Log.i(TAG, "Transaction status: "+BillingHelper.latestPurchase.purchaseState);
//           Log.i(TAG, "Item purchased is: "+BillingHelper.latestPurchase.productId);
//
//           if(BillingHelper.latestPurchase.isPurchased()){
//               //code here which is to be performed after successful purchase
//           }
//       };
//
//    };    


    // TODO: 
    // TODO: 
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
            super.onCreate(savedInstanceState);

            setContentView(R.layout.extra_help);

            //Now setup the in-app billing</pre>
            handler = new Handler();            

            mExtraHelpPurchaseObserver = new ExtraHelpPurchaseObserver(handler); 

            mBillingService = new BillingService();

            mBillingService.setContext(this);
            mPurchaseDatabase = new PurchaseDatabase(this);

            ResponseHandler.register(mExtraHelpPurchaseObserver);

            //Check if billing is supported. (Optional)
            //boolean check = mBillingService.checkBillingSupported();




//              startService(new Intent(mContext, BillingService.class));
//              BillingHelper.setCompletedHandler(mTransactionHandler);             

            // Make sure the user is logged in
            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences( ExtraHelpActivity.this);
            final String user_id = prefs.getString( "user_id" , null );        

            Button donate = (Button)findViewById(R.id.donate); 
            donate.setOnClickListener(new Button.OnClickListener() 
            {  
               public void onClick(View v) 
               {

                   // Send me an email that a comment was submitted on a question. 

//                    boolean val = mBillingService.requestPurchase(
//                            "android.test.purchased", payloadContents);
                  //Replace "android.test.purchased" with your product ID that you added to Google Play or make it a variable that is populated from a list.
                  //Place this code in a button event or where ever it fits in your process flow.

                  if (mBillingService.requestPurchase(issueProductIdDonate, Consts.ITEM_TYPE_INAPP ,  null)) 
                  {

                  } 
                  else 
                  {

                        Log.i("tag", "Can't purchase on this device");

                  }
               }
            });                 


            try 
            {
                  boolean bindResult = bindService(
                    new Intent("com.android.vending.billing.MarketBillingService.BIND"), 
                    this,
                    Context.BIND_AUTO_CREATE);
                  if (bindResult) 
                  {
                    Log.i( "Err" , "Service bind successful.");
                  } 
                  else 
                  {
                    Log.e( "Err", "Could not bind to the MarketBillingService.");
                  }
            } 
            catch (SecurityException e) 
            {
                  Log.e( "Err" , "Security exception: " + e);
            }           

    }




    /**
     * Save the context of the log so simple things like rotation will not
     * result in the log being cleared.
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) 
    {
        super.onSaveInstanceState(outState);
        //outState.putString(LOG_TEXT_KEY, Html.toHtml((Spanned) mLogTextView.getText()));
    }

    /**
     * Restore the contents of the log if it has previously been saved.
     */
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) 
    {
        super.onRestoreInstanceState(savedInstanceState);
        if (savedInstanceState != null) 
        {
            //mLogTextView.setText(Html.fromHtml(savedInstanceState.getString(LOG_TEXT_KEY)));
        }
    }

    @Override
    protected Dialog onCreateDialog(int id) 
    {
        switch (id) 
        {
            case DIALOG_CANNOT_CONNECT_ID:
            return createDialog(1,1);
        case DIALOG_BILLING_NOT_SUPPORTED_ID:
            return createDialog(2,2);
            case DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID:
                return createDialog(3,3);

        //          case DIALOG_CANNOT_CONNECT_ID:
//              return createDialog(R.string.cannot_connect_title,
//                      R.string.cannot_connect_message);
//          case DIALOG_BILLING_NOT_SUPPORTED_ID:
//              return createDialog(R.string.billing_not_supported_title,
//                      R.string.billing_not_supported_message);
//              case DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID:
//                  return createDialog(R.string.subscriptions_not_supported_title,
//                          R.string.subscriptions_not_supported_message);
            default:
                return null;
        }
    }

    private Dialog createDialog(int titleId, int messageId) {
        String helpUrl = replaceLanguageAndRegion("help_url");
        if (Consts.DEBUG) {
            Log.i(TAG, helpUrl);
        }
        final Uri helpUri = Uri.parse(helpUrl);

        // TODO: replace 1 with the thing its supposed to be - I think learn more url :)
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(titleId)
            .setIcon(android.R.drawable.stat_sys_warning)
            .setMessage(messageId)
            .setCancelable(false)
            .setPositiveButton(android.R.string.ok, null)
            .setNegativeButton(1, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    Intent intent = new Intent(Intent.ACTION_VIEW, helpUri);
                    startActivity(intent);
                }
            });
        return builder.create();
    }

    /**
     * Replaces the language and/or country of the device into the given string.
     * The pattern "%lang%" will be replaced by the device's language code and
     * the pattern "%region%" will be replaced with the device's country code.
     *
     * @param str the string to replace the language/country within
     * @return a string containing the local language and region codes
     */
    private String replaceLanguageAndRegion(String str) {
        // Substitute language and or region if present in string
        if (str.contains("%lang%") || str.contains("%region%")) {
            Locale locale = Locale.getDefault();
            str = str.replace("%lang%", locale.getLanguage().toLowerCase());
            str = str.replace("%region%", locale.getCountry().toLowerCase());
        }
        return str;
    }

    /**
     * Sets up the UI.
     */
    private void setupWidgets() 
    {
        mOwnedItemsCursor = mPurchaseDatabase.queryAllPurchasedItems();
        startManagingCursor(mOwnedItemsCursor);
        String[] from = new String[] { PurchaseDatabase.PURCHASED_PRODUCT_ID_COL,
                PurchaseDatabase.PURCHASED_QUANTITY_COL
        };
//        int[] to = new int[] { R.id.item_name, R.id.item_quantity };
//        mOwnedItemsAdapter = new SimpleCursorAdapter(this, R.layout.item_row,
//                mOwnedItemsCursor, from, to);
//        mOwnedItemsTable = (ListView) findViewById(R.id.owned_items);
//        mOwnedItemsTable.setAdapter(mOwnedItemsAdapter);
    }

    private void prependLogEntry(CharSequence cs) {
        SpannableStringBuilder contents = new SpannableStringBuilder(cs);
        contents.append('\n');
        contents.append(mLogTextView.getText());
        mLogTextView.setText(contents);
    }

    private void logProductActivity(String product, String activity) {
        SpannableStringBuilder contents = new SpannableStringBuilder();
        contents.append(Html.fromHtml("<b>" + product + "</b>: "));
        contents.append(activity);
        prependLogEntry(contents);
    }

    /**
     * If the database has not been initialized, we send a
     * RESTORE_TRANSACTIONS request to Android Market to get the list of purchased items
     * for this user. This happens if the application has just been installed
     * or the user wiped data. We do not want to do this on every startup, rather, we want to do
     * only when the database needs to be initialized.
     */
    private void restoreDatabase() {
        SharedPreferences prefs = getPreferences(MODE_PRIVATE);
        boolean initialized = prefs.getBoolean(DB_INITIALIZED, false);
        if (!initialized) {
            mBillingService.restoreTransactions();
            Toast.makeText(this, 3, Toast.LENGTH_LONG).show(); 
            // Used to be R.string.restoring_transactions instead of 3
        }
    }

    /**
     * Creates a background thread that reads the database and initializes the
     * set of owned items.
     */
    private void initializeOwnedItems() {
        new Thread(new Runnable() {
            public void run() {
                doInitializeOwnedItems();
            }
        }).start();
    }

    /**
     * Reads the set of purchased items from the database in a background thread
     * and then adds those items to the set of owned items in the main UI
     * thread.
     */
    private void doInitializeOwnedItems() {
        Cursor cursor = mPurchaseDatabase.queryAllPurchasedItems();
        if (cursor == null) {
            return;
        }

        final Set<String> ownedItems = new HashSet<String>();
        try {
            int productIdCol = cursor.getColumnIndexOrThrow(
                    PurchaseDatabase.PURCHASED_PRODUCT_ID_COL);
            while (cursor.moveToNext()) {
                String productId = cursor.getString(productIdCol);
                ownedItems.add(productId);
            }
        } finally {
            cursor.close();
        }

        // We will add the set of owned items in a new Runnable that runs on
        // the UI thread so that we don't need to synchronize access to
        // mOwnedItems.
        mHandler.post(new Runnable() {
            public void run() {
                mOwnedItems.addAll(ownedItems);
                mCatalogAdapter.setOwnedItems(mOwnedItems);
            }
        });
    }

    /**
     * Called when a button is pressed.
     */
    public void onClick(View v) {
        if (v == mBuyButton) {
            if (Consts.DEBUG) {
                Log.d(TAG, "buying: " + mItemName + " sku: " + mSku);
            }

            if (mManagedType != Managed.SUBSCRIPTION &&
                    !mBillingService.requestPurchase(mSku, Consts.ITEM_TYPE_INAPP, mPayloadContents)) {
                showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
            } else if (!mBillingService.requestPurchase(mSku, Consts.ITEM_TYPE_SUBSCRIPTION, mPayloadContents)) {
                // Note: mManagedType == Managed.SUBSCRIPTION
                showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID);
            }
        } else if (v == mEditPayloadButton) {
            showPayloadEditDialog();
        } else if (v == mEditSubscriptionsButton) {
            editSubscriptions();
        }
    }

    /** List subscriptions for this package in Google Play
     *
     * This allows users to unsubscribe from this apps subscriptions.
     *
     * Subscriptions are listed on the Google Play app detail page, so this
     * should only be called if subscriptions are known to be present.
     */
    private void editSubscriptions() {
        // Get current package name
        String packageName = getPackageName();
        // Open app detail in Google Play
        Intent i = new Intent(Intent.ACTION_VIEW,
                             Uri.parse("market://details?id=" + packageName));
        startActivity(i);
    }

    /**
     * Displays the dialog used to edit the payload dialog.
     */
    private void showPayloadEditDialog() 
    {
        AlertDialog.Builder dialog = new AlertDialog.Builder(this);
        final View view = View.inflate(this, R.layout.edit_payload, null);
        final TextView payloadText = (TextView) view.findViewById(R.id.payload_text);
        if (mPayloadContents != null) {
            payloadText.setText(mPayloadContents);
        }

        dialog.setView(view);
        dialog.setPositiveButton(
                R.string.edit_payload_accept,
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        mPayloadContents = payloadText.getText().toString();
                    }
                });
        dialog.setNegativeButton(
                R.string.edit_payload_clear,
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        if (dialog != null) {
                            mPayloadContents = null;
                            dialog.cancel();
                        }
                    }
                });
        dialog.setOnCancelListener(new DialogInterface.OnCancelListener(
4

3 回答 3

3

您是否在清单文件中授予权限?

<uses-permission android:name="com.android.vending.BILLING" />

于 2012-07-12T16:35:44.897 回答
3

哎呀。

mLogTextView从未初始化。

添加mLogTextView = findViewById(R.id.blaaa);

我相信你已经知道了

如果我错了,请告诉我,以便我删除这个答案(:

于 2012-07-17T05:44:21.290 回答
1

我添加了一个答案,因为我的课太长了,所以我达到了原始问题中的字符数限制。

这是 extra_help.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        >        

<include android:id="@+id/header"
         layout="@layout/header"
         android:layout_height="wrap_content"
         android:layout_width="fill_parent"/>

<ScrollView
      android:layout_width="fill_parent"
      android:layout_height="fill_parent" 
      android:padding="5px">    

<LinearLayout 

        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:padding="5px"
        >  

<TextView
    android:id="@+id/heading_1"    
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:textColor="@color/light_best_blue"
    android:text="THE APP IS FREE TO HELP AS MANY PEOPLE AS POSSIBLE, PLEASE GIVE BACK"
    />

    <Button
    android:id="@+id/donate"
    android:layout_marginTop ="10dp"        
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Donate $1.99 Since the App is Free"
    /> 

</LinearLayout>

</ScrollView>

</LinearLayout>
于 2012-07-17T06:15:56.263 回答