Android8.1 MTK平台 增加以太网静态IP功能

简介: Android8.1 MTK平台 增加以太网静态IP功能

前言


android 源码中一般都自带DHCP上网的,静态IP上网是没有的。这就需要我们自己添加了,

因为之前搞过6.0的静态IP功能,同样是 MTK 平台的,差异还是有点大的,

对比分析修改完成了需求,特此分享一下,避免更多的人踩坑。


如果这篇文章帮到你,欢迎点赞和转发,请注明原文地址


Android8.1


先上效果图,毕竟没图你说个锤子

20190529214012856.gif


动图


20190529214455204.jpg

20190529214119317.png


20190529214138501.png

如图所示在 settings 中增加以太网的配置项


1、vendor\mediatek\proprietary\packages\apps\MtkSettings\res\xml\network_and_internet.xml


<com.android.settingslib.RestrictedPreference
    android:key="ethernet_settings"
    android:title="@string/ethernet_settings_title"
    android:summary="@string/summary_placeholder"
    android:icon="@drawable/ic_ethernet_cell"
    android:fragment="com.android.settings.ethernet.EthernetSettings"
    android:order="-17"/>

在 mobile_network_settings 和 tether_settings 之间增加如上代码

对应的 icon 资源文件是我从 SystemUI 中拷贝过来的,稍微调整了下大小,也贴给你们吧

2、

vendor\mediatek\proprietary\packages\apps\MtkSettings\res\drawable\ic_ethernet_cell.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:autoMirrored="true"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="48"
    android:viewportHeight="48"
    android:tint="?android:attr/colorControlNormal">
    <path
        android:fillColor="#fff"
        android:pathData="M15.54 13.52l-3.08-2.55L1.64 24l10.82 13.04 3.08-2.55L6.84 24l8.7-10.48zM14 26h4v-4h-4v4zm20-4h-4v4h4v-4zm-12 4h4v-4h-4v4zm13.54-15.04l-3.08 2.55L41.16 24l-8.7 10.48 3.08 2.55L46.36 24 35.54 10.96z"/>
</vector>


vendor\mediatek\proprietary\packages\apps\MtkSettings\res\values\strings.xml

<string name="ethernet_ip_settings_invalid_ip">"Please fill in the correct format."</string>
<string name="eth_ip_settings_please_complete_settings">"Network information is not complete, please fill in the complete"</string>
<string name="save_satic_ethernet">"Save"</string>
<string name="enthernet_static">"Use static settings"</string>
<string name="enthernet_ip_address">"IP address"</string>
<string name="enthernet_gateway">"gateway"</string>
<string name="enthernet_netmask">"Subnet mask"</string>
<string name="enthernet_dns1">"domain1"</string>
<string name="enthernet_dns2">"domain2"</string>
<string name="ethernet_quick_toggle_title">"Ethernet"</string>
<string name="open_ethernet">"Open Ethernet"</string>
<string name="ethernet_static_ip_settings_title">"Setting Ethernet"</string>
<string name="ethernet_settings">"Ethernet"</string>
<string name="ethernet_settings_title">"Ethernet"</string>

vendor\mediatek\proprietary\packages\apps\MtkSettings\res\values-zh-rCN\strings.xml

<string name="ethernet_ip_settings_invalid_ip">"请填写正确的格式"</string>
<string name="save_satic_ethernet">"保存"</string>
<string name="eth_ip_settings_please_complete_settings">"网络信息不完整,请填写完整"</string>
<string name="enthernet_static">"使用静态设置"</string>
<string name="enthernet_ip_address">"IP地址"</string>
<string name="enthernet_gateway">"网关"</string>
<string name="enthernet_netmask">"子网掩码"</string>
<string name="enthernet_dns1">"域名1"</string>
<string name="enthernet_dns2">"域名2"</string>
<string name="ethernet_quick_toggle_title">"以太网"</string>
<string name="open_ethernet">"打开以太网"</string>
<string name="ethernet_static_ip_settings_title">"配置以太网"</string>
<string name="ethernet_settings">"以太网"</string>
<string name="ethernet_settings_title">"以太网"</string> 


3、增加对应设置的两个布局xml

vendor\mediatek\proprietary\packages\apps\MtkSettings\res\xml\ethernet_settings.xml

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
    android:title="@string/ethernet_settings">
<SwitchPreference
    android:key="ethernet"
    android:title="@string/ethernet_quick_toggle_title"
    android:summary="@string/open_ethernet"/>
<PreferenceScreen
    android:dependency="ethernet"
    android:fragment="com.android.settings.ethernet.EthernetStaticIP"
    android:key="ethernet_static_ip"
    android:title="@string/ethernet_static_ip_settings_title" />
</PreferenceScreen>

vendor\mediatek\proprietary\packages\apps\MtkSettings\res\xml\ethernet_static_ip.xml

  <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
        android:title="@string/ethernet_static_ip_settings_title">
    <SwitchPreference
            android:key="use_static_ip"
            android:title="@string/enthernet_static"
            android:persistent="false"/>    
    <EditTextPreference
            android:dependency="use_static_ip"
            android:key="ip_address"
            android:title="@string/enthernet_ip_address"
            android:persistent="false"
            android:singleLine="true"/>    
    <EditTextPreference
            android:dependency="use_static_ip"
            android:key="gateway"
            android:title="@string/enthernet_gateway"
            android:persistent="false"
            android:singleLine="true"/>    
    <EditTextPreference
            android:dependency="use_static_ip"
            android:key="netmask"
            android:title="@string/enthernet_netmask"
            android:persistent="false"
            android:singleLine="true" />    
    <EditTextPreference
            android:dependency="use_static_ip"
            android:key="dns1"
            android:title="@string/enthernet_dns1"
            android:persistent="false"
            android:singleLine="true"/>    
    <EditTextPreference
            android:dependency="use_static_ip"
            android:key="dns2"
            android:title="@string/enthernet_dns2"
            android:persistent="false"
            android:singleLine="true"/>    
