Wednesday, February 13, 2013

Creating Ads Manger for Android - Part 2 - Finishing SBCAdManager




previous parts

 Last time we started building of our ads manager, the class that will handle for you woking with ad networks. So far we set our layout, defined some settings and started the SBCAdManager class. Today we will finish it and in next parts we will implement three ad providers - AdMob, Madvertise and Leadbolt.

Construction od SBCAdManager


 Ok, let's go for SBCAdManager constructor:

 public SBCAdManager(SBCEngine aSBCEngine, ViewGroup aLayout)
 {
  mSBCEngine = aSBCEngine;
  mLayout = aLayout;
  
  float dp = aSBCEngine.getApplicationContext().getResources().getDisplayMetrics().density;
  DisplayMetrics dm = new DisplayMetrics(); 
  aSBCEngine.getWindowManager().getDefaultDisplay().getMetrics(dm); 
  int screenWidth = dm.widthPixels; 
  
  //Log.d("AdsSystem", "density:" + dp + " screenwidth:" + screenWidth);
  
  if (screenWidth > 800 && dp <= 1.0f)
  {
   mSystems = SBCAdsConstants.systemsBig;
   mProbability = SBCAdsConstants.probabilityBig;
   mSystemsNames = SBCAdsConstants.systemsNamesBig;
  }
  else
  {
   mSystems = SBCAdsConstants.systemsSmall;
   mProbability = SBCAdsConstants.probabilitySmall;
   mSystemsNames = SBCAdsConstants.systemsNamesSmall;
  }

  mSystemStatus = new SBCAdSystemStatus[mSystems.length];
  
  for (int i = 0; i < mSystemStatus.length; i++)
   mSystemStatus[i] = new SBCAdSystemStatus();
 }

 The initialization part is little bit longer than it necessary has to be. As I last time mentioned we had to choose two "configurations" for our Deadly Abyss 2 game - one for small screens and one for large screens. In constructor you can see we are measuring the size of the display and choosing the appropriate config for it. I left the code here as you can do the same or even more select different sizes of ads from your provider based on screen resolution.
 After that I am creating array of statuses for all ad systems. Statuses were aso explained last time. Its purpouse is to keep information on which ad network failed in sending ad and how long time ago it was. These information are then used when selecting ad network and the time is used to automatically revive failed ad network after some time.

Starting and stopping the Ad Manager


 Now we created the object. Before I show the start() method here is stop() method and also stopAndDestroyAdSystem() method.

 // ------------------------------------------------------------------------
 public void stop()
 {
  Log.d("AdsSystem", "stopping AdsSystem");
  
  // prevent repeated stopping
  if (mStatus == eState.STOPPED)
  {
   Log.d("AdsSystem", "AdsSystem already stopped");
   return;
  }
  
  if (mSBCAdSystem != null)
  {
   stopAndDestroyAdSystem();
  }
  
  mStatus = eState.STOPPED;
 }
 
 // ------------------------------------------------------------------------
 public void stopAndDestroyAdSystem()
 {
  if (mSBCAdSystem != null)
  {
   mSBCAdSystem.stop();
   mSBCAdSystem.destroy();
   mSBCAdSystem = null;
  }
 }

 It first checks whether the manager itself is in running state and if yes it then checks if some ad system is running. If yas again, the runnin ad system is destroyed and then also the SBCAdManaget is put into STOPPED state.

 Start() method looks like this:

 public void start()
 {
  Log.d("AdsSystem", "starting AdsSystem");
  
  // prevent repeated starting
  if (mStatus == eState.RUNNING)
  {
   Log.d("AdsSystem", "AdsSystem already started");
   return;
  }
  
  if (mSBCAdSystem == null)
   setAdSystem(); 

  if (mSBCAdSystem != null)
   mSBCAdSystem.start();
  
  mStatus = eState.RUNNING;
 }

 First it checks whether the manager was already started and if not new system is set. As the setAdSystem may set no ad network (for example if the user is offline) the result  is checked and if not null then the selected ad system is started.


