【详解】Android远程http下载和动态注册第三方so lib库教程
一、背景
在开发Android应用程序的实现,有时候需要引入第三方so lib库,但第三方so库比较大,例如开源第三方播放组件ffmpeg库, 如果直接打包的apk包里面, 整个应用程序会大很多.经过查阅资料和实验,发现通过远程下载so文件,然后再动态注册so文件时可行的。主要需要解决下载so文件存放位置以及文件读写权限问题。
二、主要思路
1、首先把so放到网络上面,比如测试放到:http://codestudy.sinaapp.com//lib/test.so
2、应用启动时,启动异步线程下载so文件,并写入到/data/data/packageName/app_libs目录下面
3、调用System.load 注册so文件。因路径必须有执行权限,我们不能加载SD卡上的so,但可以通过调用context.getDir("libs", Context.MODE_PRIVATE)把so文件写入到应用程序的私有目录/data/data/packageName/app_libs。
三、代码实现
1、网络下载so文件,并写入到应用程序的私有目录/data/data/PackageName/app_libs
代码如下 | 复制代码 |
/** * 下载文件到/data/data/PackageName/app_libs下面 * @param context * @param url * @param fileName * @return */ public static File downloadHttpFileToLib(Context context, String url, String fileName) { long start = System.currentTimeMillis(); FileOutputStream outStream = null; InputStream is = null; File soFile = null; try { HttpClient client = new DefaultHttpClient(); HttpGet get = new HttpGet(url); HttpResponse response = client.execute(get); HttpEntity entity = response.getEntity(); File dir = context.getDir("libs", Context.MODE_PRIVATE); soFile = new File(dir, fileName); outStream = new FileOutputStream(soFile); is = entity.getContent(); if (is != null) { byte[] buf = new byte[1024]; int ch = -1; while ((ch = is.read(buf)) > 0) { outStream.write(buf, 0, ch); //Log.d(">>>httpDownloadFile:", "download 进行中...."); } } outStream.flush(); long end = System.currentTimeMillis(); Log.d(">>>httpDownloadFile cost time:", (end-start)/1000 + "s"); Log.d(">>>httpDownloadFile:", "download success"); return soFile; } catch (IOException e) { Log.d(">>>httpDownloadFile:", "download failed" + e.toString()); return null; } finally { if (outStream != null) { try { outStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } } |
2、调用System.load 注册so文件
代码如下 | 复制代码 |
new Thread(new Runnable() { @Override public void run() { File soFile = FileUtils.downloadHttpFileToLib(getApplicationContext(), "http://codestudy.sinaapp.com//lib/test.so", "test.so"); if (soFile != null) { try { Log.d(">>>loadAppFile load path:", soFile.getAbsolutePath()); System.load(soFile.getAbsolutePath()); } catch (Exception e) { Log.e(">>>loadAppFile load error:", "so load failed:" + e.toString()); } } } }).start(); |
四、需要解决的问题
1、so文件下载以及注册时机。测试发现libffmpeg.so 8M的文件单线程下载需要10-13s左右
2、so下载失败或者注册失败该怎么处理。例如so播放组件是否尝试采用android系统原生MediaPlayer进行播放
3、当初次so还没有下载完注册成功时,进入播放页面时,需要友好提示用户,比如loading 视频正在加载等等
4、无网络情况等等情况
五、说明
上面的demo经过3(2.3/4.2/4.4)实际机型测试可以正常使用,然后根据第四点列举问题完善以下,即可使用。
首先内部存储路径为/data/data/youPackageName/,下面讲解的各路径都是基于你自己的应用的内部存储路径下。所有内部存储中保存的文件在用户卸载应用的时候会被删除。
一、 files
1. Context.getFilesDir(),该方法返回/data/data/youPackageName/files的File对象。
2. Context.openFileInput()与Context.openFileOutput(),只能读取和写入files下的文件,返回的是FileInputStream和FileOutputStream对象。
3. Context.fileList(),返回files下所有的文件名,返回的是String[]对象。
4. Context.deleteFile(String),删除files下指定名称的文件。
二、cache
Context.getCacheDir(),该方法返回/data/data/youPackageName/cache的File对象。
三、getDir
getDir(String name, int mode),返回/data/data/youPackageName/下的指定名称的文件夹File对象,如果该文件夹不存在则用指定名称创建一个新的文件夹。
Android有一套自己的安全模型, 可参见Android开发文档。当应用程序安装时就会分配一个userid,当应用要去访问其他资源比如文件时,需要userid匹配。默认情况下 ,任何应用创建的文件,数据库, sharedpreferences都应该是私有的(位于/data/data/your_project/files/),其余程序无法访问。除非在创建时指明是MODE_WORLD_READABLE 或者 MODE_WORLD_WRITEABLE,只要这样其余程序才能正确访问。因为有这种Android读写文件的方法在安全上有所保障,进程打开文件时Android要求检查进程的user id。所以不能直接用java的api来打开,因为java的io函数没有提这个机制 。有一个问题系统必须已经root,并且文件浏览器取得root权限否则通过文件浏览器是看不到那些目录的。
一、私有文件夹下的文件存取(./data/data/com.包名/files/下面,相当于程序工作目录)
无法用java的api直接打开程序私有的数据 ,默认路径为/data/data/your_project/files/,从sdcard中去读文件,首先要把文件通过android-sdk-windowstoolsadb.exe把本地计算机上的文件copy到sdcard上去,adb.exe push e:/Android.txt/sdcard/, 不可以用adb.exe push e:Android.txt sdcard,注意斜杠的,在linux下和winodws下是不一样的, 同样: 把仿真器上的文件copy到本地计算机上用: adb pull ./sdcard/Android.txt e:/ ,读取路径可以用“/sdcard/Android.txt”也可以用“mnt/sdcard/Android.txt”
如:
FileReader file = new FileReader("Android.txt");
这里特别强调私有数据!言外之意是如果某个文件或者数据不是程序私有的,既访问它时无须经过Android的权限检查,那么还是可以用java的io api来直接访问的。所谓的非私有数据是只放在sdcard上的文件或者数据,
可以用java的io api来直接打开sdcard上文件。
如:
FileReader file = new FileReader("/sdcard/Android.txt");
如果要打开程序自己私有的文件和数据,那必须使用Activity提供openFileOutput和openFileInput方法。创建程序私有的文件,由于权限方面的要求,必须使用activity提供的Android读写文件方法。
如:
代码如下 | 复制代码 |
import java.io.FileInputStream; import java.io.FileOutputStream; import org.apache.http.util.EncodingUtils; public void writeFileData(String fileName,String message){ try{ FileOutputStream fout = openFileOutput(fileName, MODE_PRIVATE); //OutputStreamWriter outWriter = new OutputStreamWriter(fout); byte [] bytes = message.getBytes(); fout.write(bytes); fout.close(); } catch(Exception e){ e.printStackTrace(); } } public String readFileData(String fileName){ String res=""; try{ //FileInputStream fin = openFileInput(fileName); //InputStreamReader inReader = new InputStreamReader(fin); //或者 FileInputStream fin = new FileInputStream(fileName); int length = fin.available(); byte [] buffer = new byte[length]; fin.read(buffer); res = EncodingUtils.getString(buffer, "UTF-8"); fin.close(); } catch(Exception e){ e.printStackTrace(); } return res; } |
二、从resource中的raw文件夹中获取文件并读取数据(资源文件只能读不能写,在"程序名resrawFileName.txt")
代码如下 | 复制代码 |
public String getFromRaw(String fileName){ String res = ""; try{ InputStream in = getResources().openRawResource(R.raw.test1); //资源在Testresrawtest1.txt int length = in.available(); byte [] buffer = new byte[length]; in.read(buffer); //依bbi.txt的编码类型选择合适的编码,如果不调整会乱码 res = EncodingUtils.getString(buffer, "UTF-8"); //res =EncodingUtils.getString(buffer, "BIG5"); //res =EncodingUtils.getString(buffer, "UNICODE"); in.close(); } catch(Exception e){ e.printStackTrace(); } return res ; } |
三、从asset中获取文件并读取数据(资源文件只能读不能写,在"程序名assertsFileName.txt")
代码如下 | 复制代码 |
public String getFromAsset(String fileName){ String res=""; try{ InputStream in = getResources().getAssets().open(fileName); int length = in.available(); byte [] buffer = new byte[length]; in.read(buffer); res = EncodingUtils.getString(buffer, "UTF-8"); } catch(Exception e){ e.printStackTrace(); } return res; } |
一、获取线程池的三种方法:
1、ExecutorService pool = Executors.newFixedThreadPool
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。
2、ExecutorService pool = Executors.newSingleThreadExecutor
创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的 newFixedThreadPool(1) 不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。
3、ExecutorService pool = Executors.newCachedThreadPool
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。注意,可以使用 ThreadPoolExecutor 构造方法创建具有类似属性但细节不同(例如超时参数)的线程池。
二、线程池类 java.util.concurrent.ThreadPoolExecutor讲解
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue workQueue,RejectedExecutionHandler handler)
参数说明
corePoolSize: 线程池维护线程的最少数量
maximumPoolSize:线程池维护线程的最大数量
keepAliveTime: 线程池维护线程所允许的空闲时间
unit: 线程池维护线程所允许的空闲时间的单位
workQueue: 线程池所使用的缓冲队列
handler: 线程池对拒绝任务的处理策略
线程创建规则
一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是 Runnable类型对象的run()方法。
当一个任务通过execute(Runnable)方法欲添加到线程池时:
l 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
2 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
3 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
4 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
5 当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。
核心和最大池大小
ThreadPoolExecutor 将根据 corePoolSize(参见 getCorePoolSize())和 maximumPoolSize(参见 getMaximumPoolSize())设置的边界自动调整池大小。当新任务在方法execute(java.lang.Runnable) 中提交时,如果运行的线程少于 corePoolSize,则创建新线程来处理请求,即使其他辅助线程是空闲的。如果运行的线程多于 corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程。如果设置的 corePoolSize 和 maximumPoolSize 相同,则创建了固定大小的线程池。如果将 maximumPoolSize 设置为基本的无界值(如 Integer.MAX_VALUE),则允许池适应任意数量的并发任务。在大多数情况下,核心和最大池大小仅基于构造来设置,不过也可以使用setCorePoolSize(int) 和 setMaximumPoolSize(int) 进行动态更改。
创建新线程
使用 ThreadFactory 创建新线程。如果没有另外说明,则在同一个 ThreadGroup 中一律使用 Executors.defaultThreadFactory() 创建线程,并且这些线程具有相同的NORM_PRIORITY 优先级和非守护进程状态。通过提供不同的 ThreadFactory,可以改变线程的名称、线程组、优先级、守护进程状态,等等。如果从 newThread 返回null 时 ThreadFactory 未能创建线程,则执行程序将继续运行,但不能执行任何任务。如果池中当前有多于 corePoolSize 的线程,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止(参见 getKeepAliveTime(java.util.concurrent.TimeUnit))。这提供了当池处于非活动状态时减少资源消耗的方法。如果池后来变得更为活动,则可以创建新的线程。也可以使用方法 setKeepAliveTime(long, java.util.concurrent.TimeUnit) 动态地更改此参数。使用 Long.MAX_VALUE TimeUnit.NANOSECONDS 的值在关闭前有效地从以前的终止状态禁用空闲线程。
排队
所有 BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:
A. 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。
B. 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
C. 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。
execute()执行
execute()方法中,调用了三个私有方法
addIfUnderCorePoolSize():在线程池大小小于核心线程池大小的情况下,扩展线程池
addIfUnderMaximumPoolSize():在线程池大小小于线程池大小上限的情况下,扩展线程池
ensureQueuedTaskHandled():保证在线程池关闭的情况下,新加入队列的线程也能正确处理
线程池的停止和关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
临时接受支付宝支付任务,最初研究旧版本,后来发现新版本更简单明了优化,使用最新版的,看见旧版的写出来的人多,新版的少,咱这最精炼的通过实践滴,与大家共同进步。
1.下载移动支付接口SDK2.0标准版,解压取出:
(1)从客户端alipay-sdk-common文件夹中取出alipaysdk.jar 、alipaysecsdk.jar 、alipayutdid.jar放入新建项目libs中,android4.0之后只要放入免手动导入,低于这个版本的按旧方法手动导入。
(2)从客户端Demo中取出Base64.java 、Result.java、SignUtils.java放在src中,对应支持的。
2.权限开通:
代码如下 | 复制代码 |
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" /> |
3.支付接口调用:
代码如下 | 复制代码 |
/** * 通过支付宝支付订单 *void * @exception * @since 1.0.0 */ public void pay(final String orderInfo, final String sign){ threadManager.startTaskThread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub // 构造PayTask 对象 PayTask alipay = new PayTask(activity); //拼接成完整支付信息(订单+签名) final String payInfo = orderInfo + "&sign="" + sign + """ + "&" + getSignType(); // 调用支付接口 String result = alipay.pay(payInfo); payResult = Base64.encode(result.getBytes()); Result rtl = new Result(result); //解析支付结果 //TextUtils.equals(resultStatus, "9000") //支付结果错误码: /*9000:订单支付成功 *8000:正在处理中("支付结果确认中") 代表支付结果因为支付渠道原因或者系统原因还在等待支付结果确认,最终交易是否成功以服务端异步通知为准(小概率状态) *4000:订单支付失败 *6001:用户中途取消 *6002:网络连接出错 */ final String resultStatus = rtl.resultStatus; //支付错误码 GoloLog.d(ALIPAY_PAY_KEY, "get alipay result status: "+ resultStatus); if (activity != null) { activity.runOnUiThread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub if (TextUtils.equals(resultStatus, "9000")) { //9000:订单支付成功 Toast.makeText(activity, "支付成功", Toast.LENGTH_SHORT).show(); }else { // 判断resultStatus 为非“9000”则代表可能支付失败 // “8000” 代表支付结果因为支付渠道原因或者系统原因还在等待支付结果确认,最终交易是否成功以服务端异步通知为准(小概率状态) if (TextUtils.equals(resultStatus, "8000")) { Toast.makeText(activity, "支付结果确认中", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(activity, "支付失败", Toast.LENGTH_SHORT).show(); } } } }); } } }); } |
4.个人界面调用:
orderInfo 订单内容
strsign 签名
代码如下 | 复制代码 |
//支付宝支付 private void toAlipay() { // TODO Auto-generated method stub String orderInfo = OrderBean.getOrderInfo(); String strsign = OrderBean.getOrderSign(); alipayPayHandler.pay(orderInfo, strsign); } |
项目需要,在ListView中显示多张图片,用到了GridView,不过如果使用普通的GridView,Item仅仅只是显示一部分,超出第一行以后的都无法显示了,这个很无语,所以又得继承下GridView重写onMeasure方法去测量子控件的宽高了..
这里只是贴出自定义GridView的代码,直接在xml中使用,ListView的Adapter中调用即可:
代码如下 | 复制代码 |
public class GridViewForListView extends GridView { public GridViewForListView(Context context) { super(context); } public GridViewForListView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec); } } |
相关文章
- ps动态环绕动画效果是现在很多人都非常喜欢的,大多数人还不知道ps动态环绕动画效果怎么制作下面文章就给大家介绍下ps怎么制作科技感十足的动态环绕动画效果,一起来看看...2017-07-06
- 下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
- 这篇文章主要介绍了vue 实现动态路由的方法,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下...2020-07-06
- 这篇文章主要介绍了C#实现HTTP下载文件的方法,包括了HTTP通信的创建、本地文件的写入等,非常具有实用价值,需要的朋友可以参考下...2020-06-25
Android开发中findViewById()函数用法与简化
findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20- 如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
- 夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
- 为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
- 如果我们要在Android应用APP中加载html5页面,我们可以使用WebView,本文我们分享两个WebView加载html5页面实例应用。 实例一:WebView加载html5实现炫酷引导页面大多...2016-09-20
- 举一个案例:复制代码 代码如下:<?phpclass Downfile { function downserver($file_name){$file_path = "./img/".$file_name;//转码,文件名转为gb2312解决中文乱码$file_name = iconv("utf-8","gb2312",$file_name...2014-06-07
- 深入理解Android中View和ViewGroup从组成架构上看,似乎ViewGroup在View之上,View需要继承ViewGroup,但实际上不是这样的。View是基类,ViewGroup是它的子类。本教程我们深...2016-09-20
- 下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
- 今天我们来给大家介绍下在Vue开发中我们经常会碰到的一种需求场景,本文主要介绍了Vue动态查询规则生成组件,需要的朋友们下面随着小编来一起学习学习吧...2021-05-27
- java开发的Android应用,性能一直是一个大问题,,或许是Java语言本身比较消耗内存。本文我们来谈谈Android 性能优化之MemoryFile文件读写。 Android匿名共享内存对外A...2016-09-20
- TextView默认是横着显示了,今天我们一起来看看Android设置TextView竖着显示如何来实现吧,今天我们就一起来看看操作细节,具体的如下所示。 在开发Android程序的时候,...2016-10-02
- 这篇文章介绍了c#动态调用Webservice的两种方法实例,有需要的朋友可以参考一下...2020-06-25
- 这篇文章主要为大家详细介绍了Visual Studio 2015下载和安装图文教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-22
android.os.BinderProxy cannot be cast to com解决办法
本文章来给大家介绍关于android.os.BinderProxy cannot be cast to com解决办法,希望此文章对各位有帮助呀。 Android在绑定服务的时候出现java.lang.ClassCastExc...2016-09-20- 本文详细讲解了SQLServer中执行动态SQL的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2022-05-19
- 想在网页中动态地显示当前系统的时间,找了好多,不过都是一些停在那里不动的。。。不过皇天不负有心人,终于让我找到了...2020-06-25