</PreferenceScreen>


4、增加对应两个布局的 java 控制类


vendor\mediatek\proprietary\packages\apps\MtkSettings\src\com\android\settings\ethernet\EthernetSettings.java


package com.android.settings.ethernet;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceGroup;
import android.support.v7.preference.PreferenceScreen;
import android.support.v7.preference.CheckBoxPreference;
import android.support.v14.preference.SwitchPreference;
import android.provider.Settings;
import android.provider.Settings.System;
import android.provider.Settings.Secure;
import android.util.Log;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.net.InetAddress;
import android.net.EthernetManager;
import android.net.StaticIpConfiguration;
import android.net.LinkAddress;
import android.net.IpConfiguration;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.R;
public class EthernetSettings extends SettingsPreferenceFragment 
    implements Preference.OnPreferenceChangeListener{
    private static final String TAG = "EthernetSettings";
    private static final String USE_ETHERNET_SETTINGS = "ethernet";
    public static final String IS_ETHERNET_OPEN = Settings.IS_ETHERNET_OPEN;  
    private SwitchPreference mUseEthernet;
    private IntentFilter mIntentFilter;
    private boolean isEthernetEnabled() {
    return Settings.System.getInt(getActivity().getContentResolver(), IS_ETHERNET_OPEN,0) == 1 ? true : false;
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.ethernet_settings);
    mUseEthernet = (SwitchPreference) findPreference(USE_ETHERNET_SETTINGS);
    mUseEthernet.setOnPreferenceChangeListener(this);
    if(isEthernetEnabled()) {
       mUseEthernet.setChecked(true);
    } else {
       mUseEthernet.setChecked(false);
    }
    File f = new File("sys/class/net/eth0/address");
    if (f.exists()) {
       mUseEthernet.setEnabled(true);   
    } else {
       mUseEthernet.setEnabled(false);
    }
    }
    @Override
    public void onResume() {
        super.onResume();
    }
     @Override
     public int getMetricsCategory(){return MetricsEvent.ETHERNET;}
     @Override
     public void onPause() {
        super.onPause();
    } 
    @Override
    public boolean onPreferenceChange(Preference preference, Object value) {
      boolean result = true;
      final String key = preference.getKey();
      if (USE_ETHERNET_SETTINGS.equals(key)) {
        Settings.System.putInt(getActivity().getContentResolver(), IS_ETHERNET_OPEN, 
          ((Boolean) value) ? 1 : 0);
      }
      return result;
    }
}