Selecting the ad system


 The setAdSystem() method is the place where several things happen:
  • first it is checked if the user is online,
  • then reviveAdSystems is called to revive dead ad system - the ones that failed to deliver ads in past time,
  • new system is chosen,
  • based on result new ad system object is created and SBCAdManager is set as object that is listening for its events (events like - user clicked ad, ad request failed, ...)
 private void setAdSystem()
 {
  if (!isOnline())
  {
   Log.d("AdsSystem", "system is off-line");
   return;
  }
 
  Log.d("AdsSystem", "selecting new ad system");
  
  reviveAdSystems();

  int newSystem = chooseAdSystem();
  if (newSystem == -1)
  {
   mSBCAdSystem = null;
   mLastSBCAdSystem = -1;
   return;
  }
  
  switch (newSystem)
  {
  case SBCAdsConstants.ADMOB:
   mSBCAdSystem = new AdMobSystem(mSBCEngine, mLayout);
   Log.d("AdsSystem", "AdMob selected");
   break;
  
  case SBCAdsConstants.MADVERTISE:
   mSBCAdSystem = new MadvertiseSystem(mSBCEngine, mLayout);
   Log.d("AdsSystem", "Madvertise selected");
   break;
  
  case SBCAdsConstants.LEADBOLT:
   mSBCAdSystem = new LeadboltSystem(mSBCEngine, mLayout);
   Log.d("AdsSystem", "Leadbolt selected");
   break;
  }
  
  mLastSBCAdSystem = newSystem;
  if (mSBCAdSystem != null)
   mSBCAdSystem.setEventListener(this);
 }

 The isOnLine() method is short helper returning true or false:

 private boolean isOnline()
 {
  ConnectivityManager cm = (ConnectivityManager) 
    mSBCEngine.getSystemService(Context.CONNECTIVITY_SERVICE);
  NetworkInfo nInfo = cm.getActiveNetworkInfo();
  return (nInfo != null && nInfo.isAvailable() && nInfo.isConnected());
 }

 More interesting are other two methods - reviveAdSystem() and chooseAdSystem(). The first one has listing like this:

 private void reviveAdSystems()
 {
  int systemsCount = mSystems.length;
  
  // revive all systems that failed before some time
  long currentTime = System.currentTimeMillis();
  for (int i = 0; i < systemsCount; i++)
  {
   SBCAdSystemStatus status = mSystemStatus[i];
   
   if (status.isFailed() && 
     currentTime - status.getFailedTime() > SBCAdsConstants.REVIVE_TIME)
   {
    Log.d("AdsSystem", "Reviving system " + mSystemsNames[i]);
    status.setFailedTime(0);
    status.setFailed(false);
   }
  }
 }

 It simply goes through statuses of all systems and if the system is marked as failed then it cheks time when the ad system fialed. If it is in past long time ago enough the system is marked as live again and so ready for selection.

 chooseAdSystem() method first gets random number from 0 to 99. It then loops through your configuration take from constant and set in constructor. Based on this it finds ad network. If the network is dead - it's status isFailed() is true. then it loops through othe ad networks starting with next one. If the loop completes and you return to first selected network it means that all networks are dead. In such  case null is returned. Otherwise index of selected live ad system is returned.

 private int chooseAdSystem()
 {
  int result = -1;
  int systemsCount = mSystems.length;
  
  int prob = mRnd.nextInt(100);
  int cumul = 0;
  for (int i = 0; i < systemsCount; i++)
  {
   cumul += mProbability[i];

   // possible system - check for last failures 
   if (prob < cumul)
   {
    // chosen system failed in last 2 minutes - choose next 
    if (mSystemStatus[i].isFailed())
    {
     int idx = i + 1;
     while (idx != i)
     {
      if (idx >= systemsCount)
       idx = 0;
      
      if (!mSystemStatus[idx].isFailed())
      {
       result = mSystems[idx];
       break;
      }
      
      ++ idx;
     }
    }
    else
    {
     result = mSystems[i]; 
    }
    
    break;
   }
  }
  
  return result;
 }


Callbacks - SBCAdEventListener


 In the end of setAdSystem() method we set listener for events comming from ad network. As the listener we set SBCAdEventListener interface that is implemented by SBCAdSystem class. The interface methods are callbacks that are called when some event in ad system occures. The implementation of them is like this:

 // ------------------------------------------------------------------------
 // CALLBACKS 
 // ------------------------------------------------------------------------
 public void onClick(SBCAdSystem aSBCAdSystem)
 {
 }

 // ------------------------------------------------------------------------
 public void onReceived(SBCAdSystem aSBCAdSystem)
 {
 }

 // ------------------------------------------------------------------------
 public void onFailed(SBCAdSystem aSBCAdSystem)
 {
  if (aSBCAdSystem == mSBCAdSystem)
  {
   if (mLastSBCAdSystem != -1)
   {
    // mark this system as failing
    mSystemStatus[mLastSBCAdSystem].setFailed(true);
    mSystemStatus[mLastSBCAdSystem].setFailedTime(System.currentTimeMillis());
   }
   changeAdSystem();
  }
 }

 As you can see, currently I have only onFailed implemented. If the ad system says that it failed I just mark it as dead. I record the time of death and try to chnge the system with changeAdSystem() method which has this listing:

 private void changeAdSystem()
 {
  long delayTime = allAdSystemsFailed() ? SBCAdsConstants.NEXT_TRY_TIME : 0;
  
  Log.d("AdsSystem", "Change Ad System in " + delayTime + " millis");
  
  mHandler.postDelayed((new Runnable()
  {
   @Override
   public void run()
   {
    Log.d("AdsSystem", "Changing ad system");
    if (mSBCAdSystem != null)
    {
     stopAndDestroyAdSystem();
    }

    setAdSystem();
    if (mSBCAdSystem != null)
     mSBCAdSystem.start();
   }
  }), delayTime);
 }

 This is kind of thin ice for me as I am not too experienced Java/Android coder. So, any improvements or suggestions are welcome. What the mehod does is that it first calculates when the system should change - immediately or if all systems are dead than after time defined in constatnts (I use 2 minutes). Next the actual ad system changing method is scheduled. It deletes any previous ad system sets new and starts it.

 For completness here is also the allAdSystemsFailed() method. As the name says it just returns true  if all systems are dead.

 private boolean allAdSystemsFailed()
 {
  boolean result = true;
  int systemsCount = mSystems.length - 1;
  
  while (systemsCount >= 0 && result)
  {
   result = (result && mSystemStatus[systemsCount].isFailed());
   -- systemsCount;
  }
  
  return result;
 }


Conclusion


 Ok, now the SBCAdSystem is finished. But for now we have no ad networks implemented to choose from yet. Next time we will start with AdMob. During reading you probably imagined thousands of enhancements - like storing the network the user clicks on ads most often. Maintain some ad network preference list on your server and ask for it during construction instead of pure random selection. Enhance the selection taht it will take into account also some priorities you assign to ad networks and so on ... I will be happy to hear about your ideas or code enhancements in comments.