In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article introduces the relevant knowledge of "how to solve the problem of APP positioning too frequently by Android". In the operation of actual cases, many people will encounter such a dilemma, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!
1. Background
Positioning is now one of the most basic and indispensable capabilities of many APP, especially for applications such as taxi hailing and takeout. However, the call to positioning can not be unrestrained, a little carelessly may lead to excessive power consumption of the device, and eventually cause users to uninstall the application.
The project I work in is an APP running in the background, and you need to get the current location in the background from time to time. In addition, many cooperative third-party libraries will be introduced in the project, and these libraries will also have the behavior of calling location. Therefore, we will often receive feedback from tests that our application consumes too much power due to frequent positioning.
When troubleshooting this problem, the author first ruled out the problem of our business logic, because each functional module in the project called the uniformly encapsulated interface of the positioning module, in which some statistics and monitoring of the calling frequency of the corresponding interface were made and the relevant log statements were printed, while the printing frequency and times of the log statements related to positioning in the problem log are all within a very reasonable range.
It was only then that I realized that the culprit of frequent positioning was not within us, but by a third-party database. So the problem is, with so many third-party libraries introduced, how do I know whose location calls are unreasonable? Although I typed log in the common location module in the project, the problem is that the third-party library can not be tuned to our internal interface. So can we go to a lower level to bury some statistics?
2. AOP
AOP, or aspect-oriented programming, is nothing new. As far as I understand it, AOP abstracts our code into a hierarchical structure, and then inserts some general logic between two layers through non-intrusive methods, which is often used to count burial points, log output, permission interception, and so on. For more information, you can search for related articles. Here we will not elaborate on AOP.
To count the calls to a method at the level of the application, it is clear that AOP is a good fit. The typical application of AOP in Android is AspectJ, so I decided to try AspectJ, but where is the most appropriate insertion point? I decided to look for the answer in the SDK source code.
3. Strategy exploration
First of all, let's take a look at how the location interface is generally called:
LocationManager locationManager = (LocationManager) context.getSystemService (Context.LOCATION_SERVICE); / / single positioning locationManager.requestSingleUpdate (provider, new MyLocationLisenter (), getLooper ()); / / continuous positioning locationManager.requestSingleUpdate (provider,minTime, minDistance, new MyLocationLisenter ())
Of course, there are not only these two interfaces, but also several overloaded interfaces, but by looking at the source code of LocationManager, we can find that all of them will be called to this method eventually:
/ LocationManager.java private void requestLocationUpdates (LocationRequest request, LocationListener listener, Looper looper, PendingIntent intent) {String packageName = mContext.getPackageName (); / / wrap the listener class ListenerTransport transport = wrapListener (listener, looper); try {mService.requestLocationUpdates (request, transport, intent, packageName);} catch (RemoteException e) {throw e.rethrowFromSystemServer ();}}
This seems to be an appropriate insertion point, but if you print log when this method is called through the comments of AspectJ (the specific use of AspectJ is not the focus of this article, which will not be explained here), you will find that the log you want is not typed at all after the compilation runs.
By understanding how AspectJ works, we can see why this approach doesn't work:
... After the class file is generated to before the dex file is generated, traverse and match all the pointcuts declared in the AspectJ file, and then weave the pre-declared code into the
LocationManager is a class in android.jar and does not participate in compilation (android.jar is located in the android device). This also declares that AspectJ's solution cannot meet the demand.
4. to open or find a new path or snap course
Soft can only come hard, I decided to sacrifice reflection + dynamic proxy kill, but also the premise is to find a suitable insertion point.
By reading the source code of the LocationManager above, you can see that the positioning operation is finally performed by the requestLocationUpdates method delegated to the member object mService. This mService is a good entry point, so now the idea is clear: first implement a proxy class for mService, and then execute some of your own embedded logic (such as typing log or uploading to the server, etc.) when the method we are interested in (requestLocationUpdates) is called. First implement the proxy class:
Public class ILocationManagerProxy implements InvocationHandler {private Object mLocationManager; public ILocationManagerProxy (Object locationManager) {this.mLocationManager = locationManager;} @ Override public Object invoke (Object proxy, Method method, Object [] args) throws Throwable {if (TextUtils.equals ("requestLocationUpdates", method.getName ()) {/ / get the current function call stack StackTraceElement [] stackTrace = Thread.currentThread (). GetStackTrace (); if (stackTrace = = null | | stackTrace.length
< 3) { return null; } StackTraceElement log = stackTrace[2]; String invoker = null; boolean foundLocationManager = false; for (int i = 0; i < stackTrace.length; i++) { StackTraceElement e = stackTrace[i]; if (TextUtils.equals(e.getClassName(), "android.location.LocationManager")) { foundLocationManager = true; continue; } //找到LocationManager外层的调用者 if (foundLocationManager && !TextUtils.equals(e.getClassName(), "android.location.LocationManager")) { invoker = e.getClassName() + "." + e.getMethodName(); //此处可将定位接口的调用者信息根据自己的需求进行记录,这里我将调用类、函数名、以及参数打印出来 Log.d("LocationTest", "invoker is " + invoker + "(" + args + ")"); break; } } } return method.invoke(mLocationManager, args); } } 以上这个代理的作用就是取代 LocationManager 的 mService 成员,而实际的 ILocationManager 将被这个代理包装。这样我就能对实际 ILocationManager 的方法进行插桩,比如可以打 log,或将调用信息记录在本地磁盘等。值得一提的是, 由于我只关心 requestLocationUpdates, 所以对这个方法进行了过滤,当然你也可以根据需要制定自己的过滤规则。代理类实现好了之后,接下来我们就要开始真正的 hook 操作了,因此我们实现如下方法: public static void hookLocationManager(LocationManager locationManager) { try { Object iLocationManager = null; Class locationManagerClazsz = Class.forName("android.location.LocationManager"); //获取LocationManager的mService成员 iLocationManager = getField(locationManagerClazsz, locationManager, "mService"); Class iLocationManagerClazz = Class.forName("android.location.ILocationManager"); //创建代理类 Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{iLocationManagerClazz}, new ILocationManagerProxy(iLocationManager)); //在这里移花接木,用代理类替换掉原始的ILocationManager setField(locationManagerClazsz, locationManager, "mService", proxy); } catch (Exception e) { e.printStackTrace(); } } 简单几行代码就可以完成 hook 操作了,使用方法也很简单,只需要将 LocationManager 实例传进这个方法就可以了。现在回想一下我们是怎么获取 LocationManager 实例的: LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); 咱们一般当然是想 hook 应用全局的定位接口调用了,聪明的你也许想到了在 Application 初始化的时候去执行 hook 操作。也就是 public class App extends Application { @Override public void onCreate() { LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); HookHelper.hookLocationManager(locationManager); super.onCreate(); } } 可是这样真的能保证全局的 LocationManager 都能被 hook 到吗?实测后你会发现还是有漏网之鱼的,例如如果你通过 Activity 的 context 获取到的 LocationManager 实例就不会被 hook 到,因为他跟 Application 中获取到的 LocationManager 完全不是同一个实例,想知道具体原因的话可参阅这里。 所以如果要 hook 到所有的 LocationManager 实例的话,我们还得去看看 LocationManager 到底是怎么被创建的。 //ContextImpl.java @Override public Object getSystemService(String name) { return SystemServiceRegistry.getSystemService(this, name); } 我们再到 SystemServiceRegistry 一探究竟 //SystemServiceRegistry.java final class SystemServiceRegistry { private static final String TAG = "SystemServiceRegistry"; ... static { ... //注册ServiceFetcher, ServiceFetcher就是用于创建LocationManager的工厂类 registerService(Context.LOCATION_SERVICE, LocationManager.class, new CachedServiceFetcher() { @Override public LocationManager createService(ContextImpl ctx) throws ServiceNotFoundException { IBinder b = ServiceManager.getServiceOrThrow(Context.LOCATION_SERVICE); return new LocationManager(ctx, ILocationManager.Stub.asInterface(b)); }}); ... } //所有ServiceFetcher与服务名称的映射 private static final HashMap>(); public static Object getSystemService (ContextImpl ctx, String name) {ServiceFetcher fetcher = SYSTEM_SERVICE_FETCHERS.get (name); return fetcher! = null? Fetcher.getService (ctx): null;} static abstract interface ServiceFetcher {T getService (ContextImpl ctx);}}
At this point, we know that the real place to create an LocationManager instance is in CachedServiceFetcher.createService, and the problem is simple. I called hookLocationManager where the LocationManager was created, so there was no slip-through. But to achieve this, we have to hook the CachedServiceFetcher corresponding to LocationService. The general idea is to replace the CachedServiceFetcher corresponding to LocationService in SYSTEM_SERVICE_FETCHERS with our implemented proxy class LMCachedServiceFetcherProxy, and call hookLocationManager in the proxy method. The code is as follows:
Public class LMCachedServiceFetcherProxy implements InvocationHandler {private Object mLMCachedServiceFetcher; public LMCachedServiceFetcherProxy (Object LMCachedServiceFetcher) {this.mLMCachedServiceFetcher = LMCachedServiceFetcher;} @ Override public Object invoke (Object proxy, Method method, Object [] args) throws Throwable {/ / Why intercept getService instead of createService? If (TextUtils.equals (method.getName (), "getService")) {Object result = method.invoke (mLMCachedServiceFetcher, args); if (result instanceof LocationManager) {/ / here hookLocationManager HookHelper.hookLocationManager ((LocationManager) result);} return result;} return method.invoke (mLMCachedServiceFetcher, args);}} / / HookHelper.java public static void hookSystemServiceRegistry () {try {Object systemServiceFetchers = null Class locationManagerClazsz = Class.forName ("android.app.SystemServiceRegistry"); / / get the SYSTEM_SERVICE_FETCHERS member of SystemServiceRegistry systemServiceFetchers = getField (locationManagerClazsz, null, "SYSTEM_SERVICE_FETCHERS"); if (systemServiceFetchers instanceof HashMap) {HashMap fetchersMap = (HashMap) systemServiceFetchers; Object locationServiceFetcher = fetchersMap.get (Context.LOCATION_SERVICE); Class serviceFetcheClazz = Class.forName ("android.app.SystemServiceRegistry$ServiceFetcher") / / create the proxy class Object proxy = Proxy.newProxyInstance (Thread.currentThread (). GetContextClassLoader (), new Class [] {serviceFetcheClazz}, new LMCachedServiceFetcherProxy (locationServiceFetcher)); / / replace the original ServiceFetcher if (fetchersMap.put (Context.LOCATION_SERVICE, proxy) = = locationServiceFetcher) {Log.d ("LocationTest", "hook success!") with the proxy class Catch (Exception e) {e.printStackTrace ();}}
You may have found that the place we mentioned above to create a LocationManager instance is in CachedServiceFetcher.createService, but here I only go to hook LocationManager when getService is called. This is because createService is called too early, even earlier than the initialization of Application, so we can only start with getService. After the above analysis, we know that CachedServiceFetcher.getService will be called every time you call context.getSystemService, but createService will not be called every time, because CachedServiceFetcher implements a caching mechanism internally to ensure that only one LocationManager instance can be created per context. Well, this gives rise to another problem, that is, the same LocationManager may be hook many times. This problem can also be solved. We just need to record each LocationManager instance that has been hook. The final code of HookHelper is as follows:
Public class HookHelper {public static final String TAG = "LocationHook"; private static final Set hooked = new HashSet (); public static void hookSystemServiceRegistry () {try {Object systemServiceFetchers = null; Class locationManagerClazsz = Class.forName ("android.app.SystemServiceRegistry"); / / get the SYSTEM_SERVICE_FETCHERS member of SystemServiceRegistry systemServiceFetchers = getField (locationManagerClazsz, null, "SYSTEM_SERVICE_FETCHERS"); if (systemServiceFetchers instanceof HashMap) {HashMap fetchersMap = (HashMap) systemServiceFetchers Object locationServiceFetcher = fetchersMap.get (Context.LOCATION_SERVICE); Class serviceFetcheClazz = Class.forName ("android.app.SystemServiceRegistry$ServiceFetcher"); / / create proxy class Object proxy = Proxy.newProxyInstance (Thread.currentThread (). GetContextClassLoader (), new Class [] {serviceFetcheClazz}, new LMCachedServiceFetcherProxy (locationServiceFetcher)) / replace the original ServiceFetcher if (fetchersMap.put (Context.LOCATION_SERVICE, proxy) = = locationServiceFetcher) {Log.d ("LocationTest", "hook success!");} catch (Exception e) {e.printStackTrace ();}} public static void hookLocationManager (LocationManager locationManager) {try {Object iLocationManager = null Class locationManagerClazsz = Class.forName ("android.location.LocationManager"); / / get the mService member of LocationManager iLocationManager = getField (locationManagerClazsz, locationManager, "mService"); if (hooked.contains (iLocationManager)) {return;// this instance has been hook} Class iLocationManagerClazz = Class.forName ("android.location.ILocationManager") / / create the proxy class Object proxy = Proxy.newProxyInstance (Thread.currentThread (). GetContextClassLoader (), new Class [] {iLocationManagerClazz}, new ILocationManagerProxy (iLocationManager)); / / replace the original ILocationManager setField (locationManagerClazsz, locationManager, "mService", proxy) with the proxy class here; / / record the instance hooked.add (proxy) that has been hook } catch (Exception e) {e.printStackTrace ();}} public static Object getField (Class clazz, Object target, String name) throws Exception {Field field = clazz.getDeclaredField (name); field.setAccessible (true); return field.get (target);} public static void setField (Class clazz, Object target, String name, Object value) throws Exception {Field field = clazz.getDeclaredField (name); field.setAccessible (true) Field.set (target, value);}} "how to solve the problem of APP positioning too frequently by Android" ends here, thank you for your reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.