vendor\mediatek\proprietary\packages\apps\MtkSettings\src\com\android\settings\ethernet\EthernetStaticIP.java

  package com.android.settings.ethernet;
  import android.app.Dialog;
  import android.content.DialogInterface;
  import android.content.Intent;
  import android.content.ContentResolver;
  import android.os.Bundle;
  import android.support.v7.preference.Preference;
  import android.preference.PreferenceActivity;
  import android.support.v7.preference.PreferenceScreen;
  import android.support.v7.preference.CheckBoxPreference;
  import android.support.v7.preference.EditTextPreference;
  import android.support.v14.preference.SwitchPreference;
  import android.provider.Settings;
  import android.provider.Settings.Secure;
  import android.util.Log;
  import android.view.ContextMenu;
  import android.view.Menu;
  import android.view.MenuInflater;
  import android.view.MenuItem;
  import android.view.View;
  import android.view.ContextMenu.ContextMenuInfo;
  import android.widget.AdapterView;
  import android.widget.Toast;
  import android.widget.AdapterView.AdapterContextMenuInfo;
  import android.text.TextUtils;
  import java.util.Set;
  import java.util.WeakHashMap;
  import java.util.Formatter;
  import java.net.InetAddress;
  import android.net.EthernetManager;
  import android.net.StaticIpConfiguration;
  import android.net.LinkAddress;
  import android.net.IpConfiguration;
  import android.net.IpConfiguration.IpAssignment;
  import android.net.IpConfiguration.ProxySettings;
  import android.net.NetworkInfo.DetailedState;
  import android.content.BroadcastReceiver;
  import android.content.IntentFilter;
  import android.content.Context;
  import android.net.NetworkInfo;
  import android.view.KeyEvent;
  import android.view.Menu;
  import android.view.MenuItem;
  import android.app.AlertDialog;
  import com.android.settings.SettingsPreferenceFragment;
  import com.android.settings.R;
  import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
  public class EthernetStaticIP  extends SettingsPreferenceFragment 
  implements Preference.OnPreferenceChangeListener {
      private static final String TAG = "EthernetStaticIP";
      public static final boolean DEBUG = false;
      private static void LOG(String msg) {
          if ( DEBUG ) {
              Log.d(TAG, msg);
          }
      }
    /*-------------------------------------------------------*/
      private static final String KEY_USE_STATIC_IP = "use_static_ip";
      private static final String KEY_IP_ADDRESS = "ip_address";
      private static final String KEY_GATEWAY = "gateway";
      private static final String KEY_NETMASK = "netmask";
      private static final String KEY_DNS1 = "dns1";
      private static final String KEY_DNS2 = "dns2";
      public static final String ETHERNET_USE_STATIC_IP = Settings.IS_ETHERNET_STATUC_OPEN;
      private static final int MENU_ITEM_SAVE = Menu.FIRST;
      private static final int MENU_ITEM_CANCEL = Menu.FIRST + 1;
      private String[] mSettingNames = {
          Settings.ETHERNET_STATIC_IP, 
          Settings.ETHERNET_STATIC_GATEWAY,
          Settings.ETHERNET_STATIC_NETMASK,
          Settings.ETHERNET_STATIC_DNS1, 
          Settings.ETHERNET_STATIC_DNS2
      };
      private String[] mPreferenceKeys = {
          KEY_IP_ADDRESS,
          KEY_GATEWAY,
          KEY_NETMASK,
          KEY_DNS1,
          KEY_DNS2,
      };
    /*-------------------------------------------------------*/
      private SwitchPreference mUseStaticIpSwitch;
      private StaticIpConfiguration mStaticIpConfiguration;
      private IpConfiguration mIpConfiguration;
      private EthernetManager mEthernetManager;
      private boolean isOnPause = false;
      private boolean chageState = false;
      public EthernetStaticIP() {
      }
      @Override
      public void onActivityCreated(Bundle savedInstanceState){
          super.onActivityCreated(savedInstanceState);
          addPreferencesFromResource(R.xml.ethernet_static_ip);
          mUseStaticIpSwitch = (SwitchPreference)findPreference(KEY_USE_STATIC_IP);
          mUseStaticIpSwitch.setOnPreferenceChangeListener(this);
          for ( int i = 0; i < mPreferenceKeys.length; i++ ) {
              Preference preference = findPreference(mPreferenceKeys[i] );
              preference.setOnPreferenceChangeListener(this);
          }
          setHasOptionsMenu(true);
      }
      @Override
      public void onResume() {
          super.onResume();
          if(!isOnPause) {
              updateIpSettingsInfo();
          }
          isOnPause = false;
      }
      @Override
      public int getMetricsCategory(){return MetricsEvent.ETHERNET_STATIC;}    
      @Override
      public void onPause() {
          isOnPause = true;
          super.onPause();
      }
      @Override
      public void onDestroy() {
          super.onDestroy();
      }
      @Override
      public void onSaveInstanceState(Bundle outState) {
          super.onSaveInstanceState(outState);
      }
      @Override
      public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
          menu.add(Menu.NONE, MENU_ITEM_SAVE, 0, R.string.save_satic_ethernet)
                  .setEnabled(true)
                  .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
          super.onCreateOptionsMenu(menu, inflater);
      }
      @Override
      public boolean onOptionsItemSelected(MenuItem item) {
          switch (item.getItemId()) {
              case MENU_ITEM_SAVE:
                saveIpSettingsInfo();
      if(isIpDataInUiComplete())
                   finish();
                  return true;
              case MENU_ITEM_CANCEL:
                  finish();
                  return true;
          }
          return super.onOptionsItemSelected(item);
      }        
      private void updateIpSettingsInfo() {
        LOG("Static IP status updateIpSettingsInfo");
          ContentResolver contentResolver = getContentResolver();
          mUseStaticIpSwitch.setChecked(Settings.System.getInt(contentResolver, ETHERNET_USE_STATIC_IP, 0) != 0);
          for (int i = 0; i < mSettingNames.length; i++) {
              EditTextPreference preference = (EditTextPreference) findPreference(mPreferenceKeys[i]);
              String settingValue = Settings.System.getString(contentResolver, mSettingNames[i]);
              preference.setText(settingValue);
              preference.setSummary(settingValue);
          }
      }
      private void saveIpSettingsInfo() {
          ContentResolver contentResolver = getContentResolver();
    /*      
          if(!chageState)   
            return;
    */      
          if(!isIpDataInUiComplete()) 
          {     
             Toast.makeText(getActivity(), R.string.eth_ip_settings_please_complete_settings, Toast.LENGTH_LONG).show();
             return;
          }
      mIpConfiguration = new IpConfiguration();
      mStaticIpConfiguration = new StaticIpConfiguration();
          for (int i = 0; i < mSettingNames.length; i++) {
              EditTextPreference preference = (EditTextPreference) findPreference(mPreferenceKeys[i]);
              String text = preference.getText();
        try {
          switch (mPreferenceKeys[i]) {
         case KEY_IP_ADDRESS:
              mStaticIpConfiguration.ipAddress = new LinkAddress(InetAddress.getByName(text), 24);
            break;
         case KEY_GATEWAY:
            mStaticIpConfiguration.gateway = InetAddress.getByName(text);
            break;
         case KEY_NETMASK:
        mStaticIpConfiguration.domains = text;
            break;
         case KEY_DNS1:
            mStaticIpConfiguration.dnsServers.add(InetAddress.getByName(text));
            break;
         case KEY_DNS2:
            mStaticIpConfiguration.dnsServers.add(InetAddress.getByName(text));
            break;  
            }            
        } catch (Exception e) {
                  e.printStackTrace();
              }
              if ( null == text || TextUtils.isEmpty(text) ) {
                 Settings.System.putString(contentResolver, mSettingNames[i], null);
              }
              else {
                 Settings.System.putString(contentResolver, mSettingNames[i], text);
              }
          }
      mIpConfiguration.ipAssignment = IpAssignment.STATIC;
      mIpConfiguration.proxySettings = ProxySettings.STATIC;
          mIpConfiguration.staticIpConfiguration = mStaticIpConfiguration;  
      mEthernetManager = (EthernetManager) getSystemService(Context.ETHERNET_SERVICE);
      if (mUseStaticIpSwitch.isChecked())
              mEthernetManager.setConfiguration(mIpConfiguration); 
          Settings.System.putInt(contentResolver,ETHERNET_USE_STATIC_IP, mUseStaticIpSwitch.isChecked() ? 1 : 0);
          // disable ethernet
          boolean enable = Secure.getInt(getContentResolver(), "isEnthernetOn", 1) == 1;
      LOG("notify Secure.ETHERNET_ON changed. enable = " + enable);
          if(enable) {
            LOG("first disable");
            Secure.putInt(getContentResolver(), "isEnthernetOn", 0);
        try {
          Thread.sleep(500);
        } catch (InterruptedException e) {}   
        LOG("second enable");
            Secure.putInt(getContentResolver(), "isEnthernetOn", 1);      
          }
      }
      @Override
      public boolean onPreferenceTreeClick(Preference preference) {
          boolean result = true;     
          LOG("onPreferenceTreeClick()  chageState = " + chageState);
          chageState = true;
          return result;
      }
    @Override
      public boolean onPreferenceChange(Preference preference, Object newValue) {
          boolean result = true;
          String key = preference.getKey();
          LOG("onPreferenceChange() : key = " + key);
          if ( null == key ) {
              return true;
          }else if (key.equals(KEY_USE_STATIC_IP)) {
          }else if ( key.equals(KEY_IP_ADDRESS) 
                  || key.equals(KEY_GATEWAY)
                  || key.equals(KEY_NETMASK)
                  || key.equals(KEY_DNS1)
                  || key.equals(KEY_DNS2) ) { 
              String value = (String) newValue;       
              LOG("onPreferenceChange() : value = " + value);
              if ( TextUtils.isEmpty(value) ) {
                  ( (EditTextPreference)preference).setText(value);
                  preference.setSummary(value);
                  result = true;
              }
              else  if ( !isValidIpAddress(value) ) {
                  LOG("onPreferenceChange() : IP address user inputed is INVALID." );
                  Toast.makeText(getActivity(), R.string.ethernet_ip_settings_invalid_ip, Toast.LENGTH_LONG).show();
                  return false;
              }
              else {
                  ( (EditTextPreference)preference).setText(value);
                  preference.setSummary(value);
                  result = true;
              }
          }
          return result;
      }    
      private boolean isValidIpAddress(String value) {
          int start = 0;
          int end = value.indexOf('.');
          int numBlocks = 0;
          while (start < value.length()) {
              if ( -1 == end ) {
                  end = value.length();
              }
              try {
                  int block = Integer.parseInt(value.substring(start, end));
                  if ((block > 255) || (block < 0)) {
                      Log.w(TAG, "isValidIpAddress() : invalid 'block', block = " + block);
                      return false;
                  }
              } catch (NumberFormatException e) {
                  Log.w(TAG, "isValidIpAddress() : e = " + e);
                  return false;
              }
              numBlocks++;
              start = end + 1;
              end = value.indexOf('.', start);
          }
          return numBlocks == 4;
      }
      private boolean isIpDataInUiComplete() {
          ContentResolver contentResolver = getContentResolver();
          for (int i = 0; i < (mPreferenceKeys.length - 1); i++) {
              EditTextPreference preference = (EditTextPreference) findPreference(mPreferenceKeys[i]);
              String text = preference.getText();
              LOG("isIpDataInUiComplete() : text = " + text);
              if ( null == text || TextUtils.isEmpty(text) ) {
                  return false;
              }
          }
          return true;
      }
  }


到这一步 Settings 的修改就完成了,就能实现上图的效果了,你可以mm push看效果了


如果你编译报错,大概是 Settings 中没有添加对应的变量,我的本来就有的,


没有的可参考下面的加一下


frameworks\base\core\java\android\provider\Settings.java

// Intent actions for Settings
// ethernet
public static final String ETHERNET_STATIC_IP = "ethernet_static_ip";
public static final String ETHERNET_STATIC_GATEWAY = "ethernet_static_gateway";
public static final String ETHERNET_STATIC_NETMASK = "ethernet_static_netmask";
public static final String ETHERNET_STATIC_DNS1 = "ethernet_static_dns1";
public static final String ETHERNET_STATIC_DNS2 = "ethernet_static_dns2";
public static final String IS_ETHERNET_OPEN = "isEthernetOpen";
public static final String IS_ETHERNET_STATUC_OPEN = "isEthernetStaticOpen";

加完后你需要先 make update-api成功后,在重新 mm 编译应该就好了


5、修改 framework 层网卡相关的控制代码


frameworks\opt\net\ethernet\java\com\android\server\ethernet\EthernetNetworkFactory.java

将原来的 startIpManager() 直接替换为如下的方法


public void startIpManager() {
    if (DBG) {
        Log.d(TAG, String.format("starting IpManager(%s): mNetworkInfo=%s", mIface,
                mNetworkInfo));
    }
    LinkProperties linkProperties;
    IpConfiguration config = mEthernetManager.getConfiguration();
    if (config.getIpAssignment() == IpAssignment.STATIC) {
        if (!setStaticIpAddress(config.getStaticIpConfiguration())) {
            // We've already logged an error.
            return;
        }
        linkProperties = config.getStaticIpConfiguration().toLinkProperties(mIface);
        if (config.getProxySettings() == ProxySettings.STATIC ||
                config.getProxySettings() == ProxySettings.PAC) {
            // mIpManager.setHttpProxy(config.getHttpProxy());
            linkProperties.setHttpProxy(config.getHttpProxy());
        }
        String tcpBufferSizes = mContext.getResources().getString(
                com.android.internal.R.string.config_ethernet_tcp_buffers);     
        if (TextUtils.isEmpty(tcpBufferSizes) == false) {
            linkProperties.setTcpBufferSizes(tcpBufferSizes);
        }
    } else {
        mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, mHwAddr);
    }
    IpManager.Callback ipmCallback = new IpManager.Callback() {
        @Override
        public void onProvisioningSuccess(LinkProperties newLp) {
            mHandler.post(() -> onIpLayerStarted(newLp));
        }
        @Override
        public void onProvisioningFailure(LinkProperties newLp) {
            mHandler.post(() -> onIpLayerStopped(newLp));
        }
        @Override
        public void onLinkPropertiesChange(LinkProperties newLp) {
            mHandler.post(() -> updateLinkProperties(newLp));
        }
    };
    stopIpManager();
    mIpManager = new IpManager(mContext, mIface, ipmCallback);
    if (config.getProxySettings() == ProxySettings.STATIC ||
            config.getProxySettings() == ProxySettings.PAC) {
        mIpManager.setHttpProxy(config.getHttpProxy());
    }
    final String tcpBufferSizes = mContext.getResources().getString(
            com.android.internal.R.string.config_ethernet_tcp_buffers);
    if (!TextUtils.isEmpty(tcpBufferSizes)) {
        mIpManager.setTcpBufferSizes(tcpBufferSizes);
    }
    if (config.getIpAssignment() == IpAssignment.STATIC) {
        mIpManager.startProvisioning(config.getStaticIpConfiguration());
    } else {
        final ProvisioningConfiguration provisioningConfiguration =
                mIpManager.buildProvisioningConfiguration()
                        .withProvisioningTimeoutMs(0)
                        .build();
        mIpManager.startProvisioning(provisioningConfiguration);
    }
}

frameworks\opt\net\ethernet\java\com\android\server\ethernet\EthernetServiceImpl.java


在 EthernetServiceImpl 中增加网卡开关和静态IP开关的监听处理

  private final EthernetOpenedObserver mOpenObserver = new EthernetOpenedObserver();
  private final EthernetStaticObserver mStaticObserver = new EthernetStaticObserver();
  public EthernetServiceImpl(Context context) {
        .....
       mContext.getContentResolver().registerContentObserver(
            System.getUriFor(IS_ETHERNET_OPEN), false, mOpenObserver);
       mContext.getContentResolver().registerContentObserver(
            System.getUriFor(ETHERNET_USE_STATIC_IP), false, mStaticObserver);    
    }
    private void enforceChangePermission() {
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.CHANGE_NETWORK_STATE,
                "EthernetService");
    }
  @Override
    public void setConfiguration(IpConfiguration config) {
        if (!mStarted.get()) {
            Log.w(TAG, "System isn't ready enough to change ethernet configuration");
        }
       enforceChangePermission();  
        enforceConnectivityInternalPermission();
        .....
    }
  private boolean isStatic()
    {
   return Settings.System.getInt(mContext.getContentResolver(),ETHERNET_USE_STATIC_IP,0) ==1;
    } 
    private int getState()
    {
     return Settings.System.getInt(mContext.getContentResolver(), IS_ETHERNET_OPEN,0);
    }
  private final class EthernetOpenedObserver extends ContentObserver {
    public EthernetOpenedObserver() {
        super(new Handler());
    }
    @Override
    public void onChange(boolean selfChange, Uri uri, int userId) {
      super.onChange(selfChange, uri, userId);
        Log.i(TAG, "EthernetServiceImpl isEthernetOpen onChange....");
      if (getState() == 1) {
         if (isStatic()) {
                StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration();
                staticIpConfiguration.domains = Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_NETMASK);
                try {
                    staticIpConfiguration.gateway = InetAddress.getByName(Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_GATEWAY));
                    staticIpConfiguration.ipAddress = new LinkAddress(InetAddress.getByName(Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_IP)), 24);
                    staticIpConfiguration.dnsServers.add(InetAddress.getByName(Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_DNS1)));
                staticIpConfiguration.dnsServers.add(InetAddress.getByName(Settings.System.getString(mContext.getContentResolver(),Settings.ETHERNET_STATIC_DNS2)));
            }
            catch (Exception e){
                        e.printStackTrace();
                    }
                  mIpConfiguration.staticIpConfiguration = staticIpConfiguration;
              }
              mStarted.set(true);
          mTracker.start(mContext, mHandler);
      } 
     else { 
    mTracker.stop();    
     }
    }
}
private final class EthernetStaticObserver extends ContentObserver {
    public EthernetStaticObserver() {
        super(new Handler());
    }
    @Override
    public void onChange(boolean selfChange, Uri uri, int userId) {
        super.onChange(selfChange, uri, userId);
        Log.i(TAG, "EthernetServiceImpl isEthernetStaticOpen onChange....");
        if (!isStatic()) {
            mIpConfiguration = mEthernetConfigStore.readIpAndProxyConfigurations();
            mTracker.stop();
            mStarted.set(true);
            mTracker.start(mContext, mHandler);           
       }  
    }
 }


好了,到此就搞定了动图的效果了,开关以太网和设置静态IP上网,下面我们再来瞅瞅 6.0的有啥区别


Android6.0


效果图和上面的一样,这里就省略了,Settings 的修改和上面的一样,有两个细小的


区别就是


1、导包需要注意,EthernetSettings 和 EthernetStaticIP 中的 Preference


相关的包,在8.1中都换成了 support.v7 下面的,6.0 还是用原来的 android.preference


2、6.0 中的 MetricsLogger.ETHERNET 和 MetricsLogger.ETHERNET_STATIC


在8.1中替换为 MetricsEvent.ETHERNET 和 MetricsEvent.ETHERNET_STATIC


导包 com.android.internal.logging.MetricsLogger


frameworks/base/proto/src/metrics_constants.proto

      ETHERNET = 1144;
      ETHERNET_STATIC = 1145;

framework 方面的差异


EthernetServiceImpl.java 文件和 8.1 的一样,主要差别在 EthernetNetworkFactory.java

相比较而言没有 8.1 的复杂,下面贴一下完整的代码

frameworks\opt\net\ethernet\java\com\android\server\ethernet\EthernetNetworkFactory.java

class EthernetNetworkFactory {
    private static final String NETWORK_TYPE = "Ethernet";
    private static final String TAG = "EthernetNetworkFactory";
    private static final int NETWORK_SCORE = 110;
    private static final boolean DBG = true;
    public static final String ETHERNET_USE_STATIC_IP = Settings.IS_ETHERNET_STATUC_OPEN;
    /** Tracks interface changes. Called from NetworkManagementService. */
    private InterfaceObserver mInterfaceObserver;
    /** For static IP configuration */
    private EthernetManager mEthernetManager;
    /** To set link state and configure IP addresses. */
    private INetworkManagementService mNMService;
    /* To communicate with ConnectivityManager */
    private NetworkCapabilities mNetworkCapabilities;
    private NetworkAgent mNetworkAgent;
    private LocalNetworkFactory mFactory;
    private Context mContext;
    /** Product-dependent regular expression of interface names we track. */
    private static String mIfaceMatch = "";
    /** To notify Ethernet status. */
    private final RemoteCallbackList<IEthernetServiceListener> mListeners;
    /** Data members. All accesses to these must be synchronized(this). */
    private static String mIface = "";
    private String mHwAddr;
    private static boolean mLinkUp;
    private NetworkInfo mNetworkInfo;
    private LinkProperties mLinkProperties;
    EthernetNetworkFactory(RemoteCallbackList<IEthernetServiceListener> listeners) {
        mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_ETHERNET, 0, NETWORK_TYPE, "");
        mLinkProperties = new LinkProperties();
        initNetworkCapabilities();
        mListeners = listeners;
    }
    private class LocalNetworkFactory extends NetworkFactory {
        LocalNetworkFactory(String name, Context context, Looper looper) {
            super(looper, context, name, new NetworkCapabilities());
        }
        protected void startNetwork() {
            onRequestNetwork();
        }
        protected void stopNetwork() {
        }
    }
    /**
     * Updates interface state variables.
     * Called on link state changes or on startup.
     */
    private void updateInterfaceState(String iface, boolean up) {
        if (!mIface.equals(iface)) {
            return;
        }
        Log.d(TAG, "updateInterface: " + iface + " link " + (up ? "up" : "down"));
        synchronized(this) {
            mLinkUp = up;
            mNetworkInfo.setIsAvailable(up);
            if (!up) {
                // Tell the agent we're disconnected. It will call disconnect().
                mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr);
            }
            updateAgent();
            // set our score lower than any network could go
            // so we get dropped.  TODO - just unregister the factory
            // when link goes down.
            mFactory.setScoreFilter(up ? NETWORK_SCORE : -1);
        }
    }
    private class InterfaceObserver extends BaseNetworkObserver {
        @Override
        public void interfaceLinkStateChanged(String iface, boolean up) {
            updateInterfaceState(iface, up);
        }
        @Override
        public void interfaceAdded(String iface) {
            maybeTrackInterface(iface);
        }
        @Override
        public void interfaceRemoved(String iface) {
            stopTrackingInterface(iface);
        }
    }
    private void setInterfaceUp(String iface) {
        // Bring up the interface so we get link status indications.
        try {
            NetworkUtils.stopDhcp(iface);
            mNMService.setInterfaceUp(iface);
            String hwAddr = null;
            InterfaceConfiguration config = mNMService.getInterfaceConfig(iface);
            if (config == null) {
                Log.e(TAG, "Null iterface config for " + iface + ". Bailing out.");
                return;
            }
            synchronized (this) {
                if (!isTrackingInterface()) {
                    setInterfaceInfoLocked(iface, config.getHardwareAddress());
                    mNetworkInfo.setIsAvailable(true);
                    mNetworkInfo.setExtraInfo(mHwAddr);
                } else {
                    Log.e(TAG, "Interface unexpectedly changed from " + iface + " to " + mIface);
                    mNMService.setInterfaceDown(iface);
                }
            }
        } catch (RemoteException e) {
            Log.e(TAG, "Error upping interface " + mIface + ": " + e);
        }
    }
    private boolean maybeTrackInterface(String iface) {
        // If we don't already have an interface, and if this interface matches
        // our regex, start tracking it.
        if (!iface.matches(mIfaceMatch) || isTrackingInterface())
            return false;
        Log.d(TAG, "Started tracking interface " + iface);
        setInterfaceUp(iface);
        return true;
    }
    private void stopTrackingInterface(String iface) {
        if (!iface.equals(mIface))
            return;
        Log.d(TAG, "Stopped tracking interface " + iface);
        // TODO: Unify this codepath with stop().
        synchronized (this) {
            NetworkUtils.stopDhcp(mIface);
            setInterfaceInfoLocked("", null);
            mNetworkInfo.setExtraInfo(null);
            mLinkUp = false;
            mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr);
            updateAgent();
            mNetworkAgent = null;
            mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_ETHERNET, 0, NETWORK_TYPE, "");
            mLinkProperties = new LinkProperties();
        }
    }
    private boolean setStaticIpAddress(StaticIpConfiguration staticConfig) {
        if (staticConfig!=null && staticConfig.ipAddress != null &&
                staticConfig.gateway != null &&
                staticConfig.dnsServers.size() > 0) {
            try {
                Log.i(TAG, "Applying static IPv4 configuration to " + mIface + ": " + staticConfig);
                InterfaceConfiguration config = mNMService.getInterfaceConfig(mIface);
                config.setLinkAddress(staticConfig.ipAddress);
                mNMService.setInterfaceConfig(mIface, config);
                return true;
            } catch(RemoteException|IllegalStateException e) {
               Log.e(TAG, "Setting static IP address failed: " + e.getMessage());
            }
        } else {
            Log.e(TAG, "Invalid static IP configuration.");
        }
        return false;
    }
    public void updateAgent() {
        synchronized (EthernetNetworkFactory.this) {
            if (mNetworkAgent == null) return;
            if (DBG) {
                Log.i(TAG, "Updating mNetworkAgent with: " +
                      mNetworkCapabilities + ", " +
                      mNetworkInfo + ", " +
                      mLinkProperties);
            }
            mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
            mNetworkAgent.sendNetworkInfo(mNetworkInfo);
            mNetworkAgent.sendLinkProperties(mLinkProperties);
            // never set the network score below 0.
            mNetworkAgent.sendNetworkScore(mLinkUp? NETWORK_SCORE : 0);
        }
    }
    /* Called by the NetworkFactory on the handler thread. */
    public void onRequestNetwork() {
        // TODO: Handle DHCP renew.
        Thread dhcpThread = new Thread(new Runnable() {
            public void run() {
                if (DBG) Log.i(TAG, "dhcpThread(" + mIface + "): mNetworkInfo=" + mNetworkInfo);
                LinkProperties linkProperties;
                IpConfiguration config = mEthernetManager.getConfiguration();
              /*  if (config.getIpAssignment() == IpAssignment.STATIC) */
                if (isStatic())
                {
                    if (!setStaticIpAddress(config.getStaticIpConfiguration())) {
                        // We've already logged an error.
                        return;
                    }
                    linkProperties = config.getStaticIpConfiguration().toLinkProperties(mIface);
                } else {
                    mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, mHwAddr);
                    DhcpResults dhcpResults = new DhcpResults();
                    // TODO: Handle DHCP renewals better.
                    // In general runDhcp handles DHCP renewals for us, because
                    // the dhcp client stays running, but if the renewal fails,
                    // we will lose our IP address and connectivity without
                    // noticing.
                    if (!NetworkUtils.runDhcp(mIface, dhcpResults)) {
                        Log.e(TAG, "DHCP request error:" + NetworkUtils.getDhcpError());
                        // set our score lower than any network could go
                        // so we get dropped.
                        mFactory.setScoreFilter(-1);
                        // If DHCP timed out (as opposed to failing), the DHCP client will still be
                        // running, because in M we changed its timeout to infinite. Stop it now.
                        NetworkUtils.stopDhcp(mIface);
                        return;
                    }
                    linkProperties = dhcpResults.toLinkProperties(mIface);
                }
                if (config.getProxySettings() == ProxySettings.STATIC ||
                        config.getProxySettings() == ProxySettings.PAC) {
                    linkProperties.setHttpProxy(config.getHttpProxy());
                }
                String tcpBufferSizes = mContext.getResources().getString(
                        com.android.internal.R.string.config_ethernet_tcp_buffers);
                if (TextUtils.isEmpty(tcpBufferSizes) == false) {
                    linkProperties.setTcpBufferSizes(tcpBufferSizes);
                }
                synchronized(EthernetNetworkFactory.this) {
                    if (mNetworkAgent != null) {
                        Log.e(TAG, "Already have a NetworkAgent - aborting new request");
                        return;
                    }
                    mLinkProperties = linkProperties;
                    mNetworkInfo.setIsAvailable(true);
                    mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, mHwAddr);
                    // Create our NetworkAgent.
                    mNetworkAgent = new NetworkAgent(mFactory.getLooper(), mContext,
                            NETWORK_TYPE, mNetworkInfo, mNetworkCapabilities, mLinkProperties,
                            NETWORK_SCORE) {
                        public void unwanted() {
                            synchronized(EthernetNetworkFactory.this) {
                                if (this == mNetworkAgent) {
                                    NetworkUtils.stopDhcp(mIface);
                                    mLinkProperties.clear();
                                    mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null,
                                            mHwAddr);
                                    updateAgent();
                                    mNetworkAgent = null;
                                    try {
                                        mNMService.clearInterfaceAddresses(mIface);
                                    } catch (Exception e) {
                                        Log.e(TAG, "Failed to clear addresses or disable ipv6" + e);
                                    }
                                } else {
                                    Log.d(TAG, "Ignoring unwanted as we have a more modern " +
                                            "instance");
                                }
                            }
                        };
                    };
                }
            }
        });
        dhcpThread.start();
    }
    /**
     * Begin monitoring connectivity
     */
    public synchronized void start(Context context, Handler target) {
        // The services we use.
        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
        mNMService = INetworkManagementService.Stub.asInterface(b);
        mEthernetManager = (EthernetManager) context.getSystemService(Context.ETHERNET_SERVICE);
        // Interface match regex.
        mIfaceMatch = context.getResources().getString(
                com.android.internal.R.string.config_ethernet_iface_regex);
        // Create and register our NetworkFactory.
        mFactory = new LocalNetworkFactory(NETWORK_TYPE, context, target.getLooper());
        mFactory.setCapabilityFilter(mNetworkCapabilities);
        mFactory.setScoreFilter(-1); // this set high when we have an iface
        mFactory.register();
        mContext = context;
        // Start tracking interface change events.
        mInterfaceObserver = new InterfaceObserver();
        try {
            mNMService.registerObserver(mInterfaceObserver);
        } catch (RemoteException e) {
            Log.e(TAG, "Could not register InterfaceObserver " + e);
        }
        // If an Ethernet interface is already connected, start tracking that.
        // Otherwise, the first Ethernet interface to appear will be tracked.
        try {
            final String[] ifaces = mNMService.listInterfaces();
            for (String iface : ifaces) {
                synchronized(this) {
                    if (maybeTrackInterface(iface)) {
                        // We have our interface. Track it.
                        // Note: if the interface already has link (e.g., if we
                        // crashed and got restarted while it was running),
                        // we need to fake a link up notification so we start
                        // configuring it. Since we're already holding the lock,
                        // any real link up/down notification will only arrive
                        // after we've done this.
                        if (mNMService.getInterfaceConfig(iface).hasFlag("running")) {
                            updateInterfaceState(iface, true);
                        }
                        break;
                    }
                }
            }
        } catch (RemoteException e) {
            Log.e(TAG, "Could not get list of interfaces " + e);
        }
    }
    public synchronized void stop() {
        NetworkUtils.stopDhcp(mIface);
        // ConnectivityService will only forget our NetworkAgent if we send it a NetworkInfo object
        // with a state of DISCONNECTED or SUSPENDED. So we can't simply clear our NetworkInfo here:
        // that sets the state to IDLE, and ConnectivityService will still think we're connected.
        //
        // TODO: stop using explicit comparisons to DISCONNECTED / SUSPENDED in ConnectivityService,
        // and instead use isConnectedOrConnecting().
        mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr);
        mLinkUp = false;
        updateAgent();
        mLinkProperties = new LinkProperties();
        mNetworkAgent = null;
        setInterfaceInfoLocked("", null);
        mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_ETHERNET, 0, NETWORK_TYPE, "");
        mFactory.unregister();
    }
    private void initNetworkCapabilities() {
        mNetworkCapabilities = new NetworkCapabilities();
        mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET);
        mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
        mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
        // We have no useful data on bandwidth. Say 100M up and 100M down. :-(
        mNetworkCapabilities.setLinkUpstreamBandwidthKbps(100 * 1000);
        mNetworkCapabilities.setLinkDownstreamBandwidthKbps(100 * 1000);
    }
    public synchronized boolean isTrackingInterface() {
        return !TextUtils.isEmpty(mIface);
    }
    /**
     * Set interface information and notify listeners if availability is changed.
     * This should be called with the lock held.
     */
    private void setInterfaceInfoLocked(String iface, String hwAddr) {
        boolean oldAvailable = isTrackingInterface();
        mIface = iface;
        mHwAddr = hwAddr;
        boolean available = isTrackingInterface();
        if (oldAvailable != available) {
            int n = mListeners.beginBroadcast();
            for (int i = 0; i < n; i++) {
                try {
                    mListeners.getBroadcastItem(i).onAvailabilityChanged(available);
                } catch (RemoteException e) {
                    // Do nothing here.
                }
            }
            mListeners.finishBroadcast();
        }
    }
    synchronized void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
        if (isTrackingInterface()) {
            pw.println("Tracking interface: " + mIface);
            pw.increaseIndent();
            pw.println("MAC address: " + mHwAddr);
            pw.println("Link state: " + (mLinkUp ? "up" : "down"));
            pw.decreaseIndent();
        } else {
            pw.println("Not tracking any interface");
        }
        pw.println();
        pw.println("NetworkInfo: " + mNetworkInfo);
        pw.println("LinkProperties: " + mLinkProperties);
        pw.println("NetworkAgent: " + mNetworkAgent);
    }
        /*private boolean isStatic()*/
    private boolean isStatic()
    {
   return Settings.System.getInt(mContext.getContentResolver(),ETHERNET_USE_STATIC_IP,0) ==1;
    } 
}


修改文件源码下载


android 8.1添加Ethernet功能(settings+framework).zip

目录
相关文章
|
2月前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
121 1
|
3月前
|
IDE Android开发 iOS开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
|
16天前
|
IDE 开发工具 Android开发
移动应用开发之旅:探索Android和iOS平台
在这篇文章中,我们将深入探讨移动应用开发的两个主要平台——Android和iOS。我们将了解它们的操作系统、开发环境和工具,并通过代码示例展示如何在这两个平台上创建一个简单的“Hello World”应用。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和技巧,帮助你更好地理解和掌握移动应用开发。
40 17
|
2月前
|
Android开发
Android开发表情emoji功能开发
本文介绍了一种在Android应用中实现emoji表情功能的方法,通过将图片与表情字符对应,实现在`TextView`中的正常显示。示例代码展示了如何使用自定义适配器加载emoji表情,并在编辑框中输入或删除表情。项目包含完整的源码结构,可作为开发参考。视频演示和源码详情见文章内链接。
74 4
Android开发表情emoji功能开发
|
2月前
|
安全 Android开发 iOS开发
Android vs iOS:探索移动操作系统的设计与功能差异###
【10月更文挑战第20天】 本文深入分析了Android和iOS两个主流移动操作系统在设计哲学、用户体验、技术架构等方面的显著差异。通过对比,揭示了这两种系统各自的独特优势与局限性,并探讨了它们如何塑造了我们的数字生活方式。无论你是开发者还是普通用户,理解这些差异都有助于更好地选择和使用你的移动设备。 ###
55 3
|
2月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
111 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
3月前
|
开发工具 Android开发 iOS开发
安卓与iOS开发环境对比:选择适合你的平台
【9月更文挑战第26天】在移动应用开发的广阔天地中,安卓和iOS是两大巨头。它们各自拥有独特的优势和挑战,影响着开发者的选择和决策。本文将深入探讨这两个平台的开发环境,帮助你理解它们的核心差异,并指导你根据个人或项目需求做出明智的选择。无论你是初学者还是资深开发者,了解这些平台的异同都至关重要。让我们一起探索,找到最适合你的那片开发天地。
|
3月前
|
Android开发 开发者
Android平台无纸化同屏如何实现实时录像功能
Android平台无纸化同屏,如果需要本地录像的话,实现难度不大,只要复用之前开发的录像模块的就可以,对我们来说,同屏采集这块,只是数据源不同而已,如果是自采集的其他数据,我们一样可以编码录像。
|
Java Android开发 开发者
Android 悬浮窗功能的实现
Android 悬浮窗功能的实现
1393 2
Android 悬浮窗功能的实现
|
Android开发 容器 数据格式
Android 购物车功能的实现
首先,众所周知,ListView是Android最常用的控件,可以说是最简单的控件,也可以说是最复杂的控件。 作为一个Android初级开发者,可能会简单的ListView展示图文信息。 作为一个有一定项目开发经验的Android开发者来说,可能会遇到ListView的列表项中存在各种按钮的需求。
1172 0